github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/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, 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  }