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  }