|  | // Copyright 2018 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 wasm | 
|  |  | 
|  | import ( | 
|  | "cmd/compile/internal/gc" | 
|  | "cmd/compile/internal/ssa" | 
|  | "cmd/compile/internal/types" | 
|  | "cmd/internal/obj" | 
|  | "cmd/internal/obj/wasm" | 
|  | "cmd/internal/objabi" | 
|  | ) | 
|  |  | 
|  | func Init(arch *gc.Arch) { | 
|  | arch.LinkArch = &wasm.Linkwasm | 
|  | arch.REGSP = wasm.REG_SP | 
|  | arch.MAXWIDTH = 1 << 50 | 
|  |  | 
|  | arch.ZeroRange = zeroRange | 
|  | arch.ZeroAuto = zeroAuto | 
|  | arch.Ginsnop = ginsnop | 
|  | arch.Ginsnopdefer = ginsnop | 
|  |  | 
|  | arch.SSAMarkMoves = ssaMarkMoves | 
|  | arch.SSAGenValue = ssaGenValue | 
|  | arch.SSAGenBlock = ssaGenBlock | 
|  | } | 
|  |  | 
|  | func zeroRange(pp *gc.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { | 
|  | if cnt == 0 { | 
|  | return p | 
|  | } | 
|  | if cnt%8 != 0 { | 
|  | gc.Fatalf("zerorange count not a multiple of widthptr %d", cnt) | 
|  | } | 
|  |  | 
|  | for i := int64(0); i < cnt; i += 8 { | 
|  | p = pp.Appendpp(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0) | 
|  | p = pp.Appendpp(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0) | 
|  | p = pp.Appendpp(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i) | 
|  | } | 
|  |  | 
|  | return p | 
|  | } | 
|  |  | 
|  | func zeroAuto(pp *gc.Progs, n *gc.Node) { | 
|  | sym := n.Sym.Linksym() | 
|  | size := n.Type.Size() | 
|  | for i := int64(0); i < size; i += 8 { | 
|  | p := pp.Prog(wasm.AGet) | 
|  | p.From = obj.Addr{Type: obj.TYPE_REG, Reg: wasm.REG_SP} | 
|  |  | 
|  | p = pp.Prog(wasm.AI64Const) | 
|  | p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0} | 
|  |  | 
|  | p = pp.Prog(wasm.AI64Store) | 
|  | p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_AUTO, Offset: n.Xoffset + i, Sym: sym} | 
|  | } | 
|  | } | 
|  |  | 
|  | func ginsnop(pp *gc.Progs) *obj.Prog { | 
|  | return pp.Prog(wasm.ANop) | 
|  | } | 
|  |  | 
|  | func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) { | 
|  | } | 
|  |  | 
|  | func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) { | 
|  | goToBlock := func(block *ssa.Block, canFallthrough bool) { | 
|  | if canFallthrough && block == next { | 
|  | return | 
|  | } | 
|  | s.Br(obj.AJMP, block) | 
|  | } | 
|  |  | 
|  | switch b.Kind { | 
|  | case ssa.BlockPlain: | 
|  | goToBlock(b.Succs[0].Block(), true) | 
|  |  | 
|  | case ssa.BlockIf: | 
|  | getValue32(s, b.Control) | 
|  | s.Prog(wasm.AI32Eqz) | 
|  | s.Prog(wasm.AIf) | 
|  | goToBlock(b.Succs[1].Block(), false) | 
|  | s.Prog(wasm.AEnd) | 
|  | goToBlock(b.Succs[0].Block(), true) | 
|  |  | 
|  | case ssa.BlockRet: | 
|  | s.Prog(obj.ARET) | 
|  |  | 
|  | case ssa.BlockRetJmp: | 
|  | p := s.Prog(obj.ARET) | 
|  | p.To.Type = obj.TYPE_MEM | 
|  | p.To.Name = obj.NAME_EXTERN | 
|  | p.To.Sym = b.Aux.(*obj.LSym) | 
|  |  | 
|  | case ssa.BlockExit: | 
|  |  | 
|  | case ssa.BlockDefer: | 
|  | p := s.Prog(wasm.AGet) | 
|  | p.From = obj.Addr{Type: obj.TYPE_REG, Reg: wasm.REG_RET0} | 
|  | s.Prog(wasm.AI64Eqz) | 
|  | s.Prog(wasm.AI32Eqz) | 
|  | s.Prog(wasm.AIf) | 
|  | goToBlock(b.Succs[1].Block(), false) | 
|  | s.Prog(wasm.AEnd) | 
|  | goToBlock(b.Succs[0].Block(), true) | 
|  |  | 
|  | default: | 
|  | panic("unexpected block") | 
|  | } | 
|  |  | 
|  | // 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) { | 
|  | switch v.Op { | 
|  | case ssa.OpWasmLoweredStaticCall, ssa.OpWasmLoweredClosureCall, ssa.OpWasmLoweredInterCall: | 
|  | s.PrepareCall(v) | 
|  | if v.Aux == gc.Deferreturn { | 
|  | // add a resume point before call to deferreturn so it can be called again via jmpdefer | 
|  | s.Prog(wasm.ARESUMEPOINT) | 
|  | } | 
|  | if v.Op == ssa.OpWasmLoweredClosureCall { | 
|  | 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} | 
|  | p.Pos = v.Pos | 
|  | } else { | 
|  | getValue64(s, v.Args[0]) | 
|  | p := s.Prog(obj.ACALL) | 
|  | p.To = obj.Addr{Type: obj.TYPE_NONE} | 
|  | p.Pos = v.Pos | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmLoweredMove: | 
|  | 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: | 
|  | 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: | 
|  | getValue64(s, v.Args[0]) | 
|  | s.Prog(wasm.AI64Eqz) | 
|  | s.Prog(wasm.AIf) | 
|  | p := s.Prog(wasm.ACALLNORESUME) | 
|  | p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.SigPanic} | 
|  | s.Prog(wasm.AEnd) | 
|  | if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers | 
|  | gc.Warnl(v.Pos, "generated nil check") | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmLoweredWB: | 
|  | 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: | 
|  | getValue32(s, v.Args[0]) | 
|  | getValue64(s, v.Args[1]) | 
|  | if v.Op == ssa.OpWasmF32Store { | 
|  | s.Prog(wasm.AF32DemoteF64) | 
|  | } | 
|  | p := s.Prog(v.Op.Asm()) | 
|  | p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt} | 
|  |  | 
|  | case ssa.OpStoreReg: | 
|  | getReg(s, wasm.REG_SP) | 
|  | getValue64(s, v.Args[0]) | 
|  | if v.Type.Etype == types.TFLOAT32 { | 
|  | s.Prog(wasm.AF32DemoteF64) | 
|  | } | 
|  | p := s.Prog(storeOp(v.Type)) | 
|  | gc.AddrAuto(&p.To, v) | 
|  |  | 
|  | default: | 
|  | 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()) | 
|  | } | 
|  | } | 
|  |  | 
|  | func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value) { | 
|  | switch v.Op { | 
|  | case ssa.OpWasmLoweredGetClosurePtr: | 
|  | getReg(s, wasm.REG_CTXT) | 
|  |  | 
|  | case ssa.OpWasmLoweredGetCallerPC: | 
|  | p := s.Prog(wasm.AI64Load) | 
|  | // Caller PC is stored 8 bytes below first parameter. | 
|  | p.From = obj.Addr{ | 
|  | Type:   obj.TYPE_MEM, | 
|  | Name:   obj.NAME_PARAM, | 
|  | Offset: -8, | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmLoweredGetCallerSP: | 
|  | p := s.Prog(wasm.AGet) | 
|  | // Caller SP is the address of the first parameter. | 
|  | p.From = obj.Addr{ | 
|  | Type:   obj.TYPE_ADDR, | 
|  | Name:   obj.NAME_PARAM, | 
|  | Reg:    wasm.REG_SP, | 
|  | Offset: 0, | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmLoweredAddr: | 
|  | p := s.Prog(wasm.AGet) | 
|  | p.From.Type = obj.TYPE_ADDR | 
|  | switch v.Aux.(type) { | 
|  | case *obj.LSym: | 
|  | gc.AddAux(&p.From, v) | 
|  | case *gc.Node: | 
|  | p.From.Reg = v.Args[0].Reg() | 
|  | gc.AddAux(&p.From, v) | 
|  | default: | 
|  | panic("wasm: bad LoweredAddr") | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmLoweredRound32F: | 
|  | getValue64(s, v.Args[0]) | 
|  | s.Prog(wasm.AF32DemoteF64) | 
|  | s.Prog(wasm.AF64PromoteF32) | 
|  |  | 
|  | case ssa.OpWasmLoweredConvert: | 
|  | getValue64(s, v.Args[0]) | 
|  |  | 
|  | case ssa.OpWasmSelect: | 
|  | 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: | 
|  | getValue64(s, v.Args[0]) | 
|  | i64Const(s, v.AuxInt) | 
|  | s.Prog(v.Op.Asm()) | 
|  |  | 
|  | case ssa.OpWasmI64Const: | 
|  | i64Const(s, v.AuxInt) | 
|  |  | 
|  | case ssa.OpWasmF64Const: | 
|  | f64Const(s, v.AuxFloat()) | 
|  |  | 
|  | case ssa.OpWasmI64Load8U, ssa.OpWasmI64Load8S, ssa.OpWasmI64Load16U, ssa.OpWasmI64Load16S, ssa.OpWasmI64Load32U, ssa.OpWasmI64Load32S, ssa.OpWasmI64Load, ssa.OpWasmF32Load, ssa.OpWasmF64Load: | 
|  | 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 { | 
|  | s.Prog(wasm.AF64PromoteF32) | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmI64Eqz: | 
|  | getValue64(s, v.Args[0]) | 
|  | s.Prog(v.Op.Asm()) | 
|  | s.Prog(wasm.AI64ExtendI32U) | 
|  |  | 
|  | 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: | 
|  | getValue64(s, v.Args[0]) | 
|  | getValue64(s, v.Args[1]) | 
|  | s.Prog(v.Op.Asm()) | 
|  | s.Prog(wasm.AI64ExtendI32U) | 
|  |  | 
|  | 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, ssa.OpWasmF64Copysign, ssa.OpWasmI64Rotl: | 
|  | getValue64(s, v.Args[0]) | 
|  | getValue64(s, v.Args[1]) | 
|  | s.Prog(v.Op.Asm()) | 
|  |  | 
|  | case ssa.OpWasmI64DivS: | 
|  | 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) | 
|  | p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmDiv} | 
|  | break | 
|  | } | 
|  | s.Prog(wasm.AI64DivS) | 
|  |  | 
|  | case ssa.OpWasmI64TruncSatF64S: | 
|  | getValue64(s, v.Args[0]) | 
|  | if objabi.GOWASM.SatConv { | 
|  | s.Prog(v.Op.Asm()) | 
|  | } else { | 
|  | p := s.Prog(wasm.ACall) | 
|  | p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncS} | 
|  | } | 
|  |  | 
|  | case ssa.OpWasmI64TruncSatF64U: | 
|  | getValue64(s, v.Args[0]) | 
|  | if objabi.GOWASM.SatConv { | 
|  | s.Prog(v.Op.Asm()) | 
|  | } else { | 
|  | p := s.Prog(wasm.ACall) | 
|  | p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncU} | 
|  | } | 
|  |  | 
|  | case | 
|  | ssa.OpWasmF64Neg, ssa.OpWasmF64ConvertI64S, ssa.OpWasmF64ConvertI64U, | 
|  | ssa.OpWasmI64Extend8S, ssa.OpWasmI64Extend16S, ssa.OpWasmI64Extend32S, | 
|  | ssa.OpWasmF64Sqrt, ssa.OpWasmF64Trunc, ssa.OpWasmF64Ceil, ssa.OpWasmF64Floor, ssa.OpWasmF64Nearest, ssa.OpWasmF64Abs, ssa.OpWasmI64Ctz, ssa.OpWasmI64Clz, ssa.OpWasmI64Popcnt: | 
|  | getValue64(s, v.Args[0]) | 
|  | s.Prog(v.Op.Asm()) | 
|  |  | 
|  | case ssa.OpLoadReg: | 
|  | p := s.Prog(loadOp(v.Type)) | 
|  | gc.AddrAuto(&p.From, v.Args[0]) | 
|  | if v.Type.Etype == types.TFLOAT32 { | 
|  | s.Prog(wasm.AF64PromoteF32) | 
|  | } | 
|  |  | 
|  | case ssa.OpCopy: | 
|  | getValue64(s, v.Args[0]) | 
|  |  | 
|  | default: | 
|  | v.Fatalf("unexpected op: %s", v.Op) | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 { | 
|  | s.Prog(wasm.AI32WrapI64) | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 { | 
|  | s.Prog(wasm.AI64ExtendI32U) | 
|  | } | 
|  | } | 
|  |  | 
|  | func i32Const(s *gc.SSAGenState, val int32) { | 
|  | p := s.Prog(wasm.AI32Const) | 
|  | p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(val)} | 
|  | } | 
|  |  | 
|  | func i64Const(s *gc.SSAGenState, val int64) { | 
|  | p := s.Prog(wasm.AI64Const) | 
|  | p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: val} | 
|  | } | 
|  |  | 
|  | func f64Const(s *gc.SSAGenState, val float64) { | 
|  | p := s.Prog(wasm.AF64Const) | 
|  | p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val} | 
|  | } | 
|  |  | 
|  | func getReg(s *gc.SSAGenState, reg int16) { | 
|  | p := s.Prog(wasm.AGet) | 
|  | p.From = obj.Addr{Type: obj.TYPE_REG, Reg: reg} | 
|  | } | 
|  |  | 
|  | func setReg(s *gc.SSAGenState, reg int16) { | 
|  | p := s.Prog(wasm.ASet) | 
|  | p.To = obj.Addr{Type: obj.TYPE_REG, Reg: reg} | 
|  | } | 
|  |  | 
|  | func loadOp(t *types.Type) obj.As { | 
|  | if t.IsFloat() { | 
|  | switch t.Size() { | 
|  | case 4: | 
|  | return wasm.AF32Load | 
|  | case 8: | 
|  | return wasm.AF64Load | 
|  | default: | 
|  | panic("bad load type") | 
|  | } | 
|  | } | 
|  |  | 
|  | switch t.Size() { | 
|  | case 1: | 
|  | if t.IsSigned() { | 
|  | return wasm.AI64Load8S | 
|  | } | 
|  | return wasm.AI64Load8U | 
|  | case 2: | 
|  | if t.IsSigned() { | 
|  | return wasm.AI64Load16S | 
|  | } | 
|  | return wasm.AI64Load16U | 
|  | case 4: | 
|  | if t.IsSigned() { | 
|  | return wasm.AI64Load32S | 
|  | } | 
|  | return wasm.AI64Load32U | 
|  | case 8: | 
|  | return wasm.AI64Load | 
|  | default: | 
|  | panic("bad load type") | 
|  | } | 
|  | } | 
|  |  | 
|  | func storeOp(t *types.Type) obj.As { | 
|  | if t.IsFloat() { | 
|  | switch t.Size() { | 
|  | case 4: | 
|  | return wasm.AF32Store | 
|  | case 8: | 
|  | return wasm.AF64Store | 
|  | default: | 
|  | panic("bad store type") | 
|  | } | 
|  | } | 
|  |  | 
|  | switch t.Size() { | 
|  | case 1: | 
|  | return wasm.AI64Store8 | 
|  | case 2: | 
|  | return wasm.AI64Store16 | 
|  | case 4: | 
|  | return wasm.AI64Store32 | 
|  | case 8: | 
|  | return wasm.AI64Store | 
|  | default: | 
|  | panic("bad store type") | 
|  | } | 
|  | } |