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 (
      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 }
    366