github.com/epfl-dcsl/gotee@v0.0.0-20200909122901-014b35f5e5e9/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 // writebarrierptr(ptr, val) 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 writebarrierptr, 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 writebarrierptr = f.fe.Syslook("writebarrierptr") 100 if !f.fe.Debug_eagerwb() { 101 gcWriteBarrier = f.fe.Syslook("gcWriteBarrier") 102 } 103 typedmemmove = f.fe.Syslook("typedmemmove") 104 typedmemclr = f.fe.Syslook("typedmemclr") 105 const0 = f.ConstInt32(initpos, f.Config.Types.UInt32, 0) 106 107 // allocate auxiliary data structures for computing store order 108 sset = f.newSparseSet(f.NumValues()) 109 defer f.retSparseSet(sset) 110 storeNumber = make([]int32, f.NumValues()) 111 } 112 113 // order values in store order 114 b.Values = storeOrder(b.Values, sset, storeNumber) 115 116 again: 117 // find the start and end of the last contiguous WB store sequence. 118 // a branch will be inserted there. values after it will be moved 119 // to a new block. 120 var last *Value 121 var start, end int 122 values := b.Values 123 FindSeq: 124 for i := len(values) - 1; i >= 0; i-- { 125 w := values[i] 126 switch w.Op { 127 case OpStoreWB, OpMoveWB, OpZeroWB: 128 start = i 129 if last == nil { 130 last = w 131 end = i + 1 132 } 133 case OpVarDef, OpVarLive, OpVarKill: 134 continue 135 default: 136 if last == nil { 137 continue 138 } 139 break FindSeq 140 } 141 } 142 stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing 143 after = append(after[:0], b.Values[end:]...) 144 b.Values = b.Values[:start] 145 146 // find the memory before the WB stores 147 mem := stores[0].MemoryArg() 148 pos := stores[0].Pos 149 bThen := f.NewBlock(BlockPlain) 150 bElse := f.NewBlock(BlockPlain) 151 bEnd := f.NewBlock(b.Kind) 152 bThen.Pos = pos 153 bElse.Pos = pos 154 bEnd.Pos = b.Pos 155 b.Pos = pos 156 157 // set up control flow for end block 158 bEnd.SetControl(b.Control) 159 bEnd.Likely = b.Likely 160 for _, e := range b.Succs { 161 bEnd.Succs = append(bEnd.Succs, e) 162 e.b.Preds[e.i].b = bEnd 163 } 164 165 // set up control flow for write barrier test 166 // load word, test word, avoiding partial register write from load byte. 167 cfgtypes := &f.Config.Types 168 flag := b.NewValue2(pos, OpLoad, cfgtypes.UInt32, wbaddr, mem) 169 flag = b.NewValue2(pos, OpNeq32, cfgtypes.Bool, flag, const0) 170 b.Kind = BlockIf 171 b.SetControl(flag) 172 b.Likely = BranchUnlikely 173 b.Succs = b.Succs[:0] 174 b.AddEdgeTo(bThen) 175 b.AddEdgeTo(bElse) 176 // TODO: For OpStoreWB and the buffered write barrier, 177 // we could move the write out of the write barrier, 178 // which would lead to fewer branches. We could do 179 // something similar to OpZeroWB, since the runtime 180 // could provide just the barrier half and then we 181 // could unconditionally do an OpZero (which could 182 // also generate better zeroing code). OpMoveWB is 183 // trickier and would require changing how 184 // cgoCheckMemmove works. 185 bThen.AddEdgeTo(bEnd) 186 bElse.AddEdgeTo(bEnd) 187 188 // for each write barrier store, append write barrier version to bThen 189 // and simple store version to bElse 190 memThen := mem 191 memElse := mem 192 for _, w := range stores { 193 ptr := w.Args[0] 194 pos := w.Pos 195 196 var fn *obj.LSym 197 var typ *obj.LSym 198 var val *Value 199 switch w.Op { 200 case OpStoreWB: 201 fn = writebarrierptr 202 val = w.Args[1] 203 nWBops-- 204 case OpMoveWB: 205 fn = typedmemmove 206 val = w.Args[1] 207 typ = w.Aux.(*types.Type).Symbol() 208 nWBops-- 209 case OpZeroWB: 210 fn = typedmemclr 211 typ = w.Aux.(*types.Type).Symbol() 212 nWBops-- 213 case OpVarDef, OpVarLive, OpVarKill: 214 } 215 216 // then block: emit write barrier call 217 switch w.Op { 218 case OpStoreWB, OpMoveWB, OpZeroWB: 219 volatile := w.Op == OpMoveWB && isVolatile(val) 220 if w.Op == OpStoreWB && !f.fe.Debug_eagerwb() { 221 memThen = bThen.NewValue3A(pos, OpWB, types.TypeMem, gcWriteBarrier, ptr, val, memThen) 222 } else { 223 memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile) 224 } 225 case OpVarDef, OpVarLive, OpVarKill: 226 memThen = bThen.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memThen) 227 } 228 229 // else block: normal store 230 switch w.Op { 231 case OpStoreWB: 232 memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse) 233 case OpMoveWB: 234 memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse) 235 memElse.Aux = w.Aux 236 case OpZeroWB: 237 memElse = bElse.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, ptr, memElse) 238 memElse.Aux = w.Aux 239 case OpVarDef, OpVarLive, OpVarKill: 240 memElse = bElse.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memElse) 241 } 242 243 if fn != nil { 244 // Note that we set up a writebarrier function call. 245 f.fe.SetWBPos(pos) 246 } 247 } 248 249 // merge memory 250 // Splice memory Phi into the last memory of the original sequence, 251 // which may be used in subsequent blocks. Other memories in the 252 // sequence must be dead after this block since there can be only 253 // one memory live. 254 bEnd.Values = append(bEnd.Values, last) 255 last.Block = bEnd 256 last.reset(OpPhi) 257 last.Type = types.TypeMem 258 last.AddArg(memThen) 259 last.AddArg(memElse) 260 for _, w := range stores { 261 if w != last { 262 w.resetArgs() 263 } 264 } 265 for _, w := range stores { 266 if w != last { 267 f.freeValue(w) 268 } 269 } 270 271 // put values after the store sequence into the end block 272 bEnd.Values = append(bEnd.Values, after...) 273 for _, w := range after { 274 w.Block = bEnd 275 } 276 277 // if we have more stores in this block, do this block again 278 if nWBops > 0 { 279 goto again 280 } 281 } 282 } 283 284 // wbcall emits write barrier runtime call in b, returns memory. 285 // if valIsVolatile, it moves val into temp space before making the call. 286 func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { 287 config := b.Func.Config 288 289 var tmp GCNode 290 if valIsVolatile { 291 // Copy to temp location if the source is volatile (will be clobbered by 292 // a function call). Marshaling the args to typedmemmove might clobber the 293 // value we're trying to move. 294 t := val.Type.ElemType() 295 tmp = b.Func.fe.Auto(val.Pos, t) 296 mem = b.NewValue1A(pos, OpVarDef, types.TypeMem, tmp, mem) 297 tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), tmp, sp) 298 siz := t.Size() 299 mem = b.NewValue3I(pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) 300 mem.Aux = t 301 val = tmpaddr 302 } 303 304 // put arguments on stack 305 off := config.ctxt.FixedFrameSize() 306 307 if typ != nil { // for typedmemmove 308 taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb) 309 off = round(off, taddr.Type.Alignment()) 310 arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) 311 mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem) 312 off += taddr.Type.Size() 313 } 314 315 off = round(off, ptr.Type.Alignment()) 316 arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) 317 mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem) 318 off += ptr.Type.Size() 319 320 if val != nil { 321 off = round(off, val.Type.Alignment()) 322 arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) 323 mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem) 324 off += val.Type.Size() 325 } 326 off = round(off, config.PtrSize) 327 328 // issue call 329 mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, fn, mem) 330 mem.AuxInt = off - config.ctxt.FixedFrameSize() 331 332 if valIsVolatile { 333 mem = b.NewValue1A(pos, OpVarKill, types.TypeMem, tmp, mem) // mark temp dead 334 } 335 336 return mem 337 } 338 339 // round to a multiple of r, r is a power of 2 340 func round(o int64, r int64) int64 { 341 return (o + r - 1) &^ (r - 1) 342 } 343 344 // IsStackAddr returns whether v is known to be an address of a stack slot 345 func IsStackAddr(v *Value) bool { 346 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 347 v = v.Args[0] 348 } 349 switch v.Op { 350 case OpSP: 351 return true 352 case OpAddr: 353 return v.Args[0].Op == OpSP 354 } 355 return false 356 } 357 358 // isVolatile returns whether v is a pointer to argument region on stack which 359 // will be clobbered by a function call. 360 func isVolatile(v *Value) bool { 361 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 362 v = v.Args[0] 363 } 364 return v.Op == OpSP 365 }