| // 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/ssa/block" | |
| "cmd/compile/internal/ssa/ssaop" | |
| "cmd/compile/internal/ssagen" | |
| "cmd/compile/internal/types" | |
| "cmd/internal/obj" | |
| "cmd/internal/obj/wasm" | |
| ) | |
| /* | |
| 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 1<<63+F<<16+B. | |
| (We also set the highest bit so a PC is distinct from a data address.) | |
| 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 block.BlockPlain, block.BlockDefer: | |
| if next != b.Succs[0].Block() { | |
| s.Br(obj.AJMP, b.Succs[0].Block()) | |
| } | |
| case block.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 block.BlockRet: | |
| s.Prog(obj.ARET) | |
| case block.BlockExit, block.BlockRetJmp: | |
| default: | |
| base.FatalfAt(b.Pos, "unexpected block b%d, kind=%v", b.ID, b.Kind) | |
| } | |
| // 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 ssaop.OpWasmLoweredStaticCall, ssaop.OpWasmLoweredClosureCall, ssaop.OpWasmLoweredInterCall, ssaop.OpWasmLoweredTailCall, ssaop.OpWasmLoweredTailCallInter: | |
| 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 == ssaop.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 == ssaop.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 | |
| if v.Op == ssaop.OpWasmLoweredTailCallInter { | |
| p.As = obj.ARET | |
| } | |
| } | |
| case ssaop.OpWasmLoweredMove: | |
| getValue32(s, v.Args[0]) | |
| getValue32(s, v.Args[1]) | |
| i32Const(s, int32(v.AuxInt)) | |
| s.Prog(wasm.AMemoryCopy) | |
| case ssaop.OpWasmLoweredZero: | |
| getValue32(s, v.Args[0]) | |
| i32Const(s, 0) | |
| i32Const(s, int32(v.AuxInt)) | |
| s.Prog(wasm.AMemoryFill) | |
| case ssaop.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 ssaop.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 ssaop.OpWasmI64Store8, ssaop.OpWasmI64Store16, ssaop.OpWasmI64Store32, ssaop.OpWasmI64Store, ssaop.OpWasmF32Store, ssaop.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 ssaop.OpWasmV128Store: | |
| getValue32(s, v.Args[0]) | |
| getValue128(s, v.Args[1]) | |
| p := s.Prog(v.Op.Asm()) | |
| p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt} | |
| case ssaop.OpStoreReg: | |
| getReg(s, wasm.REG_SP) | |
| if v.Type.Size() == 16 { | |
| getValue128(s, v.Args[0]) | |
| } else { | |
| getValue64(s, v.Args[0]) | |
| } | |
| p := s.Prog(storeOp(v.Type)) | |
| ssagen.AddrAuto(&p.To, v) | |
| case ssaop.OpClobber, ssaop.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 ssaop.OpWasmLoweredGetClosurePtr: | |
| getReg(s, wasm.REG_CTXT) | |
| case ssaop.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 ssaop.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 ssaop.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 ssaop.OpWasmLoweredConvert: | |
| getValue64(s, v.Args[0]) | |
| case ssaop.OpWasmSelect: | |
| getValue64(s, v.Args[0]) | |
| getValue64(s, v.Args[1]) | |
| getValue32(s, v.Args[2]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmSelectV: | |
| getValue128(s, v.Args[0]) | |
| getValue128(s, v.Args[1]) | |
| getValue32(s, v.Args[2]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmI64AddConst: | |
| getValue64(s, v.Args[0]) | |
| i64Const(s, v.AuxInt) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmI64Const: | |
| i64Const(s, v.AuxInt) | |
| case ssaop.OpWasmF32Const: | |
| f32Const(s, v.AuxFloat()) | |
| case ssaop.OpWasmF64Const: | |
| f64Const(s, v.AuxFloat()) | |
| case ssaop.OpWasmI64Load8U, ssaop.OpWasmI64Load8S, ssaop.OpWasmI64Load16U, ssaop.OpWasmI64Load16S, | |
| ssaop.OpWasmI64Load32U, ssaop.OpWasmI64Load32S, ssaop.OpWasmI64Load, ssaop.OpWasmF32Load, ssaop.OpWasmF64Load, ssaop.OpWasmV128Load: | |
| getValue32(s, v.Args[0]) | |
| p := s.Prog(v.Op.Asm()) | |
| p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt} | |
| case ssaop.OpWasmI64Eqz: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| if extend { | |
| s.Prog(wasm.AI64ExtendI32U) | |
| } | |
| case ssaop.OpWasmI64Eq, ssaop.OpWasmI64Ne, ssaop.OpWasmI64LtS, ssaop.OpWasmI64LtU, ssaop.OpWasmI64GtS, ssaop.OpWasmI64GtU, ssaop.OpWasmI64LeS, ssaop.OpWasmI64LeU, ssaop.OpWasmI64GeS, ssaop.OpWasmI64GeU, | |
| ssaop.OpWasmF32Eq, ssaop.OpWasmF32Ne, ssaop.OpWasmF32Lt, ssaop.OpWasmF32Gt, ssaop.OpWasmF32Le, ssaop.OpWasmF32Ge, | |
| ssaop.OpWasmF64Eq, ssaop.OpWasmF64Ne, ssaop.OpWasmF64Lt, ssaop.OpWasmF64Gt, ssaop.OpWasmF64Le, ssaop.OpWasmF64Ge: | |
| getValue64(s, v.Args[0]) | |
| getValue64(s, v.Args[1]) | |
| s.Prog(v.Op.Asm()) | |
| if extend { | |
| s.Prog(wasm.AI64ExtendI32U) | |
| } | |
| case ssaop.OpWasmI64Add, ssaop.OpWasmI64Sub, ssaop.OpWasmI64Mul, ssaop.OpWasmI64DivU, ssaop.OpWasmI64RemS, ssaop.OpWasmI64RemU, ssaop.OpWasmI64And, ssaop.OpWasmI64Or, ssaop.OpWasmI64Xor, ssaop.OpWasmI64Shl, ssaop.OpWasmI64ShrS, ssaop.OpWasmI64ShrU, ssaop.OpWasmI64Rotl, | |
| ssaop.OpWasmF32Add, ssaop.OpWasmF32Sub, ssaop.OpWasmF32Mul, ssaop.OpWasmF32Div, ssaop.OpWasmF32Copysign, | |
| ssaop.OpWasmF64Add, ssaop.OpWasmF64Sub, ssaop.OpWasmF64Mul, ssaop.OpWasmF64Div, ssaop.OpWasmF64Copysign: | |
| getValue64(s, v.Args[0]) | |
| getValue64(s, v.Args[1]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmI32Rotl: | |
| getValue32(s, v.Args[0]) | |
| getValue32(s, v.Args[1]) | |
| s.Prog(wasm.AI32Rotl) | |
| s.Prog(wasm.AI64ExtendI32U) | |
| case ssaop.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 ssaop.OpWasmI64TruncSatF32S, ssaop.OpWasmI64TruncSatF64S: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmI64TruncSatF32U, ssaop.OpWasmI64TruncSatF64U: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmF32DemoteF64: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmF64PromoteF32: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmF32ConvertI64S, ssaop.OpWasmF32ConvertI64U, | |
| ssaop.OpWasmF64ConvertI64S, ssaop.OpWasmF64ConvertI64U, | |
| ssaop.OpWasmI64Extend8S, ssaop.OpWasmI64Extend16S, ssaop.OpWasmI64Extend32S, | |
| ssaop.OpWasmF32Neg, ssaop.OpWasmF32Sqrt, ssaop.OpWasmF32Trunc, ssaop.OpWasmF32Ceil, ssaop.OpWasmF32Floor, ssaop.OpWasmF32Nearest, ssaop.OpWasmF32Abs, | |
| ssaop.OpWasmF64Neg, ssaop.OpWasmF64Sqrt, ssaop.OpWasmF64Trunc, ssaop.OpWasmF64Ceil, ssaop.OpWasmF64Floor, ssaop.OpWasmF64Nearest, ssaop.OpWasmF64Abs, | |
| ssaop.OpWasmI64Ctz, ssaop.OpWasmI64Clz, ssaop.OpWasmI64Popcnt: | |
| getValue64(s, v.Args[0]) | |
| s.Prog(v.Op.Asm()) | |
| case ssaop.OpWasmV128Zero: | |
| p := s.Prog(wasm.AV128Const) | |
| p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0} | |
| p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: 0} | |
| case ssaop.OpLoadReg: | |
| p := s.Prog(loadOp(v.Type)) | |
| ssagen.AddrAuto(&p.From, v.Args[0]) | |
| case ssaop.OpCopy: | |
| if v.Type.Size() == 16 { | |
| getValue128(s, v.Args[0]) | |
| } else { | |
| getValue64(s, v.Args[0]) | |
| } | |
| default: | |
| if !ssaGenSIMDValue(s, v, extend) { | |
| v.Fatalf("unexpected op: %s", v.Op) | |
| } | |
| } | |
| } | |
| func isAlready32(v *ssa.Value) bool { | |
| switch v.Op { | |
| case ssaop.OpWasmI64Eqz, ssaop.OpWasmI64Eq, ssaop.OpWasmI64Ne, ssaop.OpWasmI64LtS, ssaop.OpWasmI64LtU, ssaop.OpWasmI64GtS, ssaop.OpWasmI64GtU, ssaop.OpWasmI64LeS, ssaop.OpWasmI64LeU, ssaop.OpWasmI64GeS, ssaop.OpWasmI64GeU, | |
| ssaop.OpWasmF32Eq, ssaop.OpWasmF32Ne, ssaop.OpWasmF32Lt, ssaop.OpWasmF32Gt, ssaop.OpWasmF32Le, ssaop.OpWasmF32Ge, | |
| ssaop.OpWasmF64Eq, ssaop.OpWasmF64Ne, ssaop.OpWasmF64Lt, ssaop.OpWasmF64Gt, ssaop.OpWasmF64Le, ssaop.OpWasmF64Ge, | |
| ssaop.OpWasmI8x16ExtractLaneS, ssaop.OpWasmI16x8ExtractLaneS, ssaop.OpWasmI32x4ExtractLane, | |
| ssaop.OpWasmI8x16ExtractLaneU, ssaop.OpWasmI16x8ExtractLaneU: | |
| return true | |
| default: | |
| return false | |
| } | |
| } | |
| func getValue32(s *ssagen.State, v *ssa.Value) { | |
| if v.OnWasmStack { | |
| s.OnWasmStackSkipped-- | |
| ssaGenValueOnStack(s, v, false) | |
| if !isAlready32(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 getValue128(s *ssagen.State, v *ssa.Value) { | |
| if v.OnWasmStack { | |
| s.OnWasmStackSkipped-- | |
| ssaGenValueOnStack(s, v, true) | |
| return | |
| } | |
| reg := v.Reg() | |
| getReg(s, reg) | |
| } | |
| func getValueFxx(s *ssagen.State, v *ssa.Value) { | |
| if v.OnWasmStack { | |
| s.OnWasmStackSkipped-- | |
| ssaGenValueOnStack(s, v, true) | |
| return | |
| } | |
| reg := v.Reg() | |
| getReg(s, reg) | |
| } | |
| 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 | |
| case 16: | |
| return wasm.AV128Load | |
| 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 | |
| case 16: | |
| return wasm.AV128Store | |
| default: | |
| panic("bad store type") | |
| } | |
| } |