Home | History | Annotate | Download | only in ssa
      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