github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/cmd/compile/internal/ssa/writebarrier.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ssa 6 7 import ( 8 "cmd/internal/obj" 9 "cmd/internal/src" 10 ) 11 12 // needwb returns whether we need write barrier for store op v. 13 // v must be Store/Move/Zero. 14 func needwb(v *Value) bool { 15 t, ok := v.Aux.(Type) 16 if !ok { 17 v.Fatalf("store aux is not a type: %s", v.LongString()) 18 } 19 if !t.HasPointer() { 20 return false 21 } 22 if IsStackAddr(v.Args[0]) { 23 return false // write on stack doesn't need write barrier 24 } 25 return true 26 } 27 28 // writebarrier pass inserts write barriers for store ops (Store, Move, Zero) 29 // when necessary (the condition above). It rewrites store ops to branches 30 // and runtime calls, like 31 // 32 // if writeBarrier.enabled { 33 // writebarrierptr(ptr, val) 34 // } else { 35 // *ptr = val 36 // } 37 // 38 // A sequence of WB stores for many pointer fields of a single type will 39 // be emitted together, with a single branch. 40 func writebarrier(f *Func) { 41 if !f.fe.UseWriteBarrier() { 42 return 43 } 44 45 var sb, sp, wbaddr, const0 *Value 46 var writebarrierptr, typedmemmove, typedmemclr *obj.LSym 47 var stores, after []*Value 48 var sset *sparseSet 49 var storeNumber []int32 50 51 for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no stores to expand 52 // first, identify all the stores that need to insert a write barrier. 53 // mark them with WB ops temporarily. record presence of WB ops. 54 hasStore := false 55 for _, v := range b.Values { 56 switch v.Op { 57 case OpStore, OpMove, OpZero: 58 if needwb(v) { 59 switch v.Op { 60 case OpStore: 61 v.Op = OpStoreWB 62 case OpMove: 63 v.Op = OpMoveWB 64 case OpZero: 65 v.Op = OpZeroWB 66 } 67 hasStore = true 68 } 69 } 70 } 71 if !hasStore { 72 continue 73 } 74 75 if wbaddr == nil { 76 // lazily initialize global values for write barrier test and calls 77 // find SB and SP values in entry block 78 initpos := f.Entry.Pos 79 for _, v := range f.Entry.Values { 80 if v.Op == OpSB { 81 sb = v 82 } 83 if v.Op == OpSP { 84 sp = v 85 } 86 if sb != nil && sp != nil { 87 break 88 } 89 } 90 if sb == nil { 91 sb = f.Entry.NewValue0(initpos, OpSB, f.Config.Types.Uintptr) 92 } 93 if sp == nil { 94 sp = f.Entry.NewValue0(initpos, OpSP, f.Config.Types.Uintptr) 95 } 96 wbsym := &ExternSymbol{Sym: f.fe.Syslook("writeBarrier")} 97 wbaddr = f.Entry.NewValue1A(initpos, OpAddr, f.Config.Types.UInt32Ptr, wbsym, sb) 98 writebarrierptr = f.fe.Syslook("writebarrierptr") 99 typedmemmove = f.fe.Syslook("typedmemmove") 100 typedmemclr = f.fe.Syslook("typedmemclr") 101 const0 = f.ConstInt32(initpos, f.Config.Types.UInt32, 0) 102 103 // allocate auxiliary data structures for computing store order 104 sset = f.newSparseSet(f.NumValues()) 105 defer f.retSparseSet(sset) 106 storeNumber = make([]int32, f.NumValues()) 107 } 108 109 // order values in store order 110 b.Values = storeOrder(b.Values, sset, storeNumber) 111 112 again: 113 // find the start and end of the last contiguous WB store sequence. 114 // a branch will be inserted there. values after it will be moved 115 // to a new block. 116 var last *Value 117 var start, end int 118 values := b.Values 119 for i := len(values) - 1; i >= 0; i-- { 120 w := values[i] 121 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpZeroWB { 122 if last == nil { 123 last = w 124 end = i + 1 125 } 126 } else { 127 if last != nil { 128 start = i + 1 129 break 130 } 131 } 132 } 133 stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing 134 after = append(after[:0], b.Values[end:]...) 135 b.Values = b.Values[:start] 136 137 // find the memory before the WB stores 138 mem := stores[0].MemoryArg() 139 pos := stores[0].Pos 140 bThen := f.NewBlock(BlockPlain) 141 bElse := f.NewBlock(BlockPlain) 142 bEnd := f.NewBlock(b.Kind) 143 bThen.Pos = pos 144 bElse.Pos = pos 145 bEnd.Pos = b.Pos 146 b.Pos = pos 147 148 // set up control flow for end block 149 bEnd.SetControl(b.Control) 150 bEnd.Likely = b.Likely 151 for _, e := range b.Succs { 152 bEnd.Succs = append(bEnd.Succs, e) 153 e.b.Preds[e.i].b = bEnd 154 } 155 156 // set up control flow for write barrier test 157 // load word, test word, avoiding partial register write from load byte. 158 types := &f.Config.Types 159 flag := b.NewValue2(pos, OpLoad, types.UInt32, wbaddr, mem) 160 flag = b.NewValue2(pos, OpNeq32, types.Bool, flag, const0) 161 b.Kind = BlockIf 162 b.SetControl(flag) 163 b.Likely = BranchUnlikely 164 b.Succs = b.Succs[:0] 165 b.AddEdgeTo(bThen) 166 b.AddEdgeTo(bElse) 167 bThen.AddEdgeTo(bEnd) 168 bElse.AddEdgeTo(bEnd) 169 170 // for each write barrier store, append write barrier version to bThen 171 // and simple store version to bElse 172 memThen := mem 173 memElse := mem 174 for _, w := range stores { 175 ptr := w.Args[0] 176 pos := w.Pos 177 178 var fn *obj.LSym 179 var typ *ExternSymbol 180 var val *Value 181 switch w.Op { 182 case OpStoreWB: 183 fn = writebarrierptr 184 val = w.Args[1] 185 case OpMoveWB: 186 fn = typedmemmove 187 val = w.Args[1] 188 typ = &ExternSymbol{Sym: w.Aux.(Type).Symbol()} 189 case OpZeroWB: 190 fn = typedmemclr 191 typ = &ExternSymbol{Sym: w.Aux.(Type).Symbol()} 192 } 193 194 // then block: emit write barrier call 195 volatile := w.Op == OpMoveWB && isVolatile(val) 196 memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile) 197 198 // else block: normal store 199 switch w.Op { 200 case OpStoreWB: 201 memElse = bElse.NewValue3A(pos, OpStore, TypeMem, w.Aux, ptr, val, memElse) 202 case OpMoveWB: 203 memElse = bElse.NewValue3I(pos, OpMove, TypeMem, w.AuxInt, ptr, val, memElse) 204 memElse.Aux = w.Aux 205 case OpZeroWB: 206 memElse = bElse.NewValue2I(pos, OpZero, TypeMem, w.AuxInt, ptr, memElse) 207 memElse.Aux = w.Aux 208 } 209 210 if !f.WBPos.IsKnown() { 211 f.WBPos = pos 212 } 213 if f.fe.Debug_wb() { 214 f.Warnl(pos, "write barrier") 215 } 216 } 217 218 // merge memory 219 // Splice memory Phi into the last memory of the original sequence, 220 // which may be used in subsequent blocks. Other memories in the 221 // sequence must be dead after this block since there can be only 222 // one memory live. 223 bEnd.Values = append(bEnd.Values, last) 224 last.Block = bEnd 225 last.reset(OpPhi) 226 last.Type = TypeMem 227 last.AddArg(memThen) 228 last.AddArg(memElse) 229 for _, w := range stores { 230 if w != last { 231 w.resetArgs() 232 } 233 } 234 for _, w := range stores { 235 if w != last { 236 f.freeValue(w) 237 } 238 } 239 240 // put values after the store sequence into the end block 241 bEnd.Values = append(bEnd.Values, after...) 242 for _, w := range after { 243 w.Block = bEnd 244 } 245 246 // if we have more stores in this block, do this block again 247 for _, w := range b.Values { 248 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpZeroWB { 249 goto again 250 } 251 } 252 } 253 } 254 255 // wbcall emits write barrier runtime call in b, returns memory. 256 // if valIsVolatile, it moves val into temp space before making the call. 257 func wbcall(pos src.XPos, b *Block, fn *obj.LSym, typ *ExternSymbol, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { 258 config := b.Func.Config 259 260 var tmp GCNode 261 if valIsVolatile { 262 // Copy to temp location if the source is volatile (will be clobbered by 263 // a function call). Marshaling the args to typedmemmove might clobber the 264 // value we're trying to move. 265 t := val.Type.ElemType() 266 tmp = b.Func.fe.Auto(val.Pos, t) 267 aux := &AutoSymbol{Node: tmp} 268 mem = b.NewValue1A(pos, OpVarDef, TypeMem, tmp, mem) 269 tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), aux, sp) 270 siz := t.Size() 271 mem = b.NewValue3I(pos, OpMove, TypeMem, siz, tmpaddr, val, mem) 272 mem.Aux = t 273 val = tmpaddr 274 } 275 276 // put arguments on stack 277 off := config.ctxt.FixedFrameSize() 278 279 if typ != nil { // for typedmemmove 280 taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb) 281 off = round(off, taddr.Type.Alignment()) 282 arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) 283 mem = b.NewValue3A(pos, OpStore, TypeMem, ptr.Type, arg, taddr, mem) 284 off += taddr.Type.Size() 285 } 286 287 off = round(off, ptr.Type.Alignment()) 288 arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) 289 mem = b.NewValue3A(pos, OpStore, TypeMem, ptr.Type, arg, ptr, mem) 290 off += ptr.Type.Size() 291 292 if val != nil { 293 off = round(off, val.Type.Alignment()) 294 arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) 295 mem = b.NewValue3A(pos, OpStore, TypeMem, val.Type, arg, val, mem) 296 off += val.Type.Size() 297 } 298 off = round(off, config.PtrSize) 299 300 // issue call 301 mem = b.NewValue1A(pos, OpStaticCall, TypeMem, fn, mem) 302 mem.AuxInt = off - config.ctxt.FixedFrameSize() 303 304 if valIsVolatile { 305 mem = b.NewValue1A(pos, OpVarKill, TypeMem, tmp, mem) // mark temp dead 306 } 307 308 return mem 309 } 310 311 // round to a multiple of r, r is a power of 2 312 func round(o int64, r int64) int64 { 313 return (o + r - 1) &^ (r - 1) 314 } 315 316 // IsStackAddr returns whether v is known to be an address of a stack slot 317 func IsStackAddr(v *Value) bool { 318 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 319 v = v.Args[0] 320 } 321 switch v.Op { 322 case OpSP: 323 return true 324 case OpAddr: 325 return v.Args[0].Op == OpSP 326 } 327 return false 328 } 329 330 // isVolatile returns whether v is a pointer to argument region on stack which 331 // will be clobbered by a function call. 332 func isVolatile(v *Value) bool { 333 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 334 v = v.Args[0] 335 } 336 return v.Op == OpSP 337 }