github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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 var val *Value 176 ptr := w.Args[0] 177 var typ interface{} 178 if w.Op != OpStoreWB { 179 typ = &ExternSymbol{Sym: w.Aux.(Type).Symbol()} 180 } 181 pos = w.Pos 182 183 var fn *obj.LSym 184 switch w.Op { 185 case OpStoreWB: 186 fn = writebarrierptr 187 val = w.Args[1] 188 case OpMoveWB: 189 fn = typedmemmove 190 val = w.Args[1] 191 case OpZeroWB: 192 fn = typedmemclr 193 } 194 195 // then block: emit write barrier call 196 volatile := w.Op == OpMoveWB && isVolatile(val) 197 memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile) 198 199 // else block: normal store 200 switch w.Op { 201 case OpStoreWB: 202 memElse = bElse.NewValue3A(pos, OpStore, TypeMem, w.Aux, ptr, val, memElse) 203 case OpMoveWB: 204 memElse = bElse.NewValue3I(pos, OpMove, TypeMem, w.AuxInt, ptr, val, memElse) 205 memElse.Aux = w.Aux 206 case OpZeroWB: 207 memElse = bElse.NewValue2I(pos, OpZero, TypeMem, w.AuxInt, ptr, memElse) 208 memElse.Aux = w.Aux 209 } 210 211 if !f.WBPos.IsKnown() { 212 f.WBPos = pos 213 } 214 if f.fe.Debug_wb() { 215 f.Warnl(pos, "write barrier") 216 } 217 } 218 219 // merge memory 220 // Splice memory Phi into the last memory of the original sequence, 221 // which may be used in subsequent blocks. Other memories in the 222 // sequence must be dead after this block since there can be only 223 // one memory live. 224 bEnd.Values = append(bEnd.Values, last) 225 last.Block = bEnd 226 last.reset(OpPhi) 227 last.Type = TypeMem 228 last.AddArg(memThen) 229 last.AddArg(memElse) 230 for _, w := range stores { 231 if w != last { 232 w.resetArgs() 233 } 234 } 235 for _, w := range stores { 236 if w != last { 237 f.freeValue(w) 238 } 239 } 240 241 // put values after the store sequence into the end block 242 bEnd.Values = append(bEnd.Values, after...) 243 for _, w := range after { 244 w.Block = bEnd 245 } 246 247 // if we have more stores in this block, do this block again 248 for _, w := range b.Values { 249 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpZeroWB { 250 goto again 251 } 252 } 253 } 254 } 255 256 // wbcall emits write barrier runtime call in b, returns memory. 257 // if valIsVolatile, it moves val into temp space before making the call. 258 func wbcall(pos src.XPos, b *Block, fn *obj.LSym, typ interface{}, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { 259 config := b.Func.Config 260 261 var tmp GCNode 262 if valIsVolatile { 263 // Copy to temp location if the source is volatile (will be clobbered by 264 // a function call). Marshaling the args to typedmemmove might clobber the 265 // value we're trying to move. 266 t := val.Type.ElemType() 267 tmp = b.Func.fe.Auto(val.Pos, t) 268 aux := &AutoSymbol{Node: tmp} 269 mem = b.NewValue1A(pos, OpVarDef, TypeMem, tmp, mem) 270 tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), aux, sp) 271 siz := t.Size() 272 mem = b.NewValue3I(pos, OpMove, TypeMem, siz, tmpaddr, val, mem) 273 mem.Aux = t 274 val = tmpaddr 275 } 276 277 // put arguments on stack 278 off := config.ctxt.FixedFrameSize() 279 280 if typ != nil { // for typedmemmove 281 taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb) 282 off = round(off, taddr.Type.Alignment()) 283 arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) 284 mem = b.NewValue3A(pos, OpStore, TypeMem, ptr.Type, arg, taddr, mem) 285 off += taddr.Type.Size() 286 } 287 288 off = round(off, ptr.Type.Alignment()) 289 arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) 290 mem = b.NewValue3A(pos, OpStore, TypeMem, ptr.Type, arg, ptr, mem) 291 off += ptr.Type.Size() 292 293 if val != nil { 294 off = round(off, val.Type.Alignment()) 295 arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) 296 mem = b.NewValue3A(pos, OpStore, TypeMem, val.Type, arg, val, mem) 297 off += val.Type.Size() 298 } 299 off = round(off, config.PtrSize) 300 301 // issue call 302 mem = b.NewValue1A(pos, OpStaticCall, TypeMem, fn, mem) 303 mem.AuxInt = off - config.ctxt.FixedFrameSize() 304 305 if valIsVolatile { 306 mem = b.NewValue1A(pos, OpVarKill, TypeMem, tmp, mem) // mark temp dead 307 } 308 309 return mem 310 } 311 312 // round to a multiple of r, r is a power of 2 313 func round(o int64, r int64) int64 { 314 return (o + r - 1) &^ (r - 1) 315 } 316 317 // IsStackAddr returns whether v is known to be an address of a stack slot 318 func IsStackAddr(v *Value) bool { 319 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 320 v = v.Args[0] 321 } 322 switch v.Op { 323 case OpSP: 324 return true 325 case OpAddr: 326 return v.Args[0].Op == OpSP 327 } 328 return false 329 } 330 331 // isVolatile returns whether v is a pointer to argument region on stack which 332 // will be clobbered by a function call. 333 func isVolatile(v *Value) bool { 334 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 335 v = v.Args[0] 336 } 337 return v.Op == OpSP 338 }