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 "fmt" 8 9 // writebarrier expands write barrier ops (StoreWB, MoveWB, etc.) into 10 // branches and runtime calls, like 11 // 12 // if writeBarrier.enabled { 13 // writebarrierptr(ptr, val) 14 // } else { 15 // *ptr = val 16 // } 17 // 18 // If ptr is an address of a stack slot, write barrier will be removed 19 // and a normal store will be used. 20 // A sequence of WB stores for many pointer fields of a single type will 21 // be emitted together, with a single branch. 22 // 23 // Expanding WB ops introduces new control flows, and we would need to 24 // split a block into two if there were values after WB ops, which would 25 // require scheduling the values. To avoid this complexity, when building 26 // SSA, we make sure that WB ops are always at the end of a block. We do 27 // this before fuse as it may merge blocks. It also helps to reduce 28 // number of blocks as fuse merges blocks introduced in this phase. 29 func writebarrier(f *Func) { 30 var sb, sp, wbaddr *Value 31 var writebarrierptr, typedmemmove, typedmemclr interface{} // *gc.Sym 32 var storeWBs, others []*Value 33 var wbs *sparseSet 34 for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no WB stores 35 valueLoop: 36 for i, v := range b.Values { 37 switch v.Op { 38 case OpStoreWB, OpMoveWB, OpMoveWBVolatile, OpZeroWB: 39 if IsStackAddr(v.Args[0]) { 40 switch v.Op { 41 case OpStoreWB: 42 v.Op = OpStore 43 case OpMoveWB, OpMoveWBVolatile: 44 v.Op = OpMove 45 v.Aux = nil 46 case OpZeroWB: 47 v.Op = OpZero 48 v.Aux = nil 49 } 50 continue 51 } 52 53 if wbaddr == nil { 54 // initalize global values for write barrier test and calls 55 // find SB and SP values in entry block 56 initln := f.Entry.Line 57 for _, v := range f.Entry.Values { 58 if v.Op == OpSB { 59 sb = v 60 } 61 if v.Op == OpSP { 62 sp = v 63 } 64 } 65 if sb == nil { 66 sb = f.Entry.NewValue0(initln, OpSB, f.Config.fe.TypeUintptr()) 67 } 68 if sp == nil { 69 sp = f.Entry.NewValue0(initln, OpSP, f.Config.fe.TypeUintptr()) 70 } 71 wbsym := &ExternSymbol{Typ: f.Config.fe.TypeBool(), Sym: f.Config.fe.Syslook("writeBarrier").(fmt.Stringer)} 72 wbaddr = f.Entry.NewValue1A(initln, OpAddr, f.Config.fe.TypeUInt32().PtrTo(), wbsym, sb) 73 writebarrierptr = f.Config.fe.Syslook("writebarrierptr") 74 typedmemmove = f.Config.fe.Syslook("typedmemmove") 75 typedmemclr = f.Config.fe.Syslook("typedmemclr") 76 77 wbs = f.newSparseSet(f.NumValues()) 78 defer f.retSparseSet(wbs) 79 } 80 81 line := v.Line 82 83 // there may be a sequence of WB stores in the current block. find them. 84 storeWBs = storeWBs[:0] 85 others = others[:0] 86 wbs.clear() 87 for _, w := range b.Values[i:] { 88 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB { 89 storeWBs = append(storeWBs, w) 90 wbs.add(w.ID) 91 } else { 92 others = append(others, w) 93 } 94 } 95 96 // make sure that no value in this block depends on WB stores 97 for _, w := range b.Values { 98 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB { 99 continue 100 } 101 for _, a := range w.Args { 102 if wbs.contains(a.ID) { 103 f.Fatalf("value %v depends on WB store %v in the same block %v", w, a, b) 104 } 105 } 106 } 107 108 // find the memory before the WB stores 109 // this memory is not a WB store but it is used in a WB store. 110 var mem *Value 111 for _, w := range storeWBs { 112 a := w.Args[len(w.Args)-1] 113 if wbs.contains(a.ID) { 114 continue 115 } 116 if mem != nil { 117 b.Fatalf("two stores live simultaneously: %s, %s", mem, a) 118 } 119 mem = a 120 } 121 122 b.Values = append(b.Values[:i], others...) // move WB ops out of this block 123 124 bThen := f.NewBlock(BlockPlain) 125 bElse := f.NewBlock(BlockPlain) 126 bEnd := f.NewBlock(b.Kind) 127 bThen.Line = line 128 bElse.Line = line 129 bEnd.Line = line 130 131 // set up control flow for end block 132 bEnd.SetControl(b.Control) 133 bEnd.Likely = b.Likely 134 for _, e := range b.Succs { 135 bEnd.Succs = append(bEnd.Succs, e) 136 e.b.Preds[e.i].b = bEnd 137 } 138 139 // set up control flow for write barrier test 140 // load word, test word, avoiding partial register write from load byte. 141 flag := b.NewValue2(line, OpLoad, f.Config.fe.TypeUInt32(), wbaddr, mem) 142 const0 := f.ConstInt32(line, f.Config.fe.TypeUInt32(), 0) 143 flag = b.NewValue2(line, OpNeq32, f.Config.fe.TypeBool(), flag, const0) 144 b.Kind = BlockIf 145 b.SetControl(flag) 146 b.Likely = BranchUnlikely 147 b.Succs = b.Succs[:0] 148 b.AddEdgeTo(bThen) 149 b.AddEdgeTo(bElse) 150 bThen.AddEdgeTo(bEnd) 151 bElse.AddEdgeTo(bEnd) 152 153 memThen := mem 154 memElse := mem 155 for _, w := range storeWBs { 156 var val *Value 157 ptr := w.Args[0] 158 siz := w.AuxInt 159 typ := w.Aux // only non-nil for MoveWB, MoveWBVolatile, ZeroWB 160 161 var op Op 162 var fn interface{} // *gc.Sym 163 switch w.Op { 164 case OpStoreWB: 165 op = OpStore 166 fn = writebarrierptr 167 val = w.Args[1] 168 case OpMoveWB, OpMoveWBVolatile: 169 op = OpMove 170 fn = typedmemmove 171 val = w.Args[1] 172 case OpZeroWB: 173 op = OpZero 174 fn = typedmemclr 175 } 176 177 // then block: emit write barrier call 178 memThen = wbcall(line, bThen, fn, typ, ptr, val, memThen, sp, sb, w.Op == OpMoveWBVolatile) 179 180 // else block: normal store 181 if op == OpZero { 182 memElse = bElse.NewValue2I(line, op, TypeMem, siz, ptr, memElse) 183 } else { 184 memElse = bElse.NewValue3I(line, op, TypeMem, siz, ptr, val, memElse) 185 } 186 } 187 188 // merge memory 189 // Splice memory Phi into the last memory of the original sequence, 190 // which may be used in subsequent blocks. Other memories in the 191 // sequence must be dead after this block since there can be only 192 // one memory live. 193 last := storeWBs[0] 194 if len(storeWBs) > 1 { 195 // find the last store 196 last = nil 197 wbs.clear() // we reuse wbs to record WB stores that is used in another WB store 198 for _, w := range storeWBs { 199 wbs.add(w.Args[len(w.Args)-1].ID) 200 } 201 for _, w := range storeWBs { 202 if wbs.contains(w.ID) { 203 continue 204 } 205 if last != nil { 206 b.Fatalf("two stores live simultaneously: %s, %s", last, w) 207 } 208 last = w 209 } 210 } 211 bEnd.Values = append(bEnd.Values, last) 212 last.Block = bEnd 213 last.reset(OpPhi) 214 last.Type = TypeMem 215 last.AddArg(memThen) 216 last.AddArg(memElse) 217 for _, w := range storeWBs { 218 if w != last { 219 w.resetArgs() 220 } 221 } 222 for _, w := range storeWBs { 223 if w != last { 224 f.freeValue(w) 225 } 226 } 227 228 if f.Config.fe.Debug_wb() { 229 f.Config.Warnl(line, "write barrier") 230 } 231 232 break valueLoop 233 } 234 } 235 } 236 } 237 238 // wbcall emits write barrier runtime call in b, returns memory. 239 // if valIsVolatile, it moves val into temp space before making the call. 240 func wbcall(line int32, b *Block, fn interface{}, typ interface{}, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { 241 config := b.Func.Config 242 243 var tmp GCNode 244 if valIsVolatile { 245 // Copy to temp location if the source is volatile (will be clobbered by 246 // a function call). Marshaling the args to typedmemmove might clobber the 247 // value we're trying to move. 248 t := val.Type.ElemType() 249 tmp = config.fe.Auto(t) 250 aux := &AutoSymbol{Typ: t, Node: tmp} 251 mem = b.NewValue1A(line, OpVarDef, TypeMem, tmp, mem) 252 tmpaddr := b.NewValue1A(line, OpAddr, t.PtrTo(), aux, sp) 253 siz := MakeSizeAndAlign(t.Size(), t.Alignment()).Int64() 254 mem = b.NewValue3I(line, OpMove, TypeMem, siz, tmpaddr, val, mem) 255 val = tmpaddr 256 } 257 258 // put arguments on stack 259 off := config.ctxt.FixedFrameSize() 260 261 if typ != nil { // for typedmemmove 262 taddr := b.NewValue1A(line, OpAddr, config.fe.TypeUintptr(), typ, sb) 263 off = round(off, taddr.Type.Alignment()) 264 arg := b.NewValue1I(line, OpOffPtr, taddr.Type.PtrTo(), off, sp) 265 mem = b.NewValue3I(line, OpStore, TypeMem, ptr.Type.Size(), arg, taddr, mem) 266 off += taddr.Type.Size() 267 } 268 269 off = round(off, ptr.Type.Alignment()) 270 arg := b.NewValue1I(line, OpOffPtr, ptr.Type.PtrTo(), off, sp) 271 mem = b.NewValue3I(line, OpStore, TypeMem, ptr.Type.Size(), arg, ptr, mem) 272 off += ptr.Type.Size() 273 274 if val != nil { 275 off = round(off, val.Type.Alignment()) 276 arg = b.NewValue1I(line, OpOffPtr, val.Type.PtrTo(), off, sp) 277 mem = b.NewValue3I(line, OpStore, TypeMem, val.Type.Size(), arg, val, mem) 278 off += val.Type.Size() 279 } 280 off = round(off, config.PtrSize) 281 282 // issue call 283 mem = b.NewValue1A(line, OpStaticCall, TypeMem, fn, mem) 284 mem.AuxInt = off - config.ctxt.FixedFrameSize() 285 286 if valIsVolatile { 287 mem = b.NewValue1A(line, OpVarKill, TypeMem, tmp, mem) // mark temp dead 288 } 289 290 return mem 291 } 292 293 // round to a multiple of r, r is a power of 2 294 func round(o int64, r int64) int64 { 295 return (o + r - 1) &^ (r - 1) 296 } 297 298 // IsStackAddr returns whether v is known to be an address of a stack slot 299 func IsStackAddr(v *Value) bool { 300 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 301 v = v.Args[0] 302 } 303 switch v.Op { 304 case OpSP: 305 return true 306 case OpAddr: 307 return v.Args[0].Op == OpSP 308 } 309 return false 310 } 311