| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ssa |
| |
| import ( |
| "cmd/compile/internal/types" |
| "cmd/internal/obj" |
| "cmd/internal/src" |
| ) |
| |
| // needwb returns whether we need write barrier for store op v. |
| // v must be Store/Move/Zero. |
| func needwb(v *Value) bool { |
| t, ok := v.Aux.(*types.Type) |
| if !ok { |
| v.Fatalf("store aux is not a type: %s", v.LongString()) |
| } |
| if !t.HasHeapPointer() { |
| return false |
| } |
| if IsStackAddr(v.Args[0]) { |
| return false // write on stack doesn't need write barrier |
| } |
| return true |
| } |
| |
| // writebarrier pass inserts write barriers for store ops (Store, Move, Zero) |
| // when necessary (the condition above). It rewrites store ops to branches |
| // and runtime calls, like |
| // |
| // if writeBarrier.enabled { |
| // writebarrierptr(ptr, val) |
| // } else { |
| // *ptr = val |
| // } |
| // |
| // A sequence of WB stores for many pointer fields of a single type will |
| // be emitted together, with a single branch. |
| func writebarrier(f *Func) { |
| if !f.fe.UseWriteBarrier() { |
| return |
| } |
| |
| var sb, sp, wbaddr, const0 *Value |
| var writebarrierptr, typedmemmove, typedmemclr, gcWriteBarrier *obj.LSym |
| var stores, after []*Value |
| var sset *sparseSet |
| var storeNumber []int32 |
| |
| for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no stores to expand |
| // first, identify all the stores that need to insert a write barrier. |
| // mark them with WB ops temporarily. record presence of WB ops. |
| nWBops := 0 // count of temporarily created WB ops remaining to be rewritten in the current block |
| for _, v := range b.Values { |
| switch v.Op { |
| case OpStore, OpMove, OpZero: |
| if needwb(v) { |
| switch v.Op { |
| case OpStore: |
| v.Op = OpStoreWB |
| case OpMove: |
| v.Op = OpMoveWB |
| case OpZero: |
| v.Op = OpZeroWB |
| } |
| nWBops++ |
| } |
| } |
| } |
| if nWBops == 0 { |
| continue |
| } |
| |
| if wbaddr == nil { |
| // lazily initialize global values for write barrier test and calls |
| // find SB and SP values in entry block |
| initpos := f.Entry.Pos |
| for _, v := range f.Entry.Values { |
| if v.Op == OpSB { |
| sb = v |
| } |
| if v.Op == OpSP { |
| sp = v |
| } |
| if sb != nil && sp != nil { |
| break |
| } |
| } |
| if sb == nil { |
| sb = f.Entry.NewValue0(initpos, OpSB, f.Config.Types.Uintptr) |
| } |
| if sp == nil { |
| sp = f.Entry.NewValue0(initpos, OpSP, f.Config.Types.Uintptr) |
| } |
| wbsym := f.fe.Syslook("writeBarrier") |
| wbaddr = f.Entry.NewValue1A(initpos, OpAddr, f.Config.Types.UInt32Ptr, wbsym, sb) |
| writebarrierptr = f.fe.Syslook("writebarrierptr") |
| if !f.fe.Debug_eagerwb() { |
| gcWriteBarrier = f.fe.Syslook("gcWriteBarrier") |
| } |
| typedmemmove = f.fe.Syslook("typedmemmove") |
| typedmemclr = f.fe.Syslook("typedmemclr") |
| const0 = f.ConstInt32(initpos, f.Config.Types.UInt32, 0) |
| |
| // allocate auxiliary data structures for computing store order |
| sset = f.newSparseSet(f.NumValues()) |
| defer f.retSparseSet(sset) |
| storeNumber = make([]int32, f.NumValues()) |
| } |
| |
| // order values in store order |
| b.Values = storeOrder(b.Values, sset, storeNumber) |
| |
| again: |
| // find the start and end of the last contiguous WB store sequence. |
| // a branch will be inserted there. values after it will be moved |
| // to a new block. |
| var last *Value |
| var start, end int |
| values := b.Values |
| FindSeq: |
| for i := len(values) - 1; i >= 0; i-- { |
| w := values[i] |
| switch w.Op { |
| case OpStoreWB, OpMoveWB, OpZeroWB: |
| start = i |
| if last == nil { |
| last = w |
| end = i + 1 |
| } |
| case OpVarDef, OpVarLive, OpVarKill: |
| continue |
| default: |
| if last == nil { |
| continue |
| } |
| break FindSeq |
| } |
| } |
| stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing |
| after = append(after[:0], b.Values[end:]...) |
| b.Values = b.Values[:start] |
| |
| // find the memory before the WB stores |
| mem := stores[0].MemoryArg() |
| pos := stores[0].Pos |
| bThen := f.NewBlock(BlockPlain) |
| bElse := f.NewBlock(BlockPlain) |
| bEnd := f.NewBlock(b.Kind) |
| bThen.Pos = pos |
| bElse.Pos = pos |
| bEnd.Pos = b.Pos |
| b.Pos = pos |
| |
| // set up control flow for end block |
| bEnd.SetControl(b.Control) |
| bEnd.Likely = b.Likely |
| for _, e := range b.Succs { |
| bEnd.Succs = append(bEnd.Succs, e) |
| e.b.Preds[e.i].b = bEnd |
| } |
| |
| // set up control flow for write barrier test |
| // load word, test word, avoiding partial register write from load byte. |
| cfgtypes := &f.Config.Types |
| flag := b.NewValue2(pos, OpLoad, cfgtypes.UInt32, wbaddr, mem) |
| flag = b.NewValue2(pos, OpNeq32, cfgtypes.Bool, flag, const0) |
| b.Kind = BlockIf |
| b.SetControl(flag) |
| b.Likely = BranchUnlikely |
| b.Succs = b.Succs[:0] |
| b.AddEdgeTo(bThen) |
| b.AddEdgeTo(bElse) |
| // TODO: For OpStoreWB and the buffered write barrier, |
| // we could move the write out of the write barrier, |
| // which would lead to fewer branches. We could do |
| // something similar to OpZeroWB, since the runtime |
| // could provide just the barrier half and then we |
| // could unconditionally do an OpZero (which could |
| // also generate better zeroing code). OpMoveWB is |
| // trickier and would require changing how |
| // cgoCheckMemmove works. |
| bThen.AddEdgeTo(bEnd) |
| bElse.AddEdgeTo(bEnd) |
| |
| // for each write barrier store, append write barrier version to bThen |
| // and simple store version to bElse |
| memThen := mem |
| memElse := mem |
| for _, w := range stores { |
| ptr := w.Args[0] |
| pos := w.Pos |
| |
| var fn *obj.LSym |
| var typ *obj.LSym |
| var val *Value |
| switch w.Op { |
| case OpStoreWB: |
| fn = writebarrierptr |
| val = w.Args[1] |
| nWBops-- |
| case OpMoveWB: |
| fn = typedmemmove |
| val = w.Args[1] |
| typ = w.Aux.(*types.Type).Symbol() |
| nWBops-- |
| case OpZeroWB: |
| fn = typedmemclr |
| typ = w.Aux.(*types.Type).Symbol() |
| nWBops-- |
| case OpVarDef, OpVarLive, OpVarKill: |
| } |
| |
| // then block: emit write barrier call |
| switch w.Op { |
| case OpStoreWB, OpMoveWB, OpZeroWB: |
| volatile := w.Op == OpMoveWB && isVolatile(val) |
| if w.Op == OpStoreWB && !f.fe.Debug_eagerwb() { |
| memThen = bThen.NewValue3A(pos, OpWB, types.TypeMem, gcWriteBarrier, ptr, val, memThen) |
| } else { |
| memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile) |
| } |
| case OpVarDef, OpVarLive, OpVarKill: |
| memThen = bThen.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memThen) |
| } |
| |
| // else block: normal store |
| switch w.Op { |
| case OpStoreWB: |
| memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse) |
| case OpMoveWB: |
| memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse) |
| memElse.Aux = w.Aux |
| case OpZeroWB: |
| memElse = bElse.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, ptr, memElse) |
| memElse.Aux = w.Aux |
| case OpVarDef, OpVarLive, OpVarKill: |
| memElse = bElse.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memElse) |
| } |
| |
| if fn != nil { |
| // Note that we set up a writebarrier function call. |
| f.fe.SetWBPos(pos) |
| } |
| } |
| |
| // merge memory |
| // Splice memory Phi into the last memory of the original sequence, |
| // which may be used in subsequent blocks. Other memories in the |
| // sequence must be dead after this block since there can be only |
| // one memory live. |
| bEnd.Values = append(bEnd.Values, last) |
| last.Block = bEnd |
| last.reset(OpPhi) |
| last.Type = types.TypeMem |
| last.AddArg(memThen) |
| last.AddArg(memElse) |
| for _, w := range stores { |
| if w != last { |
| w.resetArgs() |
| } |
| } |
| for _, w := range stores { |
| if w != last { |
| f.freeValue(w) |
| } |
| } |
| |
| // put values after the store sequence into the end block |
| bEnd.Values = append(bEnd.Values, after...) |
| for _, w := range after { |
| w.Block = bEnd |
| } |
| |
| // if we have more stores in this block, do this block again |
| if nWBops > 0 { |
| goto again |
| } |
| } |
| } |
| |
| // wbcall emits write barrier runtime call in b, returns memory. |
| // if valIsVolatile, it moves val into temp space before making the call. |
| func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value { |
| config := b.Func.Config |
| |
| var tmp GCNode |
| if valIsVolatile { |
| // Copy to temp location if the source is volatile (will be clobbered by |
| // a function call). Marshaling the args to typedmemmove might clobber the |
| // value we're trying to move. |
| t := val.Type.ElemType() |
| tmp = b.Func.fe.Auto(val.Pos, t) |
| mem = b.NewValue1A(pos, OpVarDef, types.TypeMem, tmp, mem) |
| tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), tmp, sp) |
| siz := t.Size() |
| mem = b.NewValue3I(pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem) |
| mem.Aux = t |
| val = tmpaddr |
| } |
| |
| // put arguments on stack |
| off := config.ctxt.FixedFrameSize() |
| |
| if typ != nil { // for typedmemmove |
| taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb) |
| off = round(off, taddr.Type.Alignment()) |
| arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp) |
| mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem) |
| off += taddr.Type.Size() |
| } |
| |
| off = round(off, ptr.Type.Alignment()) |
| arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp) |
| mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem) |
| off += ptr.Type.Size() |
| |
| if val != nil { |
| off = round(off, val.Type.Alignment()) |
| arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp) |
| mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem) |
| off += val.Type.Size() |
| } |
| off = round(off, config.PtrSize) |
| |
| // issue call |
| mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, fn, mem) |
| mem.AuxInt = off - config.ctxt.FixedFrameSize() |
| |
| if valIsVolatile { |
| mem = b.NewValue1A(pos, OpVarKill, types.TypeMem, tmp, mem) // mark temp dead |
| } |
| |
| return mem |
| } |
| |
| // round to a multiple of r, r is a power of 2 |
| func round(o int64, r int64) int64 { |
| return (o + r - 1) &^ (r - 1) |
| } |
| |
| // IsStackAddr returns whether v is known to be an address of a stack slot |
| func IsStackAddr(v *Value) bool { |
| for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { |
| v = v.Args[0] |
| } |
| switch v.Op { |
| case OpSP: |
| return true |
| case OpAddr: |
| return v.Args[0].Op == OpSP |
| } |
| return false |
| } |
| |
| // isVolatile returns whether v is a pointer to argument region on stack which |
| // will be clobbered by a function call. |
| func isVolatile(v *Value) bool { |
| for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { |
| v = v.Args[0] |
| } |
| return v.Op == OpSP |
| } |