Файловый менеджер - Редактировать - /var/www/html/wasm.zip
Ðазад
PK ! ���LF LF ssa.gonu �[��� // 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") } } PK ! tr`] ] a.out.gonu �[��� // 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 ( AGet = obj.ABaseWasm + obj.A_ARCHSPECIFIC + iota 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 AMemoryInit ADataDrop AMemoryCopy AMemoryFill ATableInit AElemDrop ATableCopy ATableGrow ATableSize ATableFill 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 ) PK ! ��/�d� d� wasmobj.gonu �[��� // 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" "internal/abi" "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, 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 sigpanic *obj.LSym ) const ( /* mark flags */ WasmImport = 1 << 0 ) const ( // This is a special wasm module name that when used as the module name // in //go:wasmimport will cause the generated code to pass the stack pointer // directly to the imported function. In other words, any function that // uses the gojs module understands the internal Go WASM ABI directly. GojsModule = "gojs" ) func instinit(ctxt *obj.Link) { morestack = ctxt.Lookup("runtime.morestack") morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") sigpanic = ctxt.LookupABI("runtime.sigpanic", 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 the function exits just to call out to a wasmimport, then // generate the code to translate from our internal Go-stack // based call convention to the native webassembly call convention. if wi := s.Func().WasmImport; wi != nil { s.Func().WasmImportSym = wi.CreateSym(ctxt) p := s.Func().Text if p.Link != nil { panic("wrapper functions for WASM imports should not have a body") } to := obj.Addr{ Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s, } // If the module that the import is for is our magic "gojs" module, then this // indicates that the called function understands the Go stack-based call convention // so we just pass the stack pointer to it, knowing it will read the params directly // off the stack and push the results into memory based on the stack pointer. if wi.Module == GojsModule { // The called function has a signature of 'func(sp int)'. It has access to the memory // value somewhere to be able to address the memory based on the "sp" value. p = appendp(p, AGet, regAddr(REG_SP)) p = appendp(p, ACall, to) p.Mark = WasmImport } else { if len(wi.Results) > 1 { // TODO(evanphx) implement support for the multi-value proposal: // https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md panic("invalid results type") // impossible until multi-value proposal has landed } if len(wi.Results) == 1 { // If we have a result (rather than returning nothing at all), then // we'll write the result to the Go stack relative to the current stack pointer. // We cache the current stack pointer value on the wasm stack here and then use // it after the Call instruction to store the result. p = appendp(p, AGet, regAddr(REG_SP)) } for _, f := range wi.Params { // Each load instructions will consume the value of sp on the stack, so // we need to read sp for each param. WASM appears to not have a stack dup instruction // (a strange omission for a stack-based VM), if it did, we'd be using the dup here. p = appendp(p, AGet, regAddr(REG_SP)) // Offset is the location of the param on the Go stack (ie relative to sp). // Because of our call convention, the parameters are located an additional 8 bytes // from sp because we store the return address as an int64 at the bottom of the stack. // Ie the stack looks like [return_addr, param3, param2, param1, etc] // Ergo, we add 8 to the true byte offset of the param to skip the return address. loadOffset := f.Offset + 8 // We're reading the value from the Go stack onto the WASM stack and leaving it there // for CALL to pick them up. switch f.Type { case obj.WasmI32: p = appendp(p, AI32Load, constAddr(loadOffset)) case obj.WasmI64: p = appendp(p, AI64Load, constAddr(loadOffset)) case obj.WasmF32: p = appendp(p, AF32Load, constAddr(loadOffset)) case obj.WasmF64: p = appendp(p, AF64Load, constAddr(loadOffset)) case obj.WasmPtr: p = appendp(p, AI64Load, constAddr(loadOffset)) p = appendp(p, AI32WrapI64) default: panic("bad param type") } } // The call instruction is marked as being for a wasm import so that a later phase // will generate relocation information that allows us to patch this with then // offset of the imported function in the wasm imports. p = appendp(p, ACall, to) p.Mark = WasmImport if len(wi.Results) == 1 { f := wi.Results[0] // Much like with the params, we need to adjust the offset we store the result value // to by 8 bytes to account for the return address on the Go stack. storeOffset := f.Offset + 8 // This code is paired the code above that reads the stack pointer onto the wasm // stack. We've done this so we have a consistent view of the sp value as it might // be manipulated by the call and we want to ignore that manipulation here. switch f.Type { case obj.WasmI32: p = appendp(p, AI32Store, constAddr(storeOffset)) case obj.WasmI64: p = appendp(p, AI64Store, constAddr(storeOffset)) case obj.WasmF32: p = appendp(p, AF32Store, constAddr(storeOffset)) case obj.WasmF64: p = appendp(p, AF64Store, constAddr(storeOffset)) case obj.WasmPtr: p = appendp(p, AI64ExtendI32U) p = appendp(p, AI64Store, constAddr(storeOffset)) default: panic("bad result type") } } } p = appendp(p, obj.ARET) // It should be 0 already, but we'll set it to 0 anyway just to be sure // that the code below which adds frame expansion code to the function body // isn't run. We don't want the frame expansion code because our function // body is just the code to translate and call the imported function. framesize = 0 } else 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) } // If the framesize is 0, then imply nosplit because it's a specially // generated function. needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit() // If the maymorestack debug option is enabled, insert the // call to maymorestack *before* processing resume points so // we can construct a resume point after maymorestack for // morestack to resume at. var pMorestack = s.Func().Text if needMoreStack && ctxt.Flag_maymorestack != "" { p := pMorestack // Save REGCTXT on the stack. const tempFrame = 8 p = appendp(p, AGet, regAddr(REG_SP)) p = appendp(p, AI32Const, constAddr(tempFrame)) p = appendp(p, AI32Sub) p = appendp(p, ASet, regAddr(REG_SP)) p.Spadj = tempFrame ctxtp := obj.Addr{ Type: obj.TYPE_MEM, Reg: REG_SP, Offset: 0, } p = appendp(p, AMOVD, regAddr(REGCTXT), ctxtp) // maymorestack must not itself preempt because we // don't have full stack information, so this can be // ACALLNORESUME. p = appendp(p, ACALLNORESUME, constAddr(0)) // See ../x86/obj6.go sym := ctxt.LookupABI(ctxt.Flag_maymorestack, s.ABI()) p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym} // Restore REGCTXT. p = appendp(p, AMOVD, ctxtp, regAddr(REGCTXT)) p = appendp(p, AGet, regAddr(REG_SP)) p = appendp(p, AI32Const, constAddr(tempFrame)) p = appendp(p, AI32Add) p = appendp(p, ASet, regAddr(REG_SP)) p.Spadj = -tempFrame // Add an explicit ARESUMEPOINT after maymorestack for // morestack to resume at. pMorestack = appendp(p, ARESUMEPOINT) } // 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 needMoreStack { p := pMorestack if framesize <= abi.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(framesize-abi.StackSmall)) p = appendp(p, AI32Add) p = appendp(p, AI32LeU) } // TODO(neelance): handle wraparound case p = appendp(p, AIf) // This CALL does *not* have a resume point after it // (we already inserted all of the resume points). As // a result, morestack will resume at the *previous* // resume point (typically, the beginning of the // function) and perform the morestack check again. // This is why we don't need an explicit loop like // other architectures. 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 } // 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") } // 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) } 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 += framesize case obj.NAME_PARAM: p.From.Reg = REG_SP p.From.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address } switch p.To.Name { case obj.NAME_AUTO: p.To.Offset += framesize case obj.NAME_PARAM: p.To.Reg = REG_SP p.To.Offset += 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") } } } { 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, "_rt0_wasm_wasip1": true, "wasm_export_run": true, "wasm_export_resume": true, "wasm_export_getsp": true, "wasm_pc_f_loop": true, "gcWriteBarrier": true, "runtime.gcWriteBarrier1": true, "runtime.gcWriteBarrier2": true, "runtime.gcWriteBarrier3": true, "runtime.gcWriteBarrier4": true, "runtime.gcWriteBarrier5": true, "runtime.gcWriteBarrier6": true, "runtime.gcWriteBarrier7": true, "runtime.gcWriteBarrier8": true, "runtime.wasmDiv": true, "runtime.wasmTruncS": true, "runtime.wasmTruncU": 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", "_rt0_wasm_wasip1", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop", "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 "gcWriteBarrier": varDecls = []*varDecl{{count: 5, typ: i64}} useAssemblyRegMap() case "runtime.gcWriteBarrier1", "runtime.gcWriteBarrier2", "runtime.gcWriteBarrier3", "runtime.gcWriteBarrier4", "runtime.gcWriteBarrier5", "runtime.gcWriteBarrier6", "runtime.gcWriteBarrier7", "runtime.gcWriteBarrier8": // no locals 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, AMemoryFill: w.WriteByte(0x00) case AMemoryCopy: w.WriteByte(0x00) 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) } } PK ! BG G anames.gonu �[��� // 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: "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", "MemoryInit", "DataDrop", "MemoryCopy", "MemoryFill", "TableInit", "ElemDrop", "TableCopy", "TableGrow", "TableSize", "TableFill", "Last", "RESUMEPOINT", "CALLNORESUME", "RETUNWIND", "MOVB", "MOVH", "MOVW", "MOVD", "WORD", "LAST", } PK ! ���LF LF ssa.gonu �[��� PK ! tr`] ] �F a.out.gonu �[��� PK ! ��/�d� d� X wasmobj.gonu �[��� PK ! BG G �� anames.gonu �[��� PK ! 5�
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка