diff options
Diffstat (limited to 'src/cmd/internal/obj/wasm')
-rw-r--r-- | src/cmd/internal/obj/wasm/a.out.go | 331 | ||||
-rw-r--r-- | src/cmd/internal/obj/wasm/anames.go | 208 | ||||
-rw-r--r-- | src/cmd/internal/obj/wasm/wasmobj.go | 1185 |
3 files changed, 1724 insertions, 0 deletions
diff --git a/src/cmd/internal/obj/wasm/a.out.go b/src/cmd/internal/obj/wasm/a.out.go new file mode 100644 index 0000000..72ecaa9 --- /dev/null +++ b/src/cmd/internal/obj/wasm/a.out.go @@ -0,0 +1,331 @@ +// 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/internal/obj" + +//go:generate go run ../stringer.go -i $GOFILE -o anames.go -p wasm + +const ( + /* mark flags */ + DONE = 1 << iota + PRESERVEFLAGS // not allowed to clobber flags +) + +/* + * wasm + */ +const ( + ACallImport = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota + AGet + ASet + ATee + ANot // alias for I32Eqz + + // The following are low-level WebAssembly instructions. + // Their order matters, since it matches the opcode encoding. + // Gaps in the encoding are indicated by comments. + + AUnreachable // opcode 0x00 + ANop + ABlock + ALoop + AIf + AElse + + AEnd // opcode 0x0B + ABr + ABrIf + ABrTable + // ACall and AReturn are WebAssembly instructions. obj.ACALL and obj.ARET are higher level instructions + // with Go semantics, e.g. they manipulate the Go stack on the linear memory. + AReturn + ACall + ACallIndirect + + ADrop // opcode 0x1A + ASelect + + ALocalGet // opcode 0x20 + ALocalSet + ALocalTee + AGlobalGet + AGlobalSet + + AI32Load // opcode 0x28 + AI64Load + AF32Load + AF64Load + AI32Load8S + AI32Load8U + AI32Load16S + AI32Load16U + AI64Load8S + AI64Load8U + AI64Load16S + AI64Load16U + AI64Load32S + AI64Load32U + AI32Store + AI64Store + AF32Store + AF64Store + AI32Store8 + AI32Store16 + AI64Store8 + AI64Store16 + AI64Store32 + ACurrentMemory + AGrowMemory + + AI32Const + AI64Const + AF32Const + AF64Const + + AI32Eqz + AI32Eq + AI32Ne + AI32LtS + AI32LtU + AI32GtS + AI32GtU + AI32LeS + AI32LeU + AI32GeS + AI32GeU + + AI64Eqz + AI64Eq + AI64Ne + AI64LtS + AI64LtU + AI64GtS + AI64GtU + AI64LeS + AI64LeU + AI64GeS + AI64GeU + + AF32Eq + AF32Ne + AF32Lt + AF32Gt + AF32Le + AF32Ge + + AF64Eq + AF64Ne + AF64Lt + AF64Gt + AF64Le + AF64Ge + + AI32Clz + AI32Ctz + AI32Popcnt + AI32Add + AI32Sub + AI32Mul + AI32DivS + AI32DivU + AI32RemS + AI32RemU + AI32And + AI32Or + AI32Xor + AI32Shl + AI32ShrS + AI32ShrU + AI32Rotl + AI32Rotr + + AI64Clz + AI64Ctz + AI64Popcnt + AI64Add + AI64Sub + AI64Mul + AI64DivS + AI64DivU + AI64RemS + AI64RemU + AI64And + AI64Or + AI64Xor + AI64Shl + AI64ShrS + AI64ShrU + AI64Rotl + AI64Rotr + + AF32Abs + AF32Neg + AF32Ceil + AF32Floor + AF32Trunc + AF32Nearest + AF32Sqrt + AF32Add + AF32Sub + AF32Mul + AF32Div + AF32Min + AF32Max + AF32Copysign + + AF64Abs + AF64Neg + AF64Ceil + AF64Floor + AF64Trunc + AF64Nearest + AF64Sqrt + AF64Add + AF64Sub + AF64Mul + AF64Div + AF64Min + AF64Max + AF64Copysign + + AI32WrapI64 + AI32TruncF32S + AI32TruncF32U + AI32TruncF64S + AI32TruncF64U + AI64ExtendI32S + AI64ExtendI32U + AI64TruncF32S + AI64TruncF32U + AI64TruncF64S + AI64TruncF64U + AF32ConvertI32S + AF32ConvertI32U + AF32ConvertI64S + AF32ConvertI64U + AF32DemoteF64 + AF64ConvertI32S + AF64ConvertI32U + AF64ConvertI64S + AF64ConvertI64U + AF64PromoteF32 + AI32ReinterpretF32 + AI64ReinterpretF64 + AF32ReinterpretI32 + AF64ReinterpretI64 + AI32Extend8S + AI32Extend16S + AI64Extend8S + AI64Extend16S + AI64Extend32S + + AI32TruncSatF32S // opcode 0xFC 0x00 + AI32TruncSatF32U + AI32TruncSatF64S + AI32TruncSatF64U + AI64TruncSatF32S + AI64TruncSatF32U + AI64TruncSatF64S + AI64TruncSatF64U + + ALast // Sentinel: End of low-level WebAssembly instructions. + + ARESUMEPOINT + // ACALLNORESUME is a call which is not followed by a resume point. + // It is allowed inside of WebAssembly blocks, whereas obj.ACALL is not. + // However, it is not allowed to switch goroutines while inside of an ACALLNORESUME call. + ACALLNORESUME + + ARETUNWIND + + AMOVB + AMOVH + AMOVW + AMOVD + + AWORD + ALAST +) + +const ( + REG_NONE = 0 +) + +const ( + // globals + REG_SP = obj.RBaseWasm + iota // SP is currently 32-bit, until 64-bit memory operations are available + REG_CTXT + REG_g + // RET* are used by runtime.return0 and runtime.reflectcall. These functions pass return values in registers. + REG_RET0 + REG_RET1 + REG_RET2 + REG_RET3 + REG_PAUSE + + // i32 locals + REG_R0 + REG_R1 + REG_R2 + REG_R3 + REG_R4 + REG_R5 + REG_R6 + REG_R7 + REG_R8 + REG_R9 + REG_R10 + REG_R11 + REG_R12 + REG_R13 + REG_R14 + REG_R15 + + // f32 locals + REG_F0 + REG_F1 + REG_F2 + REG_F3 + REG_F4 + REG_F5 + REG_F6 + REG_F7 + REG_F8 + REG_F9 + REG_F10 + REG_F11 + REG_F12 + REG_F13 + REG_F14 + REG_F15 + + // f64 locals + REG_F16 + REG_F17 + REG_F18 + REG_F19 + REG_F20 + REG_F21 + REG_F22 + REG_F23 + REG_F24 + REG_F25 + REG_F26 + REG_F27 + REG_F28 + REG_F29 + REG_F30 + REG_F31 + + REG_PC_B // also first parameter, i32 + + MAXREG + + MINREG = REG_SP + REGSP = REG_SP + REGCTXT = REG_CTXT + REGG = REG_g +) diff --git a/src/cmd/internal/obj/wasm/anames.go b/src/cmd/internal/obj/wasm/anames.go new file mode 100644 index 0000000..9412384 --- /dev/null +++ b/src/cmd/internal/obj/wasm/anames.go @@ -0,0 +1,208 @@ +// Code generated by stringer -i a.out.go -o anames.go -p wasm; DO NOT EDIT. + +package wasm + +import "cmd/internal/obj" + +var Anames = []string{ + obj.A_ARCHSPECIFIC: "CallImport", + "Get", + "Set", + "Tee", + "Not", + "Unreachable", + "Nop", + "Block", + "Loop", + "If", + "Else", + "End", + "Br", + "BrIf", + "BrTable", + "Return", + "Call", + "CallIndirect", + "Drop", + "Select", + "LocalGet", + "LocalSet", + "LocalTee", + "GlobalGet", + "GlobalSet", + "I32Load", + "I64Load", + "F32Load", + "F64Load", + "I32Load8S", + "I32Load8U", + "I32Load16S", + "I32Load16U", + "I64Load8S", + "I64Load8U", + "I64Load16S", + "I64Load16U", + "I64Load32S", + "I64Load32U", + "I32Store", + "I64Store", + "F32Store", + "F64Store", + "I32Store8", + "I32Store16", + "I64Store8", + "I64Store16", + "I64Store32", + "CurrentMemory", + "GrowMemory", + "I32Const", + "I64Const", + "F32Const", + "F64Const", + "I32Eqz", + "I32Eq", + "I32Ne", + "I32LtS", + "I32LtU", + "I32GtS", + "I32GtU", + "I32LeS", + "I32LeU", + "I32GeS", + "I32GeU", + "I64Eqz", + "I64Eq", + "I64Ne", + "I64LtS", + "I64LtU", + "I64GtS", + "I64GtU", + "I64LeS", + "I64LeU", + "I64GeS", + "I64GeU", + "F32Eq", + "F32Ne", + "F32Lt", + "F32Gt", + "F32Le", + "F32Ge", + "F64Eq", + "F64Ne", + "F64Lt", + "F64Gt", + "F64Le", + "F64Ge", + "I32Clz", + "I32Ctz", + "I32Popcnt", + "I32Add", + "I32Sub", + "I32Mul", + "I32DivS", + "I32DivU", + "I32RemS", + "I32RemU", + "I32And", + "I32Or", + "I32Xor", + "I32Shl", + "I32ShrS", + "I32ShrU", + "I32Rotl", + "I32Rotr", + "I64Clz", + "I64Ctz", + "I64Popcnt", + "I64Add", + "I64Sub", + "I64Mul", + "I64DivS", + "I64DivU", + "I64RemS", + "I64RemU", + "I64And", + "I64Or", + "I64Xor", + "I64Shl", + "I64ShrS", + "I64ShrU", + "I64Rotl", + "I64Rotr", + "F32Abs", + "F32Neg", + "F32Ceil", + "F32Floor", + "F32Trunc", + "F32Nearest", + "F32Sqrt", + "F32Add", + "F32Sub", + "F32Mul", + "F32Div", + "F32Min", + "F32Max", + "F32Copysign", + "F64Abs", + "F64Neg", + "F64Ceil", + "F64Floor", + "F64Trunc", + "F64Nearest", + "F64Sqrt", + "F64Add", + "F64Sub", + "F64Mul", + "F64Div", + "F64Min", + "F64Max", + "F64Copysign", + "I32WrapI64", + "I32TruncF32S", + "I32TruncF32U", + "I32TruncF64S", + "I32TruncF64U", + "I64ExtendI32S", + "I64ExtendI32U", + "I64TruncF32S", + "I64TruncF32U", + "I64TruncF64S", + "I64TruncF64U", + "F32ConvertI32S", + "F32ConvertI32U", + "F32ConvertI64S", + "F32ConvertI64U", + "F32DemoteF64", + "F64ConvertI32S", + "F64ConvertI32U", + "F64ConvertI64S", + "F64ConvertI64U", + "F64PromoteF32", + "I32ReinterpretF32", + "I64ReinterpretF64", + "F32ReinterpretI32", + "F64ReinterpretI64", + "I32Extend8S", + "I32Extend16S", + "I64Extend8S", + "I64Extend16S", + "I64Extend32S", + "I32TruncSatF32S", + "I32TruncSatF32U", + "I32TruncSatF64S", + "I32TruncSatF64U", + "I64TruncSatF32S", + "I64TruncSatF32U", + "I64TruncSatF64S", + "I64TruncSatF64U", + "Last", + "RESUMEPOINT", + "CALLNORESUME", + "RETUNWIND", + "MOVB", + "MOVH", + "MOVW", + "MOVD", + "WORD", + "LAST", +} diff --git a/src/cmd/internal/obj/wasm/wasmobj.go b/src/cmd/internal/obj/wasm/wasmobj.go new file mode 100644 index 0000000..2e9890d --- /dev/null +++ b/src/cmd/internal/obj/wasm/wasmobj.go @@ -0,0 +1,1185 @@ +// 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 ( + "bytes" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/sys" + "encoding/binary" + "fmt" + "io" + "math" +) + +var Register = map[string]int16{ + "SP": REG_SP, + "CTXT": REG_CTXT, + "g": REG_g, + "RET0": REG_RET0, + "RET1": REG_RET1, + "RET2": REG_RET2, + "RET3": REG_RET3, + "PAUSE": REG_PAUSE, + + "R0": REG_R0, + "R1": REG_R1, + "R2": REG_R2, + "R3": REG_R3, + "R4": REG_R4, + "R5": REG_R5, + "R6": REG_R6, + "R7": REG_R7, + "R8": REG_R8, + "R9": REG_R9, + "R10": REG_R10, + "R11": REG_R11, + "R12": REG_R12, + "R13": REG_R13, + "R14": REG_R14, + "R15": REG_R15, + + "F0": REG_F0, + "F1": REG_F1, + "F2": REG_F2, + "F3": REG_F3, + "F4": REG_F4, + "F5": REG_F5, + "F6": REG_F6, + "F7": REG_F7, + "F8": REG_F8, + "F9": REG_F9, + "F10": REG_F10, + "F11": REG_F11, + "F12": REG_F12, + "F13": REG_F13, + "F14": REG_F14, + "F15": REG_F15, + + "F16": REG_F16, + "F17": REG_F17, + "F18": REG_F18, + "F19": REG_F19, + "F20": REG_F20, + "F21": REG_F21, + "F22": REG_F22, + "F23": REG_F23, + "F24": REG_F24, + "F25": REG_F25, + "F26": REG_F26, + "F27": REG_F27, + "F28": REG_F28, + "F29": REG_F29, + "F30": REG_F30, + "F31": REG_F31, + + "PC_B": REG_PC_B, +} + +var registerNames []string + +func init() { + obj.RegisterRegister(MINREG, MAXREG, rconv) + obj.RegisterOpcode(obj.ABaseWasm, Anames) + + registerNames = make([]string, MAXREG-MINREG) + for name, reg := range Register { + registerNames[reg-MINREG] = name + } +} + +func rconv(r int) string { + return registerNames[r-MINREG] +} + +var unaryDst = map[obj.As]bool{ + ASet: true, + ATee: true, + ACall: true, + ACallIndirect: true, + ACallImport: true, + ABr: true, + ABrIf: true, + ABrTable: true, + AI32Store: true, + AI64Store: true, + AF32Store: true, + AF64Store: true, + AI32Store8: true, + AI32Store16: true, + AI64Store8: true, + AI64Store16: true, + AI64Store32: true, + ACALLNORESUME: true, +} + +var Linkwasm = obj.LinkArch{ + Arch: sys.ArchWasm, + Init: instinit, + Preprocess: preprocess, + Assemble: assemble, + UnaryDst: unaryDst, +} + +var ( + morestack *obj.LSym + morestackNoCtxt *obj.LSym + gcWriteBarrier *obj.LSym + sigpanic *obj.LSym + deferreturn *obj.LSym + jmpdefer *obj.LSym +) + +const ( + /* mark flags */ + WasmImport = 1 << 0 +) + +func instinit(ctxt *obj.Link) { + morestack = ctxt.Lookup("runtime.morestack") + morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") + gcWriteBarrier = ctxt.LookupABI("runtime.gcWriteBarrier", obj.ABIInternal) + sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) + deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal) + // jmpdefer is defined in assembly as ABI0, but what we're + // looking for is the *call* to jmpdefer from the Go function + // deferreturn, so we're looking for the ABIInternal version + // of jmpdefer that's called by Go. + jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal) +} + +func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { + appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog { + if p.As != obj.ANOP { + p2 := obj.Appendp(p, newprog) + p2.Pc = p.Pc + p = p2 + } + p.As = as + switch len(args) { + case 0: + p.From = obj.Addr{} + p.To = obj.Addr{} + case 1: + if unaryDst[as] { + p.From = obj.Addr{} + p.To = args[0] + } else { + p.From = args[0] + p.To = obj.Addr{} + } + case 2: + p.From = args[0] + p.To = args[1] + default: + panic("bad args") + } + return p + } + + framesize := s.Func().Text.To.Offset + if framesize < 0 { + panic("bad framesize") + } + s.Func().Args = s.Func().Text.To.Val.(int32) + s.Func().Locals = int32(framesize) + + if s.Func().Text.From.Sym.Wrapper() { + // if g._panic != nil && g._panic.argp == FP { + // g._panic.argp = bottom-of-frame + // } + // + // MOVD g_panic(g), R0 + // Get R0 + // I64Eqz + // Not + // If + // Get SP + // I64ExtendI32U + // I64Const $framesize+8 + // I64Add + // I64Load panic_argp(R0) + // I64Eq + // If + // MOVD SP, panic_argp(R0) + // End + // End + + gpanic := obj.Addr{ + Type: obj.TYPE_MEM, + Reg: REGG, + Offset: 4 * 8, // g_panic + } + + panicargp := obj.Addr{ + Type: obj.TYPE_MEM, + Reg: REG_R0, + Offset: 0, // panic.argp + } + + p := s.Func().Text + p = appendp(p, AMOVD, gpanic, regAddr(REG_R0)) + + p = appendp(p, AGet, regAddr(REG_R0)) + p = appendp(p, AI64Eqz) + p = appendp(p, ANot) + p = appendp(p, AIf) + + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI64ExtendI32U) + p = appendp(p, AI64Const, constAddr(framesize+8)) + p = appendp(p, AI64Add) + p = appendp(p, AI64Load, panicargp) + + p = appendp(p, AI64Eq) + p = appendp(p, AIf) + p = appendp(p, AMOVD, regAddr(REG_SP), panicargp) + p = appendp(p, AEnd) + + p = appendp(p, AEnd) + } + + if framesize > 0 { + p := s.Func().Text + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI32Const, constAddr(framesize)) + p = appendp(p, AI32Sub) + p = appendp(p, ASet, regAddr(REG_SP)) + p.Spadj = int32(framesize) + } + + // Introduce resume points for CALL instructions + // and collect other explicit resume points. + numResumePoints := 0 + explicitBlockDepth := 0 + pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction + var tableIdxs []uint64 + tablePC := int64(0) + base := ctxt.PosTable.Pos(s.Func().Text.Pos).Base() + for p := s.Func().Text; p != nil; p = p.Link { + prevBase := base + base = ctxt.PosTable.Pos(p.Pos).Base() + switch p.As { + case ABlock, ALoop, AIf: + explicitBlockDepth++ + + case AEnd: + if explicitBlockDepth == 0 { + panic("End without block") + } + explicitBlockDepth-- + + case ARESUMEPOINT: + if explicitBlockDepth != 0 { + panic("RESUME can only be used on toplevel") + } + p.As = AEnd + for tablePC <= pc { + tableIdxs = append(tableIdxs, uint64(numResumePoints)) + tablePC++ + } + numResumePoints++ + pc++ + + case obj.ACALL: + if explicitBlockDepth != 0 { + panic("CALL can only be used on toplevel, try CALLNORESUME instead") + } + appendp(p, ARESUMEPOINT) + } + + p.Pc = pc + + // Increase pc whenever some pc-value table needs a new entry. Don't increase it + // more often to avoid bloat of the BrTable instruction. + // The "base != prevBase" condition detects inlined instructions. They are an + // implicit call, so entering and leaving this section affects the stack trace. + if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase { + pc++ + if p.To.Sym == sigpanic { + // The panic stack trace expects the PC at the call of sigpanic, + // not the next one. However, runtime.Caller subtracts 1 from the + // PC. To make both PC and PC-1 work (have the same line number), + // we advance the PC by 2 at sigpanic. + pc++ + } + } + } + tableIdxs = append(tableIdxs, uint64(numResumePoints)) + s.Size = pc + 1 + + if !s.Func().Text.From.Sym.NoSplit() { + p := s.Func().Text + + if framesize <= objabi.StackSmall { + // small stack: SP <= stackguard + // Get SP + // Get g + // I32WrapI64 + // I32Load $stackguard0 + // I32GtU + + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AGet, regAddr(REGG)) + p = appendp(p, AI32WrapI64) + p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 + p = appendp(p, AI32LeU) + } else { + // large stack: SP-framesize <= stackguard-StackSmall + // SP <= stackguard+(framesize-StackSmall) + // Get SP + // Get g + // I32WrapI64 + // I32Load $stackguard0 + // I32Const $(framesize-StackSmall) + // I32Add + // I32GtU + + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AGet, regAddr(REGG)) + p = appendp(p, AI32WrapI64) + p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 + p = appendp(p, AI32Const, constAddr(int64(framesize)-objabi.StackSmall)) + p = appendp(p, AI32Add) + p = appendp(p, AI32LeU) + } + // TODO(neelance): handle wraparound case + + p = appendp(p, AIf) + p = appendp(p, obj.ACALL, constAddr(0)) + if s.Func().Text.From.Sym.NeedCtxt() { + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack} + } else { + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt} + } + p = appendp(p, AEnd) + } + + // record the branches targeting the entry loop and the unwind exit, + // their targets with be filled in later + var entryPointLoopBranches []*obj.Prog + var unwindExitBranches []*obj.Prog + currentDepth := 0 + for p := s.Func().Text; p != nil; p = p.Link { + switch p.As { + case ABlock, ALoop, AIf: + currentDepth++ + case AEnd: + currentDepth-- + } + + switch p.As { + case obj.AJMP: + jmp := *p + p.As = obj.ANOP + + if jmp.To.Type == obj.TYPE_BRANCH { + // jump to basic block + p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc)) + p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B + p = appendp(p, ABr) // jump to beginning of entryPointLoop + entryPointLoopBranches = append(entryPointLoopBranches, p) + break + } + + // low-level WebAssembly call to function + switch jmp.To.Type { + case obj.TYPE_MEM: + if !notUsePC_B[jmp.To.Sym.Name] { + // Set PC_B parameter to function entry. + p = appendp(p, AI32Const, constAddr(0)) + } + p = appendp(p, ACall, jmp.To) + + case obj.TYPE_NONE: + // (target PC is on stack) + p = appendp(p, AI32WrapI64) + p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero + p = appendp(p, AI32ShrU) + + // Set PC_B parameter to function entry. + // We need to push this before pushing the target PC_F, + // so temporarily pop PC_F, using our REG_PC_B as a + // scratch register, and push it back after pushing 0. + p = appendp(p, ASet, regAddr(REG_PC_B)) + p = appendp(p, AI32Const, constAddr(0)) + p = appendp(p, AGet, regAddr(REG_PC_B)) + + p = appendp(p, ACallIndirect) + + default: + panic("bad target for JMP") + } + + p = appendp(p, AReturn) + + case obj.ACALL, ACALLNORESUME: + call := *p + p.As = obj.ANOP + + pcAfterCall := call.Link.Pc + if call.To.Sym == sigpanic { + pcAfterCall-- // sigpanic expects to be called without advancing the pc + } + + // jmpdefer manipulates the return address on the stack so deferreturn gets called repeatedly. + // Model this in WebAssembly with a loop. + if call.To.Sym == deferreturn { + p = appendp(p, ALoop) + } + + // SP -= 8 + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI32Const, constAddr(8)) + p = appendp(p, AI32Sub) + p = appendp(p, ASet, regAddr(REG_SP)) + + // write return address to Go stack + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI64Const, obj.Addr{ + Type: obj.TYPE_ADDR, + Name: obj.NAME_EXTERN, + Sym: s, // PC_F + Offset: pcAfterCall, // PC_B + }) + p = appendp(p, AI64Store, constAddr(0)) + + // low-level WebAssembly call to function + switch call.To.Type { + case obj.TYPE_MEM: + if !notUsePC_B[call.To.Sym.Name] { + // Set PC_B parameter to function entry. + p = appendp(p, AI32Const, constAddr(0)) + } + p = appendp(p, ACall, call.To) + + case obj.TYPE_NONE: + // (target PC is on stack) + p = appendp(p, AI32WrapI64) + p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero + p = appendp(p, AI32ShrU) + + // Set PC_B parameter to function entry. + // We need to push this before pushing the target PC_F, + // so temporarily pop PC_F, using our PC_B as a + // scratch register, and push it back after pushing 0. + p = appendp(p, ASet, regAddr(REG_PC_B)) + p = appendp(p, AI32Const, constAddr(0)) + p = appendp(p, AGet, regAddr(REG_PC_B)) + + p = appendp(p, ACallIndirect) + + default: + panic("bad target for CALL") + } + + // gcWriteBarrier has no return value, it never unwinds the stack + if call.To.Sym == gcWriteBarrier { + break + } + + // jmpdefer removes the frame of deferreturn from the Go stack. + // However, its WebAssembly function still returns normally, + // so we need to return from deferreturn without removing its + // stack frame (no RET), because the frame is already gone. + if call.To.Sym == jmpdefer { + p = appendp(p, AReturn) + break + } + + // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack + if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes + // trying to unwind WebAssembly stack but call has no resume point, terminate with error + p = appendp(p, AIf) + p = appendp(p, obj.AUNDEF) + p = appendp(p, AEnd) + } else { + // unwinding WebAssembly stack to switch goroutine, return 1 + p = appendp(p, ABrIf) + unwindExitBranches = append(unwindExitBranches, p) + } + + // jump to before the call if jmpdefer has reset the return address to the call's PC + if call.To.Sym == deferreturn { + // get PC_B from -8(SP) + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI32Const, constAddr(8)) + p = appendp(p, AI32Sub) + p = appendp(p, AI32Load16U, constAddr(0)) + p = appendp(p, ATee, regAddr(REG_PC_B)) + + p = appendp(p, AI32Const, constAddr(call.Pc)) + p = appendp(p, AI32Eq) + p = appendp(p, ABrIf, constAddr(0)) + p = appendp(p, AEnd) // end of Loop + } + + case obj.ARET, ARETUNWIND: + ret := *p + p.As = obj.ANOP + + if framesize > 0 { + // SP += framesize + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI32Const, constAddr(framesize)) + p = appendp(p, AI32Add) + p = appendp(p, ASet, regAddr(REG_SP)) + // TODO(neelance): This should theoretically set Spadj, but it only works without. + // p.Spadj = int32(-framesize) + } + + if ret.To.Type == obj.TYPE_MEM { + // Set PC_B parameter to function entry. + p = appendp(p, AI32Const, constAddr(0)) + + // low-level WebAssembly call to function + p = appendp(p, ACall, ret.To) + p = appendp(p, AReturn) + break + } + + // SP += 8 + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, AI32Const, constAddr(8)) + p = appendp(p, AI32Add) + p = appendp(p, ASet, regAddr(REG_SP)) + + if ret.As == ARETUNWIND { + // function needs to unwind the WebAssembly stack, return 1 + p = appendp(p, AI32Const, constAddr(1)) + p = appendp(p, AReturn) + break + } + + // not unwinding the WebAssembly stack, return 0 + p = appendp(p, AI32Const, constAddr(0)) + p = appendp(p, AReturn) + } + } + + for p := s.Func().Text; p != nil; p = p.Link { + switch p.From.Name { + case obj.NAME_AUTO: + p.From.Offset += int64(framesize) + case obj.NAME_PARAM: + p.From.Reg = REG_SP + p.From.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address + } + + switch p.To.Name { + case obj.NAME_AUTO: + p.To.Offset += int64(framesize) + case obj.NAME_PARAM: + p.To.Reg = REG_SP + p.To.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address + } + + switch p.As { + case AGet: + if p.From.Type == obj.TYPE_ADDR { + get := *p + p.As = obj.ANOP + + switch get.From.Name { + case obj.NAME_EXTERN: + p = appendp(p, AI64Const, get.From) + case obj.NAME_AUTO, obj.NAME_PARAM: + p = appendp(p, AGet, regAddr(get.From.Reg)) + if get.From.Reg == REG_SP { + p = appendp(p, AI64ExtendI32U) + } + if get.From.Offset != 0 { + p = appendp(p, AI64Const, constAddr(get.From.Offset)) + p = appendp(p, AI64Add) + } + default: + panic("bad Get: invalid name") + } + } + + case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: + if p.From.Type == obj.TYPE_MEM { + as := p.As + from := p.From + + p.As = AGet + p.From = regAddr(from.Reg) + + if from.Reg != REG_SP { + p = appendp(p, AI32WrapI64) + } + + p = appendp(p, as, constAddr(from.Offset)) + } + + case AMOVB, AMOVH, AMOVW, AMOVD: + mov := *p + p.As = obj.ANOP + + var loadAs obj.As + var storeAs obj.As + switch mov.As { + case AMOVB: + loadAs = AI64Load8U + storeAs = AI64Store8 + case AMOVH: + loadAs = AI64Load16U + storeAs = AI64Store16 + case AMOVW: + loadAs = AI64Load32U + storeAs = AI64Store32 + case AMOVD: + loadAs = AI64Load + storeAs = AI64Store + } + + appendValue := func() { + switch mov.From.Type { + case obj.TYPE_CONST: + p = appendp(p, AI64Const, constAddr(mov.From.Offset)) + + case obj.TYPE_ADDR: + switch mov.From.Name { + case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO: + p = appendp(p, AGet, regAddr(mov.From.Reg)) + if mov.From.Reg == REG_SP { + p = appendp(p, AI64ExtendI32U) + } + p = appendp(p, AI64Const, constAddr(mov.From.Offset)) + p = appendp(p, AI64Add) + case obj.NAME_EXTERN: + p = appendp(p, AI64Const, mov.From) + default: + panic("bad name for MOV") + } + + case obj.TYPE_REG: + p = appendp(p, AGet, mov.From) + if mov.From.Reg == REG_SP { + p = appendp(p, AI64ExtendI32U) + } + + case obj.TYPE_MEM: + p = appendp(p, AGet, regAddr(mov.From.Reg)) + if mov.From.Reg != REG_SP { + p = appendp(p, AI32WrapI64) + } + p = appendp(p, loadAs, constAddr(mov.From.Offset)) + + default: + panic("bad MOV type") + } + } + + switch mov.To.Type { + case obj.TYPE_REG: + appendValue() + if mov.To.Reg == REG_SP { + p = appendp(p, AI32WrapI64) + } + p = appendp(p, ASet, mov.To) + + case obj.TYPE_MEM: + switch mov.To.Name { + case obj.NAME_NONE, obj.NAME_PARAM: + p = appendp(p, AGet, regAddr(mov.To.Reg)) + if mov.To.Reg != REG_SP { + p = appendp(p, AI32WrapI64) + } + case obj.NAME_EXTERN: + p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym}) + default: + panic("bad MOV name") + } + appendValue() + p = appendp(p, storeAs, constAddr(mov.To.Offset)) + + default: + panic("bad MOV type") + } + + case ACallImport: + p.As = obj.ANOP + p = appendp(p, AGet, regAddr(REG_SP)) + p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s}) + p.Mark = WasmImport + } + } + + { + p := s.Func().Text + if len(unwindExitBranches) > 0 { + p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack + for _, b := range unwindExitBranches { + b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p} + } + } + if len(entryPointLoopBranches) > 0 { + p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks + for _, b := range entryPointLoopBranches { + b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p} + } + } + if numResumePoints > 0 { + // Add Block instructions for resume points and BrTable to jump to selected resume point. + for i := 0; i < numResumePoints+1; i++ { + p = appendp(p, ABlock) + } + p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B + p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs}) + p = appendp(p, AEnd) // end of Block + } + for p.Link != nil { + p = p.Link // function instructions + } + if len(entryPointLoopBranches) > 0 { + p = appendp(p, AEnd) // end of entryPointLoop + } + p = appendp(p, obj.AUNDEF) + if len(unwindExitBranches) > 0 { + p = appendp(p, AEnd) // end of unwindExit + p = appendp(p, AI32Const, constAddr(1)) + } + } + + currentDepth = 0 + blockDepths := make(map[*obj.Prog]int) + for p := s.Func().Text; p != nil; p = p.Link { + switch p.As { + case ABlock, ALoop, AIf: + currentDepth++ + blockDepths[p] = currentDepth + case AEnd: + currentDepth-- + } + + switch p.As { + case ABr, ABrIf: + if p.To.Type == obj.TYPE_BRANCH { + blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)] + if !ok { + panic("label not at block") + } + p.To = constAddr(int64(currentDepth - blockDepth)) + } + } + } +} + +func constAddr(value int64) obj.Addr { + return obj.Addr{Type: obj.TYPE_CONST, Offset: value} +} + +func regAddr(reg int16) obj.Addr { + return obj.Addr{Type: obj.TYPE_REG, Reg: reg} +} + +// Most of the Go functions has a single parameter (PC_B) in +// Wasm ABI. This is a list of exceptions. +var notUsePC_B = map[string]bool{ + "_rt0_wasm_js": true, + "wasm_export_run": true, + "wasm_export_resume": true, + "wasm_export_getsp": true, + "wasm_pc_f_loop": true, + "runtime.wasmMove": true, + "runtime.wasmZero": true, + "runtime.wasmDiv": true, + "runtime.wasmTruncS": true, + "runtime.wasmTruncU": true, + "runtime.gcWriteBarrier": true, + "cmpbody": true, + "memeqbody": true, + "memcmp": true, + "memchr": true, +} + +func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { + type regVar struct { + global bool + index uint64 + } + + type varDecl struct { + count uint64 + typ valueType + } + + hasLocalSP := false + regVars := [MAXREG - MINREG]*regVar{ + REG_SP - MINREG: {true, 0}, + REG_CTXT - MINREG: {true, 1}, + REG_g - MINREG: {true, 2}, + REG_RET0 - MINREG: {true, 3}, + REG_RET1 - MINREG: {true, 4}, + REG_RET2 - MINREG: {true, 5}, + REG_RET3 - MINREG: {true, 6}, + REG_PAUSE - MINREG: {true, 7}, + } + var varDecls []*varDecl + useAssemblyRegMap := func() { + for i := int16(0); i < 16; i++ { + regVars[REG_R0+i-MINREG] = ®Var{false, uint64(i)} + } + } + + // Function starts with declaration of locals: numbers and types. + // Some functions use a special calling convention. + switch s.Name { + case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop", + "runtime.wasmMove", "runtime.wasmZero", "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody": + varDecls = []*varDecl{} + useAssemblyRegMap() + case "memchr", "memcmp": + varDecls = []*varDecl{{count: 2, typ: i32}} + useAssemblyRegMap() + case "cmpbody": + varDecls = []*varDecl{{count: 2, typ: i64}} + useAssemblyRegMap() + case "runtime.gcWriteBarrier": + varDecls = []*varDecl{{count: 4, typ: i64}} + useAssemblyRegMap() + default: + // Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache. + regVars[REG_PC_B-MINREG] = ®Var{false, 0} + hasLocalSP = true + + var regUsed [MAXREG - MINREG]bool + for p := s.Func().Text; p != nil; p = p.Link { + if p.From.Reg != 0 { + regUsed[p.From.Reg-MINREG] = true + } + if p.To.Reg != 0 { + regUsed[p.To.Reg-MINREG] = true + } + } + + regs := []int16{REG_SP} + for reg := int16(REG_R0); reg <= REG_F31; reg++ { + if regUsed[reg-MINREG] { + regs = append(regs, reg) + } + } + + var lastDecl *varDecl + for i, reg := range regs { + t := regType(reg) + if lastDecl == nil || lastDecl.typ != t { + lastDecl = &varDecl{ + count: 0, + typ: t, + } + varDecls = append(varDecls, lastDecl) + } + lastDecl.count++ + if reg != REG_SP { + regVars[reg-MINREG] = ®Var{false, 1 + uint64(i)} + } + } + } + + w := new(bytes.Buffer) + + writeUleb128(w, uint64(len(varDecls))) + for _, decl := range varDecls { + writeUleb128(w, decl.count) + w.WriteByte(byte(decl.typ)) + } + + if hasLocalSP { + // Copy SP from its global variable into a local variable. Accessing a local variable is more efficient. + updateLocalSP(w) + } + + for p := s.Func().Text; p != nil; p = p.Link { + switch p.As { + case AGet: + if p.From.Type != obj.TYPE_REG { + panic("bad Get: argument is not a register") + } + reg := p.From.Reg + v := regVars[reg-MINREG] + if v == nil { + panic("bad Get: invalid register") + } + if reg == REG_SP && hasLocalSP { + writeOpcode(w, ALocalGet) + writeUleb128(w, 1) // local SP + continue + } + if v.global { + writeOpcode(w, AGlobalGet) + } else { + writeOpcode(w, ALocalGet) + } + writeUleb128(w, v.index) + continue + + case ASet: + if p.To.Type != obj.TYPE_REG { + panic("bad Set: argument is not a register") + } + reg := p.To.Reg + v := regVars[reg-MINREG] + if v == nil { + panic("bad Set: invalid register") + } + if reg == REG_SP && hasLocalSP { + writeOpcode(w, ALocalTee) + writeUleb128(w, 1) // local SP + } + if v.global { + writeOpcode(w, AGlobalSet) + } else { + if p.Link.As == AGet && p.Link.From.Reg == reg { + writeOpcode(w, ALocalTee) + p = p.Link + } else { + writeOpcode(w, ALocalSet) + } + } + writeUleb128(w, v.index) + continue + + case ATee: + if p.To.Type != obj.TYPE_REG { + panic("bad Tee: argument is not a register") + } + reg := p.To.Reg + v := regVars[reg-MINREG] + if v == nil { + panic("bad Tee: invalid register") + } + writeOpcode(w, ALocalTee) + writeUleb128(w, v.index) + continue + + case ANot: + writeOpcode(w, AI32Eqz) + continue + + case obj.AUNDEF: + writeOpcode(w, AUnreachable) + continue + + case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA: + // ignore + continue + } + + writeOpcode(w, p.As) + + switch p.As { + case ABlock, ALoop, AIf: + if p.From.Offset != 0 { + // block type, rarely used, e.g. for code compiled with emscripten + w.WriteByte(0x80 - byte(p.From.Offset)) + continue + } + w.WriteByte(0x40) + + case ABr, ABrIf: + if p.To.Type != obj.TYPE_CONST { + panic("bad Br/BrIf") + } + writeUleb128(w, uint64(p.To.Offset)) + + case ABrTable: + idxs := p.To.Val.([]uint64) + writeUleb128(w, uint64(len(idxs)-1)) + for _, idx := range idxs { + writeUleb128(w, idx) + } + + case ACall: + switch p.To.Type { + case obj.TYPE_CONST: + writeUleb128(w, uint64(p.To.Offset)) + + case obj.TYPE_MEM: + if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC { + fmt.Println(p.To) + panic("bad name for Call") + } + r := obj.Addrel(s) + r.Siz = 1 // actually variable sized + r.Off = int32(w.Len()) + r.Type = objabi.R_CALL + if p.Mark&WasmImport != 0 { + r.Type = objabi.R_WASMIMPORT + } + r.Sym = p.To.Sym + if hasLocalSP { + // The stack may have moved, which changes SP. Update the local SP variable. + updateLocalSP(w) + } + + default: + panic("bad type for Call") + } + + case ACallIndirect: + writeUleb128(w, uint64(p.To.Offset)) + w.WriteByte(0x00) // reserved value + if hasLocalSP { + // The stack may have moved, which changes SP. Update the local SP variable. + updateLocalSP(w) + } + + case AI32Const, AI64Const: + if p.From.Name == obj.NAME_EXTERN { + r := obj.Addrel(s) + r.Siz = 1 // actually variable sized + r.Off = int32(w.Len()) + r.Type = objabi.R_ADDR + r.Sym = p.From.Sym + r.Add = p.From.Offset + break + } + writeSleb128(w, p.From.Offset) + + case AF32Const: + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64)))) + w.Write(b) + + case AF64Const: + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64))) + w.Write(b) + + case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: + if p.From.Offset < 0 { + panic("negative offset for *Load") + } + if p.From.Type != obj.TYPE_CONST { + panic("bad type for *Load") + } + if p.From.Offset > math.MaxUint32 { + ctxt.Diag("bad offset in %v", p) + } + writeUleb128(w, align(p.As)) + writeUleb128(w, uint64(p.From.Offset)) + + case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32: + if p.To.Offset < 0 { + panic("negative offset") + } + if p.From.Offset > math.MaxUint32 { + ctxt.Diag("bad offset in %v", p) + } + writeUleb128(w, align(p.As)) + writeUleb128(w, uint64(p.To.Offset)) + + case ACurrentMemory, AGrowMemory: + w.WriteByte(0x00) + + } + } + + w.WriteByte(0x0b) // end + + s.P = w.Bytes() +} + +func updateLocalSP(w *bytes.Buffer) { + writeOpcode(w, AGlobalGet) + writeUleb128(w, 0) // global SP + writeOpcode(w, ALocalSet) + writeUleb128(w, 1) // local SP +} + +func writeOpcode(w *bytes.Buffer, as obj.As) { + switch { + case as < AUnreachable: + panic(fmt.Sprintf("unexpected assembler op: %s", as)) + case as < AEnd: + w.WriteByte(byte(as - AUnreachable + 0x00)) + case as < ADrop: + w.WriteByte(byte(as - AEnd + 0x0B)) + case as < ALocalGet: + w.WriteByte(byte(as - ADrop + 0x1A)) + case as < AI32Load: + w.WriteByte(byte(as - ALocalGet + 0x20)) + case as < AI32TruncSatF32S: + w.WriteByte(byte(as - AI32Load + 0x28)) + case as < ALast: + w.WriteByte(0xFC) + w.WriteByte(byte(as - AI32TruncSatF32S + 0x00)) + default: + panic(fmt.Sprintf("unexpected assembler op: %s", as)) + } +} + +type valueType byte + +const ( + i32 valueType = 0x7F + i64 valueType = 0x7E + f32 valueType = 0x7D + f64 valueType = 0x7C +) + +func regType(reg int16) valueType { + switch { + case reg == REG_SP: + return i32 + case reg >= REG_R0 && reg <= REG_R15: + return i64 + case reg >= REG_F0 && reg <= REG_F15: + return f32 + case reg >= REG_F16 && reg <= REG_F31: + return f64 + default: + panic("invalid register") + } +} + +func align(as obj.As) uint64 { + switch as { + case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8: + return 0 + case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16: + return 1 + case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32: + return 2 + case AI64Load, AF64Load, AI64Store, AF64Store: + return 3 + default: + panic("align: bad op") + } +} + +func writeUleb128(w io.ByteWriter, v uint64) { + if v < 128 { + w.WriteByte(uint8(v)) + return + } + more := true + for more { + c := uint8(v & 0x7f) + v >>= 7 + more = v != 0 + if more { + c |= 0x80 + } + w.WriteByte(c) + } +} + +func writeSleb128(w io.ByteWriter, v int64) { + more := true + for more { + c := uint8(v & 0x7f) + s := uint8(v & 0x40) + v >>= 7 + more = !((v == 0 && s == 0) || (v == -1 && s != 0)) + if more { + c |= 0x80 + } + w.WriteByte(c) + } +} |