diff options
Diffstat (limited to 'vendor/github.com/twitchyliquid64/golang-asm/obj/wasm')
3 files changed, 0 insertions, 1724 deletions
diff --git a/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/a.out.go b/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/a.out.go deleted file mode 100644 index 43a79db71..000000000 --- a/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/a.out.go +++ /dev/null @@ -1,331 +0,0 @@ -// 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 "github.com/twitchyliquid64/golang-asm/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/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/anames.go b/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/anames.go deleted file mode 100644 index 1906810e3..000000000 --- a/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/anames.go +++ /dev/null @@ -1,208 +0,0 @@ -// Code generated by stringer -i a.out.go -o anames.go -p wasm; DO NOT EDIT. - -package wasm - -import "github.com/twitchyliquid64/golang-asm/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/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/wasmobj.go b/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/wasmobj.go deleted file mode 100644 index f7e4e33a7..000000000 --- a/vendor/github.com/twitchyliquid64/golang-asm/obj/wasm/wasmobj.go +++ /dev/null @@ -1,1185 +0,0 @@ -// 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" - "github.com/twitchyliquid64/golang-asm/obj" - "github.com/twitchyliquid64/golang-asm/objabi" - "github.com/twitchyliquid64/golang-asm/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 - sigpanic0 *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.Lookup("runtime.gcWriteBarrier") - sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) - sigpanic0 = ctxt.LookupABI("runtime.sigpanic", 0) // sigpanic called from assembly, which has ABI0 - 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 && call.To.Sym != sigpanic0 { // 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.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.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) - } -} |