diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/compile/internal/wasm | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/compile/internal/wasm')
-rw-r--r-- | src/cmd/compile/internal/wasm/ssa.go | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/wasm/ssa.go b/src/cmd/compile/internal/wasm/ssa.go new file mode 100644 index 0000000..85f34a7 --- /dev/null +++ b/src/cmd/compile/internal/wasm/ssa.go @@ -0,0 +1,623 @@ +// 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/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/logopt" + "cmd/compile/internal/objw" + "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/obj/wasm" + "internal/buildcfg" +) + +/* + + Wasm implementation + ------------------- + + Wasm is a strange Go port because the machine isn't + a register-based machine, threads are different, code paths + are different, etc. We outline those differences here. + + See the design doc for some additional info on this topic. + https://docs.google.com/document/d/131vjr4DH6JFnb-blm_uRdaC0_Nv3OUwjEY5qVCxCup4/edit#heading=h.mjo1bish3xni + + PCs: + + Wasm doesn't have PCs in the normal sense that you can jump + to or call to. Instead, we simulate these PCs using our own construct. + + A PC in the Wasm implementation is the combination of a function + ID and a block ID within that function. The function ID is an index + into a function table which transfers control to the start of the + function in question, and the block ID is a sequential integer + indicating where in the function we are. + + Every function starts with a branch table which transfers control + to the place in the function indicated by the block ID. The block + ID is provided to the function as the sole Wasm argument. + + Block IDs do not encode every possible PC. They only encode places + in the function where it might be suspended. Typically these places + are call sites. + + Sometimes we encode the function ID and block ID separately. When + recorded together as a single integer, we use the value F<<16+B. + + Threads: + + Wasm doesn't (yet) have threads. We have to simulate threads by + keeping goroutine stacks in linear memory and unwinding + the Wasm stack each time we want to switch goroutines. + + To support unwinding a stack, each function call returns on the Wasm + stack a boolean that tells the function whether it should return + immediately or not. When returning immediately, a return address + is left on the top of the Go stack indicating where the goroutine + should be resumed. + + Stack pointer: + + There is a single global stack pointer which records the stack pointer + used by the currently active goroutine. This is just an address in + linear memory where the Go runtime is maintaining the stack for that + goroutine. + + Functions cache the global stack pointer in a local variable for + faster access, but any changes must be spilled to the global variable + before any call and restored from the global variable after any call. + + Calling convention: + + All Go arguments and return values are passed on the Go stack, not + the wasm stack. In addition, return addresses are pushed on the + Go stack at every call point. Return addresses are not used during + normal execution, they are used only when resuming goroutines. + (So they are not really a "return address", they are a "resume address".) + + All Go functions have the Wasm type (i32)->i32. The argument + is the block ID and the return value is the exit immediately flag. + + Callsite: + - write arguments to the Go stack (starting at SP+0) + - push return address to Go stack (8 bytes) + - write local SP to global SP + - push 0 (type i32) to Wasm stack + - issue Call + - restore local SP from global SP + - pop int32 from top of Wasm stack. If nonzero, exit function immediately. + - use results from Go stack (starting at SP+sizeof(args)) + - note that the callee will have popped the return address + + Prologue: + - initialize local SP from global SP + - jump to the location indicated by the block ID argument + (which appears in local variable 0) + - at block 0 + - check for Go stack overflow, call morestack if needed + - subtract frame size from SP + - note that arguments now start at SP+framesize+8 + + Normal epilogue: + - pop frame from Go stack + - pop return address from Go stack + - push 0 (type i32) on the Wasm stack + - return + Exit immediately epilogue: + - push 1 (type i32) on the Wasm stack + - return + - note that the return address and stack frame are left on the Go stack + + The main loop that executes goroutines is wasm_pc_f_loop, in + runtime/rt0_js_wasm.s. It grabs the saved return address from + the top of the Go stack (actually SP-8?), splits it up into F + and B parts, then calls F with its Wasm argument set to B. + + Note that when resuming a goroutine, only the most recent function + invocation of that goroutine appears on the Wasm stack. When that + Wasm function returns normally, the next most recent frame will + then be started up by wasm_pc_f_loop. + + Global 0 is SP (stack pointer) + Global 1 is CTXT (closure pointer) + Global 2 is GP (goroutine pointer) +*/ + +func Init(arch *ssagen.ArchInfo) { + arch.LinkArch = &wasm.Linkwasm + arch.REGSP = wasm.REG_SP + arch.MAXWIDTH = 1 << 50 + + arch.ZeroRange = zeroRange + arch.Ginsnop = ginsnop + + arch.SSAMarkMoves = ssaMarkMoves + arch.SSAGenValue = ssaGenValue + arch.SSAGenBlock = ssaGenBlock +} + +func zeroRange(pp *objw.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog { + if cnt == 0 { + return p + } + if cnt%8 != 0 { + base.Fatalf("zerorange count not a multiple of widthptr %d", cnt) + } + + for i := int64(0); i < cnt; i += 8 { + p = pp.Append(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0) + p = pp.Append(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0) + p = pp.Append(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i) + } + + return p +} + +func ginsnop(pp *objw.Progs) *obj.Prog { + return pp.Prog(wasm.ANop) +} + +func ssaMarkMoves(s *ssagen.State, b *ssa.Block) { +} + +func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { + switch b.Kind { + case ssa.BlockPlain: + if next != b.Succs[0].Block() { + s.Br(obj.AJMP, b.Succs[0].Block()) + } + + case ssa.BlockIf: + switch next { + case b.Succs[0].Block(): + // if false, jump to b.Succs[1] + getValue32(s, b.Controls[0]) + s.Prog(wasm.AI32Eqz) + s.Prog(wasm.AIf) + s.Br(obj.AJMP, b.Succs[1].Block()) + s.Prog(wasm.AEnd) + case b.Succs[1].Block(): + // if true, jump to b.Succs[0] + getValue32(s, b.Controls[0]) + s.Prog(wasm.AIf) + s.Br(obj.AJMP, b.Succs[0].Block()) + s.Prog(wasm.AEnd) + default: + // if true, jump to b.Succs[0], else jump to b.Succs[1] + getValue32(s, b.Controls[0]) + s.Prog(wasm.AIf) + s.Br(obj.AJMP, b.Succs[0].Block()) + s.Prog(wasm.AEnd) + s.Br(obj.AJMP, b.Succs[1].Block()) + } + + case ssa.BlockRet: + s.Prog(obj.ARET) + + case ssa.BlockExit, ssa.BlockRetJmp: + + 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) + s.Br(obj.AJMP, b.Succs[1].Block()) + s.Prog(wasm.AEnd) + if next != b.Succs[0].Block() { + s.Br(obj.AJMP, b.Succs[0].Block()) + } + + 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 *ssagen.State, v *ssa.Value) { + switch v.Op { + case ssa.OpWasmLoweredStaticCall, ssa.OpWasmLoweredClosureCall, ssa.OpWasmLoweredInterCall, ssa.OpWasmLoweredTailCall: + s.PrepareCall(v) + if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn == ir.Syms.Deferreturn { + // The runtime needs to inject jumps to + // deferreturn calls using the address in + // _func.deferreturn. Hence, the call to + // deferreturn must itself be a resumption + // point so it gets a target PC. + s.Prog(wasm.ARESUMEPOINT) + } + if v.Op == ssa.OpWasmLoweredClosureCall { + getValue64(s, v.Args[1]) + setReg(s, wasm.REG_CTXT) + } + if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn != nil { + sym := call.Fn + p := s.Prog(obj.ACALL) + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym} + p.Pos = v.Pos + if v.Op == ssa.OpWasmLoweredTailCall { + p.As = obj.ARET + } + } 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)) + s.Prog(wasm.AMemoryCopy) + + case ssa.OpWasmLoweredZero: + getValue32(s, v.Args[0]) + i32Const(s, 0) + i32Const(s, int32(v.AuxInt)) + s.Prog(wasm.AMemoryFill) + + 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: ir.Syms.SigPanic} + s.Prog(wasm.AEnd) + if logopt.Enabled() { + logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name) + } + if base.Debug.Nil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers + base.WarnfAt(v.Pos, "generated nil check") + } + + case ssa.OpWasmLoweredWB: + p := s.Prog(wasm.ACall) + // AuxInt encodes how many buffer entries we need. + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.GCWriteBarrier[v.AuxInt-1]} + setReg(s, v.Reg0()) // move result from wasm stack to register local + + case ssa.OpWasmI64Store8, ssa.OpWasmI64Store16, ssa.OpWasmI64Store32, ssa.OpWasmI64Store, ssa.OpWasmF32Store, ssa.OpWasmF64Store: + getValue32(s, v.Args[0]) + getValue64(s, v.Args[1]) + 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]) + p := s.Prog(storeOp(v.Type)) + ssagen.AddrAuto(&p.To, v) + + case ssa.OpClobber, ssa.OpClobberReg: + // TODO: implement for clobberdead experiment. Nop is ok for now. + + 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, true) + if s.OnWasmStackSkipped != 0 { + panic("wasm: bad stack") + } + setReg(s, v.Reg()) + } +} + +func ssaGenValueOnStack(s *ssagen.State, v *ssa.Value, extend bool) { + 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: + if v.Aux == nil { // address of off(SP), no symbol + getValue64(s, v.Args[0]) + i64Const(s, v.AuxInt) + s.Prog(wasm.AI64Add) + break + } + p := s.Prog(wasm.AGet) + p.From.Type = obj.TYPE_ADDR + switch v.Aux.(type) { + case *obj.LSym: + ssagen.AddAux(&p.From, v) + case *ir.Name: + p.From.Reg = v.Args[0].Reg() + ssagen.AddAux(&p.From, v) + default: + panic("wasm: bad LoweredAddr") + } + + case ssa.OpWasmLoweredConvert: + getValue64(s, v.Args[0]) + + case ssa.OpWasmSelect: + getValue64(s, v.Args[0]) + getValue64(s, v.Args[1]) + getValue32(s, v.Args[2]) + 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.OpWasmF32Const: + f32Const(s, v.AuxFloat()) + + 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} + + case ssa.OpWasmI64Eqz: + getValue64(s, v.Args[0]) + s.Prog(v.Op.Asm()) + if extend { + 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.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge, + 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()) + if extend { + 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.OpWasmI64Rotl, + ssa.OpWasmF32Add, ssa.OpWasmF32Sub, ssa.OpWasmF32Mul, ssa.OpWasmF32Div, ssa.OpWasmF32Copysign, + ssa.OpWasmF64Add, ssa.OpWasmF64Sub, ssa.OpWasmF64Mul, ssa.OpWasmF64Div, ssa.OpWasmF64Copysign: + getValue64(s, v.Args[0]) + getValue64(s, v.Args[1]) + s.Prog(v.Op.Asm()) + + case ssa.OpWasmI32Rotl: + getValue32(s, v.Args[0]) + getValue32(s, v.Args[1]) + s.Prog(wasm.AI32Rotl) + s.Prog(wasm.AI64ExtendI32U) + + 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: ir.Syms.WasmDiv} + break + } + s.Prog(wasm.AI64DivS) + + case ssa.OpWasmI64TruncSatF32S, ssa.OpWasmI64TruncSatF64S: + getValue64(s, v.Args[0]) + if buildcfg.GOWASM.SatConv { + s.Prog(v.Op.Asm()) + } else { + if v.Op == ssa.OpWasmI64TruncSatF32S { + s.Prog(wasm.AF64PromoteF32) + } + p := s.Prog(wasm.ACall) + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncS} + } + + case ssa.OpWasmI64TruncSatF32U, ssa.OpWasmI64TruncSatF64U: + getValue64(s, v.Args[0]) + if buildcfg.GOWASM.SatConv { + s.Prog(v.Op.Asm()) + } else { + if v.Op == ssa.OpWasmI64TruncSatF32U { + s.Prog(wasm.AF64PromoteF32) + } + p := s.Prog(wasm.ACall) + p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncU} + } + + case ssa.OpWasmF32DemoteF64: + getValue64(s, v.Args[0]) + s.Prog(v.Op.Asm()) + + case ssa.OpWasmF64PromoteF32: + getValue64(s, v.Args[0]) + s.Prog(v.Op.Asm()) + + case ssa.OpWasmF32ConvertI64S, ssa.OpWasmF32ConvertI64U, + ssa.OpWasmF64ConvertI64S, ssa.OpWasmF64ConvertI64U, + ssa.OpWasmI64Extend8S, ssa.OpWasmI64Extend16S, ssa.OpWasmI64Extend32S, + ssa.OpWasmF32Neg, ssa.OpWasmF32Sqrt, ssa.OpWasmF32Trunc, ssa.OpWasmF32Ceil, ssa.OpWasmF32Floor, ssa.OpWasmF32Nearest, ssa.OpWasmF32Abs, + ssa.OpWasmF64Neg, 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)) + ssagen.AddrAuto(&p.From, v.Args[0]) + + case ssa.OpCopy: + getValue64(s, v.Args[0]) + + default: + v.Fatalf("unexpected op: %s", v.Op) + + } +} + +func isCmp(v *ssa.Value) bool { + switch v.Op { + case ssa.OpWasmI64Eqz, ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU, + ssa.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge, + ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge: + return true + default: + return false + } +} + +func getValue32(s *ssagen.State, v *ssa.Value) { + if v.OnWasmStack { + s.OnWasmStackSkipped-- + ssaGenValueOnStack(s, v, false) + if !isCmp(v) { + s.Prog(wasm.AI32WrapI64) + } + return + } + + reg := v.Reg() + getReg(s, reg) + if reg != wasm.REG_SP { + s.Prog(wasm.AI32WrapI64) + } +} + +func getValue64(s *ssagen.State, v *ssa.Value) { + if v.OnWasmStack { + s.OnWasmStackSkipped-- + ssaGenValueOnStack(s, v, true) + return + } + + reg := v.Reg() + getReg(s, reg) + if reg == wasm.REG_SP { + s.Prog(wasm.AI64ExtendI32U) + } +} + +func i32Const(s *ssagen.State, val int32) { + p := s.Prog(wasm.AI32Const) + p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(val)} +} + +func i64Const(s *ssagen.State, val int64) { + p := s.Prog(wasm.AI64Const) + p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: val} +} + +func f32Const(s *ssagen.State, val float64) { + p := s.Prog(wasm.AF32Const) + p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val} +} + +func f64Const(s *ssagen.State, val float64) { + p := s.Prog(wasm.AF64Const) + p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val} +} + +func getReg(s *ssagen.State, reg int16) { + p := s.Prog(wasm.AGet) + p.From = obj.Addr{Type: obj.TYPE_REG, Reg: reg} +} + +func setReg(s *ssagen.State, 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") + } +} |