"cmd/compile/internal/types"
"cmd/internal/objabi"
"cmd/internal/src"
+ "cmd/internal/sys"
"fmt"
"unsafe"
)
// If there is no unused register, a Value will be kicked out of
// a register to make room.
func (s *regAllocState) allocReg(mask regMask, v *Value) register {
+ if v.OnWasmStack {
+ return noRegister
+ }
+
mask &= s.allocatable
mask &^= s.nospill
if mask == 0 {
s.f.Fatalf("couldn't find register to spill")
}
+ if s.f.Config.ctxt.Arch.Arch == sys.ArchWasm {
+ // TODO(neelance): In theory this should never happen, because all wasm registers are equal.
+ // So if there is still a free register, the allocation should have picked that one in the first place insead of
+ // trying to kick some other value out. In practice, this case does happen and it breaks the stack optimization.
+ s.freeReg(r)
+ return r
+ }
+
// Try to move it around before kicking out, if there is a free register.
// We generate a Copy and record it. It will be deleted if never used.
v2 := s.regs[r].v
// undone until the caller allows it by clearing nospill. Returns a
// *Value which is either v or a copy of v allocated to the chosen register.
func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos src.XPos) *Value {
+ if s.f.Config.ctxt.Arch.Arch == sys.ArchWasm && v.rematerializeable() {
+ c := v.copyIntoWithXPos(s.curBlock, pos)
+ c.OnWasmStack = true
+ s.setOrig(c, v)
+ return c
+ }
+ if v.OnWasmStack {
+ return v
+ }
+
vi := &s.values[v.ID]
pos = pos.WithNotStmt()
// Check if v is already in a requested register.
return s.regs[r].c
}
- // Allocate a register.
- r := s.allocReg(mask, v)
+ var r register
+ // If nospill is set, the value is used immedately, so it can live on the WebAssembly stack.
+ onWasmStack := nospill && s.f.Config.ctxt.Arch.Arch == sys.ArchWasm
+ if !onWasmStack {
+ // Allocate a register.
+ r = s.allocReg(mask, v)
+ }
// Allocate v to the new register.
var c *Value
}
c = s.curBlock.NewValue1(pos, OpLoadReg, v.Type, spill)
}
+
s.setOrig(c, v)
+
+ if onWasmStack {
+ c.OnWasmStack = true
+ return c
+ }
+
s.assignReg(r, v, c)
if nospill {
s.nospill |= regMask(1) << r
s.startRegs = make([][]startReg, f.NumBlocks())
s.spillLive = make([][]ID, f.NumBlocks())
s.sdom = f.sdom()
+
+ // wasm: Mark instructions that can be optimized to have their values only on the WebAssembly stack.
+ if f.Config.ctxt.Arch.Arch == sys.ArchWasm {
+ canLiveOnStack := f.newSparseSet(f.NumValues())
+ defer f.retSparseSet(canLiveOnStack)
+ for _, b := range f.Blocks {
+ // New block. Clear candidate set.
+ canLiveOnStack.clear()
+ if b.Control != nil && b.Control.Uses == 1 && !opcodeTable[b.Control.Op].generic {
+ canLiveOnStack.add(b.Control.ID)
+ }
+ // Walking backwards.
+ for i := len(b.Values) - 1; i >= 0; i-- {
+ v := b.Values[i]
+ if canLiveOnStack.contains(v.ID) {
+ v.OnWasmStack = true
+ } else {
+ // Value can not live on stack. Values are not allowed to be reordered, so clear candidate set.
+ canLiveOnStack.clear()
+ }
+ for _, arg := range v.Args {
+ // Value can live on the stack if:
+ // - it is only used once
+ // - it is used in the same basic block
+ // - it is not a "mem" value
+ // - it is a WebAssembly op
+ if arg.Uses == 1 && arg.Block == v.Block && !arg.Type.IsMemory() && !opcodeTable[arg.Op].generic {
+ canLiveOnStack.add(arg.ID)
+ }
+ }
+ }
+ }
+ }
}
// Adds a use record for id at distance dist from the start of the block.
goToBlock(b.Succs[0].Block(), true)
case ssa.BlockIf:
- getReg32(s, b.Control)
+ getValue32(s, b.Control)
s.Prog(wasm.AI32Eqz)
s.Prog(wasm.AIf)
goToBlock(b.Succs[1].Block(), false)
// Entry point for the next block. Used by the JMP in goToBlock.
s.Prog(wasm.ARESUMEPOINT)
+
+ if s.OnWasmStackSkipped != 0 {
+ panic("wasm: bad stack")
+ }
}
func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
s.Prog(wasm.ARESUMEPOINT)
}
if v.Op == ssa.OpWasmLoweredClosureCall {
- getReg64(s, v.Args[1])
+ getValue64(s, v.Args[1])
setReg(s, wasm.REG_CTXT)
}
if sym, ok := v.Aux.(*obj.LSym); ok {
p := s.Prog(obj.ACALL)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
} else {
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
p := s.Prog(obj.ACALL)
p.To = obj.Addr{Type: obj.TYPE_NONE}
}
case ssa.OpWasmLoweredMove:
- getReg32(s, v.Args[0])
- getReg32(s, v.Args[1])
+ getValue32(s, v.Args[0])
+ getValue32(s, v.Args[1])
i32Const(s, int32(v.AuxInt))
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmMove}
case ssa.OpWasmLoweredZero:
- getReg32(s, v.Args[0])
+ getValue32(s, v.Args[0])
i32Const(s, int32(v.AuxInt))
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmZero}
case ssa.OpWasmLoweredNilCheck:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
s.Prog(wasm.AI64Eqz)
s.Prog(wasm.AIf)
p := s.Prog(wasm.ACALLNORESUME)
}
case ssa.OpWasmLoweredWB:
- getReg64(s, v.Args[0])
- getReg64(s, v.Args[1])
+ getValue64(s, v.Args[0])
+ getValue64(s, v.Args[1])
p := s.Prog(wasm.ACALLNORESUME) // TODO(neelance): If possible, turn this into a simple wasm.ACall).
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: v.Aux.(*obj.LSym)}
case ssa.OpWasmI64Store8, ssa.OpWasmI64Store16, ssa.OpWasmI64Store32, ssa.OpWasmI64Store, ssa.OpWasmF32Store, ssa.OpWasmF64Store:
- getReg32(s, v.Args[0])
- getReg64(s, v.Args[1])
+ getValue32(s, v.Args[0])
+ getValue64(s, v.Args[1])
if v.Op == ssa.OpWasmF32Store {
s.Prog(wasm.AF32DemoteF64)
}
case ssa.OpStoreReg:
getReg(s, wasm.REG_SP)
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
if v.Type.Etype == types.TFLOAT32 {
s.Prog(wasm.AF32DemoteF64)
}
if v.Type.IsMemory() {
return
}
+ if v.OnWasmStack {
+ s.OnWasmStackSkipped++
+ // If a Value is marked OnWasmStack, we don't generate the value and store it to a register now.
+ // Instead, we delay the generation to when the value is used and then directly generate it on the WebAssembly stack.
+ return
+ }
ssaGenValueOnStack(s, v)
+ if s.OnWasmStackSkipped != 0 {
+ panic("wasm: bad stack")
+ }
setReg(s, v.Reg())
}
}
}
case ssa.OpWasmLoweredRound32F:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
s.Prog(wasm.AF32DemoteF64)
s.Prog(wasm.AF64PromoteF32)
case ssa.OpWasmLoweredConvert:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
case ssa.OpWasmSelect:
- getReg64(s, v.Args[0])
- getReg64(s, v.Args[1])
- getReg64(s, v.Args[2])
+ getValue64(s, v.Args[0])
+ getValue64(s, v.Args[1])
+ getValue64(s, v.Args[2])
s.Prog(wasm.AI32WrapI64)
s.Prog(v.Op.Asm())
case ssa.OpWasmI64AddConst:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
i64Const(s, v.AuxInt)
s.Prog(v.Op.Asm())
f64Const(s, v.AuxFloat())
case ssa.OpWasmI64Load8U, ssa.OpWasmI64Load8S, ssa.OpWasmI64Load16U, ssa.OpWasmI64Load16S, ssa.OpWasmI64Load32U, ssa.OpWasmI64Load32S, ssa.OpWasmI64Load, ssa.OpWasmF32Load, ssa.OpWasmF64Load:
- getReg32(s, v.Args[0])
+ getValue32(s, v.Args[0])
p := s.Prog(v.Op.Asm())
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
if v.Op == ssa.OpWasmF32Load {
}
case ssa.OpWasmI64Eqz:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
s.Prog(v.Op.Asm())
s.Prog(wasm.AI64ExtendUI32)
case ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU, ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge:
- getReg64(s, v.Args[0])
- getReg64(s, v.Args[1])
+ getValue64(s, v.Args[0])
+ getValue64(s, v.Args[1])
s.Prog(v.Op.Asm())
s.Prog(wasm.AI64ExtendUI32)
case ssa.OpWasmI64Add, ssa.OpWasmI64Sub, ssa.OpWasmI64Mul, ssa.OpWasmI64DivU, ssa.OpWasmI64RemS, ssa.OpWasmI64RemU, ssa.OpWasmI64And, ssa.OpWasmI64Or, ssa.OpWasmI64Xor, ssa.OpWasmI64Shl, ssa.OpWasmI64ShrS, ssa.OpWasmI64ShrU, ssa.OpWasmF64Add, ssa.OpWasmF64Sub, ssa.OpWasmF64Mul, ssa.OpWasmF64Div:
- getReg64(s, v.Args[0])
- getReg64(s, v.Args[1])
+ getValue64(s, v.Args[0])
+ getValue64(s, v.Args[1])
s.Prog(v.Op.Asm())
case ssa.OpWasmI64DivS:
- getReg64(s, v.Args[0])
- getReg64(s, v.Args[1])
+ getValue64(s, v.Args[0])
+ getValue64(s, v.Args[1])
if v.Type.Size() == 8 {
// Division of int64 needs helper function wasmDiv to handle the MinInt64 / -1 case.
p := s.Prog(wasm.ACall)
s.Prog(wasm.AI64DivS)
case ssa.OpWasmI64TruncSF64:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncS}
case ssa.OpWasmI64TruncUF64:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncU}
case ssa.OpWasmF64Neg, ssa.OpWasmF64ConvertSI64, ssa.OpWasmF64ConvertUI64:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
s.Prog(v.Op.Asm())
case ssa.OpLoadReg:
}
case ssa.OpCopy:
- getReg64(s, v.Args[0])
+ getValue64(s, v.Args[0])
default:
v.Fatalf("unexpected op: %s", v.Op)
}
}
-func getReg32(s *gc.SSAGenState, v *ssa.Value) {
+func getValue32(s *gc.SSAGenState, v *ssa.Value) {
+ if v.OnWasmStack {
+ s.OnWasmStackSkipped--
+ ssaGenValueOnStack(s, v)
+ s.Prog(wasm.AI32WrapI64)
+ return
+ }
+
reg := v.Reg()
getReg(s, reg)
if reg != wasm.REG_SP {
}
}
-func getReg64(s *gc.SSAGenState, v *ssa.Value) {
+func getValue64(s *gc.SSAGenState, v *ssa.Value) {
+ if v.OnWasmStack {
+ s.OnWasmStackSkipped--
+ ssaGenValueOnStack(s, v)
+ return
+ }
+
reg := v.Reg()
getReg(s, reg)
if reg == wasm.REG_SP {