github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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 (
     8  	"cmd/internal/obj"
     9  	"cmd/internal/src"
    10  )
    11  
    12  // writebarrier expands write barrier ops (StoreWB, MoveWB, etc.) into
    13  // branches and runtime calls, like
    14  //
    15  // if writeBarrier.enabled {
    16  //   writebarrierptr(ptr, val)
    17  // } else {
    18  //   *ptr = val
    19  // }
    20  //
    21  // If ptr is an address of a stack slot, write barrier will be removed
    22  // and a normal store will be used.
    23  // A sequence of WB stores for many pointer fields of a single type will
    24  // be emitted together, with a single branch.
    25  //
    26  // Expanding WB ops introduces new control flows, and we would need to
    27  // split a block into two if there were values after WB ops, which would
    28  // require scheduling the values. To avoid this complexity, when building
    29  // SSA, we make sure that WB ops are always at the end of a block. We do
    30  // this before fuse as it may merge blocks. It also helps to reduce
    31  // number of blocks as fuse merges blocks introduced in this phase.
    32  func writebarrier(f *Func) {
    33  	var sb, sp, wbaddr *Value
    34  	var writebarrierptr, typedmemmove, typedmemclr *obj.LSym
    35  	var storeWBs, others []*Value
    36  	var wbs *sparseSet
    37  	for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no WB stores
    38  	valueLoop:
    39  		for i, v := range b.Values {
    40  			switch v.Op {
    41  			case OpStoreWB, OpMoveWB, OpMoveWBVolatile, OpZeroWB:
    42  				if IsStackAddr(v.Args[0]) {
    43  					switch v.Op {
    44  					case OpStoreWB:
    45  						v.Op = OpStore
    46  					case OpMoveWB, OpMoveWBVolatile:
    47  						v.Op = OpMove
    48  						v.Aux = nil
    49  					case OpZeroWB:
    50  						v.Op = OpZero
    51  						v.Aux = nil
    52  					}
    53  					continue
    54  				}
    55  
    56  				if wbaddr == nil {
    57  					// initalize global values for write barrier test and calls
    58  					// find SB and SP values in entry block
    59  					initln := f.Entry.Pos
    60  					for _, v := range f.Entry.Values {
    61  						if v.Op == OpSB {
    62  							sb = v
    63  						}
    64  						if v.Op == OpSP {
    65  							sp = v
    66  						}
    67  					}
    68  					if sb == nil {
    69  						sb = f.Entry.NewValue0(initln, OpSB, f.Config.fe.TypeUintptr())
    70  					}
    71  					if sp == nil {
    72  						sp = f.Entry.NewValue0(initln, OpSP, f.Config.fe.TypeUintptr())
    73  					}
    74  					wbsym := &ExternSymbol{Typ: f.Config.fe.TypeBool(), Sym: f.Config.fe.Syslook("writeBarrier")}
    75  					wbaddr = f.Entry.NewValue1A(initln, OpAddr, f.Config.fe.TypeUInt32().PtrTo(), wbsym, sb)
    76  					writebarrierptr = f.Config.fe.Syslook("writebarrierptr")
    77  					typedmemmove = f.Config.fe.Syslook("typedmemmove")
    78  					typedmemclr = f.Config.fe.Syslook("typedmemclr")
    79  
    80  					wbs = f.newSparseSet(f.NumValues())
    81  					defer f.retSparseSet(wbs)
    82  				}
    83  
    84  				pos := v.Pos
    85  
    86  				// there may be a sequence of WB stores in the current block. find them.
    87  				storeWBs = storeWBs[:0]
    88  				others = others[:0]
    89  				wbs.clear()
    90  				for _, w := range b.Values[i:] {
    91  					if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB {
    92  						storeWBs = append(storeWBs, w)
    93  						wbs.add(w.ID)
    94  					} else {
    95  						others = append(others, w)
    96  					}
    97  				}
    98  
    99  				// make sure that no value in this block depends on WB stores
   100  				for _, w := range b.Values {
   101  					if w.Op == OpStoreWB || w.Op == OpMoveWB || w.Op == OpMoveWBVolatile || w.Op == OpZeroWB {
   102  						continue
   103  					}
   104  					for _, a := range w.Args {
   105  						if wbs.contains(a.ID) {
   106  							f.Fatalf("value %v depends on WB store %v in the same block %v", w, a, b)
   107  						}
   108  					}
   109  				}
   110  
   111  				// find the memory before the WB stores
   112  				// this memory is not a WB store but it is used in a WB store.
   113  				var mem *Value
   114  				for _, w := range storeWBs {
   115  					a := w.Args[len(w.Args)-1]
   116  					if wbs.contains(a.ID) {
   117  						continue
   118  					}
   119  					if mem != nil {
   120  						b.Fatalf("two stores live simultaneously: %s, %s", mem, a)
   121  					}
   122  					mem = a
   123  				}
   124  
   125  				b.Values = append(b.Values[:i], others...) // move WB ops out of this block
   126  
   127  				bThen := f.NewBlock(BlockPlain)
   128  				bElse := f.NewBlock(BlockPlain)
   129  				bEnd := f.NewBlock(b.Kind)
   130  				bThen.Pos = pos
   131  				bElse.Pos = pos
   132  				bEnd.Pos = pos
   133  
   134  				// set up control flow for end block
   135  				bEnd.SetControl(b.Control)
   136  				bEnd.Likely = b.Likely
   137  				for _, e := range b.Succs {
   138  					bEnd.Succs = append(bEnd.Succs, e)
   139  					e.b.Preds[e.i].b = bEnd
   140  				}
   141  
   142  				// set up control flow for write barrier test
   143  				// load word, test word, avoiding partial register write from load byte.
   144  				flag := b.NewValue2(pos, OpLoad, f.Config.fe.TypeUInt32(), wbaddr, mem)
   145  				const0 := f.ConstInt32(pos, f.Config.fe.TypeUInt32(), 0)
   146  				flag = b.NewValue2(pos, OpNeq32, f.Config.fe.TypeBool(), flag, const0)
   147  				b.Kind = BlockIf
   148  				b.SetControl(flag)
   149  				b.Likely = BranchUnlikely
   150  				b.Succs = b.Succs[:0]
   151  				b.AddEdgeTo(bThen)
   152  				b.AddEdgeTo(bElse)
   153  				bThen.AddEdgeTo(bEnd)
   154  				bElse.AddEdgeTo(bEnd)
   155  
   156  				memThen := mem
   157  				memElse := mem
   158  				for _, w := range storeWBs {
   159  					var val *Value
   160  					ptr := w.Args[0]
   161  					siz := w.AuxInt
   162  					typ := w.Aux // only non-nil for MoveWB, MoveWBVolatile, ZeroWB
   163  
   164  					var op Op
   165  					var fn *obj.LSym
   166  					switch w.Op {
   167  					case OpStoreWB:
   168  						op = OpStore
   169  						fn = writebarrierptr
   170  						val = w.Args[1]
   171  					case OpMoveWB, OpMoveWBVolatile:
   172  						op = OpMove
   173  						fn = typedmemmove
   174  						val = w.Args[1]
   175  					case OpZeroWB:
   176  						op = OpZero
   177  						fn = typedmemclr
   178  					}
   179  
   180  					// then block: emit write barrier call
   181  					memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, w.Op == OpMoveWBVolatile)
   182  
   183  					// else block: normal store
   184  					if op == OpZero {
   185  						memElse = bElse.NewValue2I(pos, op, TypeMem, siz, ptr, memElse)
   186  					} else {
   187  						memElse = bElse.NewValue3I(pos, op, TypeMem, siz, ptr, val, memElse)
   188  					}
   189  				}
   190  
   191  				// merge memory
   192  				// Splice memory Phi into the last memory of the original sequence,
   193  				// which may be used in subsequent blocks. Other memories in the
   194  				// sequence must be dead after this block since there can be only
   195  				// one memory live.
   196  				last := storeWBs[0]
   197  				if len(storeWBs) > 1 {
   198  					// find the last store
   199  					last = nil
   200  					wbs.clear() // we reuse wbs to record WB stores that is used in another WB store
   201  					for _, w := range storeWBs {
   202  						wbs.add(w.Args[len(w.Args)-1].ID)
   203  					}
   204  					for _, w := range storeWBs {
   205  						if wbs.contains(w.ID) {
   206  							continue
   207  						}
   208  						if last != nil {
   209  							b.Fatalf("two stores live simultaneously: %s, %s", last, w)
   210  						}
   211  						last = w
   212  					}
   213  				}
   214  				bEnd.Values = append(bEnd.Values, last)
   215  				last.Block = bEnd
   216  				last.reset(OpPhi)
   217  				last.Type = TypeMem
   218  				last.AddArg(memThen)
   219  				last.AddArg(memElse)
   220  				for _, w := range storeWBs {
   221  					if w != last {
   222  						w.resetArgs()
   223  					}
   224  				}
   225  				for _, w := range storeWBs {
   226  					if w != last {
   227  						f.freeValue(w)
   228  					}
   229  				}
   230  
   231  				if f.Config.fe.Debug_wb() {
   232  					f.Config.Warnl(pos, "write barrier")
   233  				}
   234  
   235  				break valueLoop
   236  			}
   237  		}
   238  	}
   239  }
   240  
   241  // wbcall emits write barrier runtime call in b, returns memory.
   242  // if valIsVolatile, it moves val into temp space before making the call.
   243  func wbcall(pos src.XPos, b *Block, fn *obj.LSym, typ interface{}, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value {
   244  	config := b.Func.Config
   245  
   246  	var tmp GCNode
   247  	if valIsVolatile {
   248  		// Copy to temp location if the source is volatile (will be clobbered by
   249  		// a function call). Marshaling the args to typedmemmove might clobber the
   250  		// value we're trying to move.
   251  		t := val.Type.ElemType()
   252  		tmp = config.fe.Auto(t)
   253  		aux := &AutoSymbol{Typ: t, Node: tmp}
   254  		mem = b.NewValue1A(pos, OpVarDef, TypeMem, tmp, mem)
   255  		tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), aux, sp)
   256  		siz := MakeSizeAndAlign(t.Size(), t.Alignment()).Int64()
   257  		mem = b.NewValue3I(pos, OpMove, TypeMem, siz, tmpaddr, val, mem)
   258  		val = tmpaddr
   259  	}
   260  
   261  	// put arguments on stack
   262  	off := config.ctxt.FixedFrameSize()
   263  
   264  	if typ != nil { // for typedmemmove
   265  		taddr := b.NewValue1A(pos, OpAddr, config.fe.TypeUintptr(), typ, sb)
   266  		off = round(off, taddr.Type.Alignment())
   267  		arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp)
   268  		mem = b.NewValue3I(pos, OpStore, TypeMem, ptr.Type.Size(), arg, taddr, mem)
   269  		off += taddr.Type.Size()
   270  	}
   271  
   272  	off = round(off, ptr.Type.Alignment())
   273  	arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp)
   274  	mem = b.NewValue3I(pos, OpStore, TypeMem, ptr.Type.Size(), arg, ptr, mem)
   275  	off += ptr.Type.Size()
   276  
   277  	if val != nil {
   278  		off = round(off, val.Type.Alignment())
   279  		arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp)
   280  		mem = b.NewValue3I(pos, OpStore, TypeMem, val.Type.Size(), arg, val, mem)
   281  		off += val.Type.Size()
   282  	}
   283  	off = round(off, config.PtrSize)
   284  
   285  	// issue call
   286  	mem = b.NewValue1A(pos, OpStaticCall, TypeMem, fn, mem)
   287  	mem.AuxInt = off - config.ctxt.FixedFrameSize()
   288  
   289  	if valIsVolatile {
   290  		mem = b.NewValue1A(pos, OpVarKill, TypeMem, tmp, mem) // mark temp dead
   291  	}
   292  
   293  	return mem
   294  }
   295  
   296  // round to a multiple of r, r is a power of 2
   297  func round(o int64, r int64) int64 {
   298  	return (o + r - 1) &^ (r - 1)
   299  }
   300  
   301  // IsStackAddr returns whether v is known to be an address of a stack slot
   302  func IsStackAddr(v *Value) bool {
   303  	for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
   304  		v = v.Args[0]
   305  	}
   306  	switch v.Op {
   307  	case OpSP:
   308  		return true
   309  	case OpAddr:
   310  		return v.Args[0].Op == OpSP
   311  	}
   312  	return false
   313  }