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.
12 // writebarrier expands write barrier ops (StoreWB, MoveWB, etc.) into
13 // branches and runtime calls, like
15 // if writeBarrier.enabled {
16 // writebarrierptr(ptr, val)
21 // If ptr is an address of a stack slot, write barrier will be removed
22 // and a normal store will be used.
23 // A sequence of WB stores for many pointer fields of a single type will
24 // be emitted together, with a single branch.
26 // Expanding WB ops introduces new control flows, and we would need to
27 // split a block into two if there were values after WB ops, which would
28 // require scheduling the values. To avoid this complexity, when building
29 // SSA, we make sure that WB ops are always at the end of a block. We do
30 // this before fuse as it may merge blocks. It also helps to reduce
31 // number of blocks as fuse merges blocks introduced in this phase.
32 func writebarrier(f *Func) {
33 var sb, sp, wbaddr *Value
34 var writebarrierptr, typedmemmove, typedmemclr interface{} // *gc.Sym
35 var storeWBs, others []*Value
37 for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no WB stores
39 for i, v := range b.Values {
41 case OpStoreWB, OpMoveWB, OpMoveWBVolatile, OpZeroWB:
42 if IsStackAddr(v.Args[0]) {
46 case OpMoveWB, OpMoveWBVolatile:
57 // initalize global values for write barrier test and calls
58 // find SB and SP values in entry block
60 for _, v := range f.Entry.Values {
69 sb = f.Entry.NewValue0(initln, OpSB, f.Config.fe.TypeUintptr())
72 sp = f.Entry.NewValue0(initln, OpSP, f.Config.fe.TypeUintptr())
74 wbsym := &ExternSymbol{Typ: f.Config.fe.TypeBool(), Sym: f.Config.fe.Syslook("writeBarrier").(fmt.Stringer)}
75 wbaddr = f.Entry.NewValue1A(initln, OpAddr, f.Config.fe.TypeUInt32().PtrTo(), wbsym, sb)
76 writebarrierptr = f.Config.fe.Syslook("writebarrierptr")
77 typedmemmove = f.Config.fe.Syslook("typedmemmove")
78 typedmemclr = f.Config.fe.Syslook("typedmemclr")
80 wbs = f.newSparseSet(f.NumValues())
81 defer f.retSparseSet(wbs)
86 // there may be a sequence of WB stores in the current block. find them.
87 storeWBs = storeWBs[:0]
90 for _, w := range b.Values[i:] {
91 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB {
92 storeWBs = append(storeWBs, w)
95 others = append(others, w)
99 // make sure that no value in this block depends on WB stores
100 for _, w := range b.Values {
101 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB {
104 for _, a := range w.Args {
105 if wbs.contains(a.ID) {
106 f.Fatalf("value %v depends on WB store %v in the same block %v", w, a, b)
111 // find the memory before the WB stores
112 // this memory is not a WB store but it is used in a WB store.
114 for _, w := range storeWBs {
115 a := w.Args[len(w.Args)-1]
116 if wbs.contains(a.ID) {
120 b.Fatalf("two stores live simultaneously: %s, %s", mem, a)
125 b.Values = append(b.Values[:i], others...) // move WB ops out of this block
127 bThen := f.NewBlock(BlockPlain)
128 bElse := f.NewBlock(BlockPlain)
129 bEnd := f.NewBlock(b.Kind)
134 // set up control flow for end block
135 bEnd.SetControl(b.Control)
136 bEnd.Likely = b.Likely
137 for _, e := range b.Succs {
138 bEnd.Succs = append(bEnd.Succs, e)
139 e.b.Preds[e.i].b = bEnd
142 // set up control flow for write barrier test
143 // load word, test word, avoiding partial register write from load byte.
144 flag := b.NewValue2(pos, OpLoad, f.Config.fe.TypeUInt32(), wbaddr, mem)
145 const0 := f.ConstInt32(pos, f.Config.fe.TypeUInt32(), 0)
146 flag = b.NewValue2(pos, OpNeq32, f.Config.fe.TypeBool(), flag, const0)
149 b.Likely = BranchUnlikely
150 b.Succs = b.Succs[:0]
153 bThen.AddEdgeTo(bEnd)
154 bElse.AddEdgeTo(bEnd)
158 for _, w := range storeWBs {
162 typ := w.Aux // only non-nil for MoveWB, MoveWBVolatile, ZeroWB
165 var fn interface{} // *gc.Sym
171 case OpMoveWB, OpMoveWBVolatile:
180 // then block: emit write barrier call
181 memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, w.Op == OpMoveWBVolatile)
183 // else block: normal store
185 memElse = bElse.NewValue2I(pos, op, TypeMem, siz, ptr, memElse)
187 memElse = bElse.NewValue3I(pos, op, TypeMem, siz, ptr, val, memElse)
192 // Splice memory Phi into the last memory of the original sequence,
193 // which may be used in subsequent blocks. Other memories in the
194 // sequence must be dead after this block since there can be only
197 if len(storeWBs) > 1 {
198 // find the last store
200 wbs.clear() // we reuse wbs to record WB stores that is used in another WB store
201 for _, w := range storeWBs {
202 wbs.add(w.Args[len(w.Args)-1].ID)
204 for _, w := range storeWBs {
205 if wbs.contains(w.ID) {
209 b.Fatalf("two stores live simultaneously: %s, %s", last, w)
214 bEnd.Values = append(bEnd.Values, last)
220 for _, w := range storeWBs {
225 for _, w := range storeWBs {
231 if f.Config.fe.Debug_wb() {
232 f.Config.Warnl(pos, "write barrier")
241 // wbcall emits write barrier runtime call in b, returns memory.
242 // if valIsVolatile, it moves val into temp space before making the call.
243 func wbcall(pos src.XPos, b *Block, fn interface{}, typ interface{}, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value {
244 config := b.Func.Config
248 // Copy to temp location if the source is volatile (will be clobbered by
249 // a function call). Marshaling the args to typedmemmove might clobber the
250 // value we're trying to move.
251 t := val.Type.ElemType()
252 tmp = config.fe.Auto(t)
253 aux := &AutoSymbol{Typ: t, Node: tmp}
254 mem = b.NewValue1A(pos, OpVarDef, TypeMem, tmp, mem)
255 tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), aux, sp)
256 siz := MakeSizeAndAlign(t.Size(), t.Alignment()).Int64()
257 mem = b.NewValue3I(pos, OpMove, TypeMem, siz, tmpaddr, val, mem)
261 // put arguments on stack
262 off := config.ctxt.FixedFrameSize()
264 if typ != nil { // for typedmemmove
265 taddr := b.NewValue1A(pos, OpAddr, config.fe.TypeUintptr(), typ, sb)
266 off = round(off, taddr.Type.Alignment())
267 arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp)
268 mem = b.NewValue3I(pos, OpStore, TypeMem, ptr.Type.Size(), arg, taddr, mem)
269 off += taddr.Type.Size()
272 off = round(off, ptr.Type.Alignment())
273 arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp)
274 mem = b.NewValue3I(pos, OpStore, TypeMem, ptr.Type.Size(), arg, ptr, mem)
275 off += ptr.Type.Size()
278 off = round(off, val.Type.Alignment())
279 arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp)
280 mem = b.NewValue3I(pos, OpStore, TypeMem, val.Type.Size(), arg, val, mem)
281 off += val.Type.Size()
283 off = round(off, config.PtrSize)
286 mem = b.NewValue1A(pos, OpStaticCall, TypeMem, fn, mem)
287 mem.AuxInt = off - config.ctxt.FixedFrameSize()
290 mem = b.NewValue1A(pos, OpVarKill, TypeMem, tmp, mem) // mark temp dead
296 // round to a multiple of r, r is a power of 2
297 func round(o int64, r int64) int64 {
298 return (o + r - 1) &^ (r - 1)
301 // IsStackAddr returns whether v is known to be an address of a stack slot
302 func IsStackAddr(v *Value) bool {
303 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
310 return v.Args[0].Op == OpSP