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