github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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 "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: 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 mem := v.Args[2] 82 line := v.Line 83 84 // there may be a sequence of WB stores in the current block. find them. 85 storeWBs = storeWBs[:0] 86 others = others[:0] 87 wbs.clear() 88 for _, w := range b.Values[i:] { 89 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB { 90 storeWBs = append(storeWBs, w) 91 wbs.add(w.ID) 92 } else { 93 others = append(others, w) 94 } 95 } 96 97 // make sure that no value in this block depends on WB stores 98 for _, w := range b.Values { 99 if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB { 100 continue 101 } 102 for _, a := range w.Args { 103 if wbs.contains(a.ID) { 104 f.Fatalf("value %v depends on WB store %v in the same block %v", w, a, b) 105 } 106 } 107 } 108 109 b.Values = append(b.Values[:i], others...) // move WB ops out of this block 110 111 bThen := f.NewBlock(BlockPlain) 112 bElse := f.NewBlock(BlockPlain) 113 bEnd := f.NewBlock(b.Kind) 114 bThen.Line = line 115 bElse.Line = line 116 bEnd.Line = line 117 118 // set up control flow for end block 119 bEnd.SetControl(b.Control) 120 bEnd.Likely = b.Likely 121 for _, e := range b.Succs { 122 bEnd.Succs = append(bEnd.Succs, e) 123 e.b.Preds[e.i].b = bEnd 124 } 125 126 // set up control flow for write barrier test 127 // load word, test word, avoiding partial register write from load byte. 128 flag := b.NewValue2(line, OpLoad, f.Config.fe.TypeUInt32(), wbaddr, mem) 129 const0 := f.ConstInt32(line, f.Config.fe.TypeUInt32(), 0) 130 flag = b.NewValue2(line, OpNeq32, f.Config.fe.TypeBool(), flag, const0) 131 b.Kind = BlockIf 132 b.SetControl(flag) 133 b.Likely = BranchUnlikely 134 b.Succs = b.Succs[:0] 135 b.AddEdgeTo(bThen) 136 b.AddEdgeTo(bElse) 137 bThen.AddEdgeTo(bEnd) 138 bElse.AddEdgeTo(bEnd) 139 140 memThen := mem 141 memElse := mem 142 for _, w := range storeWBs { 143 var val *Value 144 ptr := w.Args[0] 145 siz := w.AuxInt 146 typ := w.Aux // only non-nil for MoveWB, MoveWBVolatile, ZeroWB 147 148 var op Op 149 var fn interface{} // *gc.Sym 150 switch w.Op { 151 case OpStoreWB: 152 op = OpStore 153 fn = writebarrierptr 154 val = w.Args[1] 155 case OpMoveWB, OpMoveWBVolatile: 156 op = OpMove 157 fn = typedmemmove 158 val = w.Args[1] 159 case OpZeroWB: 160 op = OpZero 161 fn = typedmemclr 162 } 163 164 // then block: emit write barrier call 165 memThen = wbcall(line, bThen, fn, typ, ptr, val, memThen, sp, sb, w.Op == OpMoveWBVolatile) 166 167 // else block: normal store 168 if op == OpZero { 169 memElse = bElse.NewValue2I(line, op, TypeMem, siz, ptr, memElse) 170 } else { 171 memElse = bElse.NewValue3I(line, op, TypeMem, siz, ptr, val, memElse) 172 } 173 } 174 175 // merge memory 176 // Splice memory Phi into the last memory of the original sequence, 177 // which may be used in subsequent blocks. Other memories in the 178 // sequence must be dead after this block since there can be only 179 // one memory live. 180 v = storeWBs[len(storeWBs)-1] 181 bEnd.Values = append(bEnd.Values, v) 182 v.Block = bEnd 183 v.reset(OpPhi) 184 v.Type = TypeMem 185 v.AddArg(memThen) 186 v.AddArg(memElse) 187 for _, w := range storeWBs[:len(storeWBs)-1] { 188 for _, a := range w.Args { 189 a.Uses-- 190 } 191 } 192 for _, w := range storeWBs[:len(storeWBs)-1] { 193 f.freeValue(w) 194 } 195 196 if f.Config.fe.Debug_wb() { 197 f.Config.Warnl(line, "write barrier") 198 } 199 200 break valueLoop 201 } 202 } 203 } 204 } 205 206 // wbcall emits write barrier runtime call in b, returns memory. 207 // if valIsVolatile, it moves val into temp space before making the call. 208 func wbcall(line int32, b *Block, fn interface{}, typ interface{}, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { 209 config := b.Func.Config 210 211 var tmp GCNode 212 if valIsVolatile { 213 // Copy to temp location if the source is volatile (will be clobbered by 214 // a function call). Marshaling the args to typedmemmove might clobber the 215 // value we're trying to move. 216 t := val.Type.ElemType() 217 tmp = config.fe.Auto(t) 218 aux := &AutoSymbol{Typ: t, Node: tmp} 219 mem = b.NewValue1A(line, OpVarDef, TypeMem, tmp, mem) 220 tmpaddr := b.NewValue1A(line, OpAddr, t.PtrTo(), aux, sp) 221 siz := MakeSizeAndAlign(t.Size(), t.Alignment()).Int64() 222 mem = b.NewValue3I(line, OpMove, TypeMem, siz, tmpaddr, val, mem) 223 val = tmpaddr 224 } 225 226 // put arguments on stack 227 off := config.ctxt.FixedFrameSize() 228 229 if typ != nil { // for typedmemmove 230 taddr := b.NewValue1A(line, OpAddr, config.fe.TypeUintptr(), typ, sb) 231 off = round(off, taddr.Type.Alignment()) 232 arg := b.NewValue1I(line, OpOffPtr, taddr.Type.PtrTo(), off, sp) 233 mem = b.NewValue3I(line, OpStore, TypeMem, ptr.Type.Size(), arg, taddr, mem) 234 off += taddr.Type.Size() 235 } 236 237 off = round(off, ptr.Type.Alignment()) 238 arg := b.NewValue1I(line, OpOffPtr, ptr.Type.PtrTo(), off, sp) 239 mem = b.NewValue3I(line, OpStore, TypeMem, ptr.Type.Size(), arg, ptr, mem) 240 off += ptr.Type.Size() 241 242 if val != nil { 243 off = round(off, val.Type.Alignment()) 244 arg = b.NewValue1I(line, OpOffPtr, val.Type.PtrTo(), off, sp) 245 mem = b.NewValue3I(line, OpStore, TypeMem, val.Type.Size(), arg, val, mem) 246 off += val.Type.Size() 247 } 248 off = round(off, config.PtrSize) 249 250 // issue call 251 mem = b.NewValue1A(line, OpStaticCall, TypeMem, fn, mem) 252 mem.AuxInt = off - config.ctxt.FixedFrameSize() 253 254 if valIsVolatile { 255 mem = b.NewValue1A(line, OpVarKill, TypeMem, tmp, mem) // mark temp dead 256 } 257 258 return mem 259 } 260 261 // round to a multiple of r, r is a power of 2 262 func round(o int64, r int64) int64 { 263 return (o + r - 1) &^ (r - 1) 264 } 265 266 // IsStackAddr returns whether v is known to be an address of a stack slot 267 func IsStackAddr(v *Value) bool { 268 for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { 269 v = v.Args[0] 270 } 271 switch v.Op { 272 case OpSP: 273 return true 274 case OpAddr: 275 return v.Args[0].Op == OpSP 276 } 277 return false 278 }