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