Repository: tetratelabs/wazero
Pull request #1912 (2024-01-11 19:29:33)

wazevo(amd64): makes empty function working


Patch:
diff --git a/internal/engine/wazevo/backend/compiler.go b/internal/engine/wazevo/backend/compiler.go
index cca9e52c..256ec3d0 100644
--- a/internal/engine/wazevo/backend/compiler.go
+++ b/internal/engine/wazevo/backend/compiler.go
@@ -2,7 +2,6 @@ package backend
 
 import (
 	"context"
-	"encoding/hex"
 	"fmt"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
@@ -51,15 +50,15 @@ type Compiler interface {
 	// RegAlloc performs the register allocation after Lower is called.
 	RegAlloc()
 
-	// Finalize performs the finalization of the compilation. This must be called after RegAlloc.
+	// Finalize performs the finalization of the compilation, including machine code emission.
+	// This must be called after RegAlloc.
 	Finalize(ctx context.Context)
 
-	// Encode encodes the machine code to the buffer.
-	Encode()
-
 	// Buf returns the buffer of the encoded machine code. This is only used for testing purpose.
 	Buf() []byte
 
+	BufPtr() *[]byte
+
 	// Format returns the debug string of the current state of the compiler.
 	Format() string
 
@@ -183,10 +182,6 @@ func (c *compiler) Compile(ctx context.Context) ([]byte, []RelocationInfo, error
 	if wazevoapi.DeterministicCompilationVerifierEnabled {
 		wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "After Finalization", c.Format())
 	}
-	c.Encode()
-	if wazevoapi.DeterministicCompilationVerifierEnabled {
-		wazevoapi.VerifyOrSetDeterministicCompilationContextValue(ctx, "Encoded Machine code", hex.EncodeToString(c.buf))
-	}
 	return c.buf, c.relocations, nil
 }
 
@@ -199,12 +194,7 @@ func (c *compiler) RegAlloc() {
 func (c *compiler) Finalize(ctx context.Context) {
 	c.mach.SetupPrologue()
 	c.mach.SetupEpilogue()
-	c.mach.ResolveRelativeAddresses(ctx)
-}
-
-// Encode implements Compiler.Encode.
-func (c *compiler) Encode() {
-	c.mach.Encode()
+	c.mach.Encode(ctx)
 }
 
 // setCurrentGroupID sets the current instruction group ID.
@@ -406,6 +396,11 @@ func (c *compiler) Buf() []byte {
 	return c.buf
 }
 
+// BufPtr implements Compiler.BufPtr.
+func (c *compiler) BufPtr() *[]byte {
+	return &c.buf
+}
+
 func (c *compiler) GetFunctionABI(sig *ssa.Signature) *FunctionABI {
 	if int(sig.ID) >= len(c.abis) {
 		c.abis = append(c.abis, make([]FunctionABI, int(sig.ID)+1)...)
diff --git a/internal/engine/wazevo/backend/compiler_lower.go b/internal/engine/wazevo/backend/compiler_lower.go
index e59f8675..165786a9 100644
--- a/internal/engine/wazevo/backend/compiler_lower.go
+++ b/internal/engine/wazevo/backend/compiler_lower.go
@@ -68,7 +68,10 @@ func (c *compiler) lowerBlock(blk ssa.BasicBlock) {
 
 		switch cur.Opcode() {
 		case ssa.OpcodeReturn:
-			c.lowerFunctionReturns(cur.ReturnVals())
+			rets := cur.ReturnVals()
+			if len(rets) > 0 {
+				c.mach.LowerReturns(rets)
+			}
 			c.mach.InsertReturn()
 		default:
 			mach.LowerInstr(cur)
@@ -108,7 +111,9 @@ func (c *compiler) lowerBranches(br0, br1 *ssa.Instruction) {
 			panic("BUG: critical edge split failed")
 		}
 		if argExists && target.ReturnBlock() {
-			c.lowerFunctionReturns(args)
+			if len(args) > 0 {
+				c.mach.LowerReturns(args)
+			}
 		} else if argExists {
 			c.lowerBlockArguments(args, target)
 		}
@@ -133,10 +138,6 @@ func (c *compiler) lowerFunctionArguments(entry ssa.BasicBlock) {
 	ectx.FlushPendingInstructions()
 }
 
-func (c *compiler) lowerFunctionReturns(returns []ssa.Value) {
-	c.mach.LowerReturns(returns)
-}
-
 // lowerBlockArguments lowers how to pass arguments to the given successor block.
 func (c *compiler) lowerBlockArguments(args []ssa.Value, succ ssa.BasicBlock) {
 	if len(args) != succ.Params() {
diff --git a/internal/engine/wazevo/backend/executable_context.go b/internal/engine/wazevo/backend/executable_context.go
index a1a9658c..81c6a6b6 100644
--- a/internal/engine/wazevo/backend/executable_context.go
+++ b/internal/engine/wazevo/backend/executable_context.go
@@ -199,7 +199,6 @@ type LabelPosition[Instr any] struct {
 	SB           ssa.BasicBlock
 	L            Label
 	Begin, End   *Instr
-	BinarySize   int64
 	BinaryOffset int64
 }
 
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi.go b/internal/engine/wazevo/backend/isa/amd64/abi.go
index 19a29824..89bf93fb 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi.go
@@ -1,6 +1,7 @@
 package amd64
 
 import (
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
@@ -52,9 +53,21 @@ func (m *machine) ArgsResultsRegs() (argResultInts, argResultFloats []regalloc.R
 }
 
 // LowerParams implements backend.Machine.
-func (m *machine) LowerParams(params []ssa.Value) {
-	// TODO implement me
-	panic("implement me")
+func (m *machine) LowerParams(args []ssa.Value) {
+	a := m.currentABI
+
+	for i, ssaArg := range args {
+		if !ssaArg.Valid() {
+			continue
+		}
+		reg := m.c.VRegOf(ssaArg)
+		arg := &a.Args[i]
+		if arg.Kind == backend.ABIArgKindReg {
+			m.InsertMove(reg, arg.Reg, arg.Type)
+		} else {
+			panic("TODO: stack param")
+		}
+	}
 }
 
 // LowerReturns implements backend.Machine.
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go
new file mode 100644
index 00000000..989292b4
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go
@@ -0,0 +1,9 @@
+package amd64
+
+// entrypoint enters the machine code generated by this backend which begins with the preamble generated by functionABI.EmitGoEntryPreamble below.
+// This implements wazevo.entrypoint, and see the comments there for detail.
+func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultPtr *uint64, goAllocatedStackSlicePtr uintptr)
+
+// afterGoFunctionCallEntrypoint enters the machine code after growing the stack.
+// This implements wazevo.afterGoFunctionCallEntrypoint, and see the comments there for detail.
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer uintptr)
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
new file mode 100644
index 00000000..fb56a46e
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
@@ -0,0 +1,15 @@
+#include "funcdata.h"
+#include "textflag.h"
+
+// entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultPtr *uint64, goAllocatedStackSlicePtr uintptr
+TEXT ·entrypoint(SB), NOSPLIT|NOFRAME, $0-48
+	MOVQ preambleExecutable+0(FP), R11
+	MOVQ functionExectuable+8(FP), DI
+	MOVQ executionContextPtr+16(FP), AX      // First argument is passed in AX.
+	MOVQ moduleContextPtr+24(FP), CX         // Second argument is passed in CX.
+	MOVQ paramResultSlicePtr+32(FP), SI
+	MOVQ goAllocatedStackSlicePtr+40(FP), BX
+	JMP  R11
+
+TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-24
+	UD2 // TODO!
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
new file mode 100644
index 00000000..2cfea86f
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
@@ -0,0 +1,102 @@
+package amd64
+
+import (
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
+)
+
+var (
+	executionContextPtrReg = raxVReg
+	// savedExecutionContextPtr also must be a callee-saved reg so that they can be used in the prologue and epilogue.
+	savedExecutionContextPtr = rdxVReg
+	// paramResultSlicePtr must be callee-saved reg so that they can be used in the prologue and epilogue.
+	paramResultSlicePtr = r12VReg //nolint
+	// goAllocatedStackPtr is not used in the epilogue.
+	goAllocatedStackPtr = rbxVReg
+	// paramResultSliceCopied is not used in the epilogue.
+	paramResultSliceCopied = rsiVReg //nolint
+	// functionExecutable is not used in the epilogue.
+	functionExecutable = rdiVReg
+)
+
+// CompileEntryPreamble implements backend.Machine.
+func (m *machine) CompileEntryPreamble(sig *ssa.Signature) []byte {
+	root := m.compileEntryPreamble(sig)
+	m.encodeWithoutRelResolution(root)
+	buf := m.c.Buf()
+	return buf
+}
+
+func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction {
+	abi := backend.FunctionABI{}
+	abi.Init(sig, intArgResultRegs, floatArgResultRegs)
+
+	root := m.allocateNop()
+
+	//// ----------------------------------- prologue ----------------------------------- ////
+
+	// First, we save executionContextPtrReg into a callee-saved register so that it can be used in epilogue as well.
+	// 		mov %executionContextPtrReg, %savedExecutionContextPtr
+	cur := m.move64(executionContextPtrReg, savedExecutionContextPtr, root)
+
+	// Next is to save the original RBP and RSP into the execution context.
+	// 		mov %rbp, wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg)
+	// 		mov %rsp, wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg)
+	cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, true, cur)
+	cur = m.loadOrStore64AtExecutionCtx(executionContextPtrReg, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, true, cur)
+
+	// Now set the RSP to the Go-allocated stack pointer.
+	// 		mov %goAllocatedStackPtr, %rsp
+	cur = m.move64(goAllocatedStackPtr, rspVReg, cur)
+
+	// TODO: read the arguments from paramResultSliceCopied and set them correctly into the regs and stacks.
+	// 	The first two args are module context ptr and execution context ptr, and they are passed in %rax and %rcx.
+	if len(abi.Args) > 2 {
+		panic("TODO")
+	}
+
+	// Now ready to call the real function. Note that at this point stack pointer is already set to the Go-allocated,
+	// which is aligned to 16 bytes.
+	call := m.allocateInstr().asCallIndirect(newOperandReg(functionExecutable), &abi)
+	cur = linkInstr(cur, call)
+
+	//// ----------------------------------- epilogue ----------------------------------- ////
+
+	// TODO: read the results from regs and the stack, and set them correctly into the paramResultSlicePtr.
+	if len(abi.Rets) > 0 {
+		panic("TODO")
+	}
+
+	// Finally, restore the original RBP and RSP.
+	// 		mov wazevoapi.ExecutionContextOffsetOriginalFramePointer(%executionContextPtrReg), %rbp
+	// 		mov wazevoapi.ExecutionContextOffsetOriginalStackPointer(%executionContextPtrReg), %rsp
+	cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalFramePointer, rbpVReg, false, cur)
+	cur = m.loadOrStore64AtExecutionCtx(savedExecutionContextPtr, wazevoapi.ExecutionContextOffsetOriginalStackPointer, rspVReg, false, cur)
+
+	ret := m.allocateInstr().asRet(&abi)
+	linkInstr(cur, ret)
+	return root
+}
+
+func (m *machine) move64(src, dst regalloc.VReg, prev *instruction) *instruction {
+	mov := m.allocateInstr().asMovRR(src, dst, true)
+	return linkInstr(prev, mov)
+}
+
+func (m *machine) loadOrStore64AtExecutionCtx(execCtx regalloc.VReg, offset wazevoapi.Offset, r regalloc.VReg, store bool, prev *instruction) *instruction {
+	mem := newOperandMem(newAmodeImmReg(offset.U32(), execCtx))
+	instr := m.allocateInstr()
+	if store {
+		instr.asMovRM(r, mem, 8)
+	} else {
+		instr.asMov64MR(mem, r)
+	}
+	return linkInstr(prev, instr)
+}
+
+// This is for debugging.
+func (m *machine) linkUD2(cur *instruction) *instruction { //nolint
+	return linkInstr(cur, m.allocateInstr().asUD2())
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
new file mode 100644
index 00000000..69946355
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
@@ -0,0 +1,14 @@
+package amd64
+
+import (
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
+)
+
+// CompileGoFunctionTrampoline implements backend.Machine.
+func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte {
+	// TODO
+	ud2 := m.allocateInstr().asUD2()
+	m.encodeWithoutRelResolution(ud2)
+	return m.c.Buf()
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index aff8803c..c12614e1 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -160,15 +160,27 @@ func (i *instruction) String() string {
 }
 
 // Defs implements regalloc.Instr.
-func (i *instruction) Defs(i2 *[]regalloc.VReg) []regalloc.VReg {
-	// TODO implement me
-	panic("implement me")
+func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg {
+	*regs = (*regs)[:0]
+	switch dk := defKinds[i.kind]; dk {
+	case defKindNone:
+	case defKindOp2:
+		*regs = append(*regs, i.op2.r)
+	default:
+		panic(fmt.Sprintf("BUG: invalid defKind %s for %s", dk, i))
+	}
+	return *regs
 }
 
 // Uses implements regalloc.Instr.
-func (i *instruction) Uses(i2 *[]regalloc.VReg) []regalloc.VReg {
-	// TODO implement me
-	panic("implement me")
+func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
+	*regs = (*regs)[:0]
+	switch uk := useKinds[i.kind]; uk {
+	case useKindNone:
+	default:
+		panic(fmt.Sprintf("BUG: invalid useKind %s for %s", uk, i))
+	}
+	return *regs
 }
 
 // AssignUse implements regalloc.Instr.
@@ -185,8 +197,8 @@ func (i *instruction) AssignDef(reg regalloc.VReg) {
 
 // IsCopy implements regalloc.Instr.
 func (i *instruction) IsCopy() bool {
-	// TODO implement me
-	panic("implement me")
+	k := i.kind
+	return k == movRR || k == xmmUnaryRmR
 }
 
 func resetInstruction(i *instruction) {
@@ -205,6 +217,16 @@ func asNop(i *instruction) {
 	i.kind = nop0
 }
 
+func (i *instruction) asNop0WithLabel(label backend.Label) *instruction { //nolint
+	i.kind = nop0
+	i.u1 = uint64(label)
+	return i
+}
+
+func (i *instruction) nop0Label() backend.Label {
+	return backend.Label(i.u1)
+}
+
 type instructionKind int
 
 const (
@@ -362,6 +384,8 @@ const (
 
 	// An instruction that will always trigger the illegal instruction exception.
 	ud2
+
+	instrMax
 )
 
 func (k instructionKind) String() string {
@@ -572,7 +596,7 @@ func (i *instruction) asGprToXmm(op sseOpcode, rm operand, rd regalloc.VReg, _64
 	return i
 }
 
-func (i *instruction) movRM(rm regalloc.VReg, rd operand, size byte) *instruction {
+func (i *instruction) asMovRM(rm regalloc.VReg, rd operand, size byte) *instruction {
 	if rd.kind != operandKindMem {
 		panic("BUG")
 	}
@@ -605,6 +629,11 @@ func (i *instruction) asMovzxRmR(ext extMode, src operand, rd regalloc.VReg) *in
 	return i
 }
 
+func (i *instruction) asUD2() *instruction {
+	i.kind = ud2
+	return i
+}
+
 func (i *instruction) asMov64MR(rm operand, rd regalloc.VReg) *instruction {
 	if rm.kind != operandKindMem {
 		panic("BUG")
@@ -1141,3 +1170,56 @@ func (s sseOpcode) String() string {
 		panic("BUG")
 	}
 }
+
+func linkInstr(prev, next *instruction) *instruction {
+	prev.next = next
+	next.prev = prev
+	return next
+}
+
+type defKind byte
+
+const (
+	defKindNone defKind = iota + 1
+	defKindOp2
+)
+
+var defKinds = [instrMax]defKind{
+	nop0: defKindNone,
+	ret:  defKindNone,
+}
+
+// String implements fmt.Stringer.
+func (d defKind) String() string {
+	switch d {
+	case defKindNone:
+		return "none"
+	case defKindOp2:
+		return "op2"
+	default:
+		return "invalid"
+	}
+}
+
+type useKind byte
+
+const (
+	useKindNone useKind = iota + 1
+	useKindOp1
+)
+
+var useKinds = [instrMax]useKind{
+	nop0: useKindNone,
+	ret:  useKindNone,
+}
+
+func (u useKind) String() string {
+	switch u {
+	case useKindNone:
+		return "none"
+	case useKindOp1:
+		return "op1"
+	default:
+		return "invalid"
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index 6959fba4..ac7f257e 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -7,18 +7,7 @@ import (
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
 
-// Encode implements backend.Machine Encode.
-func (m *machine) Encode() {
-	m.encode(m.ectx.RootInstr)
-}
-
-func (m *machine) encode(root *instruction) {
-	for cur := root; cur != nil; cur = cur.next {
-		cur.encode(m.c)
-	}
-}
-
-func (i *instruction) encode(c backend.Compiler) {
+func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 	switch i.kind {
 	case nop0:
 	case ret:
@@ -664,6 +653,7 @@ func (i *instruction) encode(c backend.Compiler) {
 		op := i.op1
 		switch op.kind {
 		case operandKindLabel:
+			needsLabelResolution = true
 			panic("BUG: at this point label should have been resolved to imm32")
 		case operandKindImm32:
 			c.EmitByte(0x0f)
@@ -723,6 +713,7 @@ func (i *instruction) encode(c backend.Compiler) {
 	default:
 		panic(fmt.Sprintf("TODO: %v", i.kind))
 	}
+	return
 }
 
 func encodeRegReg(
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 354118fe..44f47d7d 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -8,7 +8,6 @@ import (
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
-	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
 )
 
 // NewBackend returns a new backend for arm64.
@@ -39,6 +38,13 @@ type (
 		spillSlotSize int64
 		currentABI    *backend.FunctionABI
 		clobberedRegs []regalloc.VReg
+
+		labelResolutionPends []labelResolutionPend
+	}
+
+	labelResolutionPend struct {
+		instr  *instruction
+		offset int64
 	}
 )
 
@@ -59,7 +65,10 @@ func (m *machine) ExecutableContext() backend.ExecutableContext { return m.ectx
 func (m *machine) DisableStackCheck() { m.stackBoundsCheckDisabled = true }
 
 // SetCompiler implements backend.Machine.
-func (m *machine) SetCompiler(compiler backend.Compiler) { m.c = compiler }
+func (m *machine) SetCompiler(c backend.Compiler) {
+	m.c = c
+	m.regAllocFn = backend.NewRegAllocFunction[*instruction, *machine](m, c.SSABuilder(), c)
+}
 
 // SetCurrentABI implements backend.Machine.
 func (m *machine) SetCurrentABI(abi *backend.FunctionABI) {
@@ -87,8 +96,26 @@ func (m *machine) InsertReturn() {
 
 // LowerSingleBranch implements backend.Machine.
 func (m *machine) LowerSingleBranch(b *ssa.Instruction) {
-	// TODO implement me
-	panic("implement me")
+	ectx := m.ectx
+	switch b.Opcode() {
+	case ssa.OpcodeJump:
+		_, _, targetBlk := b.BranchData()
+		if b.IsFallthroughJump() {
+			return
+		}
+		jmp := m.allocateInstr()
+		target := ectx.GetOrAllocateSSABlockLabel(targetBlk)
+		if target == backend.LabelReturn {
+			jmp.asRet(m.currentABI)
+		} else {
+			jmp.asJmp(newOperandLabel(target))
+		}
+		m.insert(jmp)
+	case ssa.OpcodeBrTable:
+		panic("TODO: implement me")
+	default:
+		panic("BUG: unexpected branch opcode" + b.Opcode().String())
+	}
 }
 
 // LowerConditionalBranch implements backend.Machine.
@@ -158,44 +185,54 @@ func (m *machine) Format() string {
 	return "\n" + strings.Join(lines, "\n") + "\n"
 }
 
-// SetupPrologue implements backend.Machine.
-func (m *machine) SetupPrologue() {
-	// TODO implement me
-	panic("implement me")
-}
-
-// SetupEpilogue implements backend.Machine.
-func (m *machine) SetupEpilogue() {
-	// TODO implement me
-	panic("implement me")
+func (m *machine) encodeWithoutRelResolution(root *instruction) {
+	for cur := root; cur != nil; cur = cur.next {
+		cur.encode(m.c)
+	}
 }
 
-// ResolveRelativeAddresses implements backend.Machine.
-func (m *machine) ResolveRelativeAddresses(ctx context.Context) {
-	// TODO implement me
-	panic("implement me")
-}
+// Encode implements backend.Machine Encode.
+func (m *machine) Encode(context.Context) {
+	ectx := m.ectx
+	bufPtr := m.c.BufPtr()
 
-// ResolveRelocations implements backend.Machine.
-func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, binary []byte, relocations []backend.RelocationInfo) {
-	// TODO implement me
-	panic("implement me")
-}
+	m.labelResolutionPends = m.labelResolutionPends[:0]
+	for _, pos := range ectx.OrderedBlockLabels {
+		offset := int64(len(*bufPtr))
+		pos.BinaryOffset = offset
+		for cur := pos.Begin; cur != pos.End.next; cur = cur.next {
+			offset := int64(len(*bufPtr))
+			if cur.kind == nop0 {
+				l := cur.nop0Label()
+				if pos, ok := ectx.LabelPositions[l]; ok {
+					pos.BinaryOffset = offset
+				}
+			}
 
-// CompileGoFunctionTrampoline implements backend.Machine.
-func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte {
-	// TODO implement me
-	panic("implement me")
-}
+			needLabelResolution := cur.encode(m.c)
+			if needLabelResolution {
+				m.labelResolutionPends = append(m.labelResolutionPends,
+					labelResolutionPend{instr: cur, offset: int64(offset)},
+				)
+			}
+		}
+	}
 
-// CompileStackGrowCallSequence implements backend.Machine.
-func (m *machine) CompileStackGrowCallSequence() []byte {
-	// TODO implement me
-	panic("implement me")
+	for i := range m.labelResolutionPends {
+		p := &m.labelResolutionPends[i]
+		switch p.instr.kind {
+		case jmp:
+			panic("TODO")
+		case jmpIf:
+			panic("TODO")
+		default:
+			panic("BUG")
+		}
+	}
 }
 
-// CompileEntryPreamble implements backend.Machine.
-func (m *machine) CompileEntryPreamble(signature *ssa.Signature) []byte {
+// ResolveRelocations implements backend.Machine.
+func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, binary []byte, relocations []backend.RelocationInfo) {
 	// TODO implement me
 	panic("implement me")
 }
@@ -209,7 +246,24 @@ func (m *machine) allocateInstr() *instruction {
 	return instr
 }
 
+func (m *machine) allocateNop() *instruction {
+	instr := m.allocateInstr()
+	instr.kind = nop0
+	return instr
+}
+
 func (m *machine) insert(i *instruction) {
 	ectx := m.ectx
 	ectx.PendingInstructions = append(ectx.PendingInstructions, i)
 }
+
+func (m *machine) allocateBrTarget() (nop *instruction, l backend.Label) { //nolint
+	ectx := m.ectx
+	l = ectx.AllocateLabel()
+	nop = m.allocateInstr()
+	nop.asNop0WithLabel(l)
+	pos := ectx.AllocateLabelPosition(l)
+	pos.Begin, pos.End = nop, nop
+	ectx.LabelPositions[l] = pos
+	return
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
new file mode 100644
index 00000000..3dd6c2f1
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
@@ -0,0 +1,117 @@
+package amd64
+
+// CompileStackGrowCallSequence implements backend.Machine.
+func (m *machine) CompileStackGrowCallSequence() []byte {
+	// TODO
+	ud2 := m.allocateInstr().asUD2()
+	m.encodeWithoutRelResolution(ud2)
+	return m.c.Buf()
+}
+
+// SetupPrologue implements backend.Machine.
+func (m *machine) SetupPrologue() {
+	cur := m.ectx.RootInstr
+	prevInitInst := cur.next
+
+	// At this point, we have the stack layout as follows:
+	//
+	//                   (high address)
+	//                 +-----------------+ <----- RBP (somewhere in the middle of the stack)
+	//                 |     .......     |
+	//                 |      ret Y      |
+	//                 |     .......     |
+	//                 |      ret 0      |
+	//                 |      arg X      |
+	//                 |     .......     |
+	//                 |      arg 1      |
+	//                 |      arg 0      |
+	//                 |   Return Addr   |
+	//       RSP ----> +-----------------+
+	//                    (low address)
+
+	// First, we push the RBP, and update the RBP to the current RSP.
+	// 		push %rbp
+	// 		mov %rsp, %rbp
+	cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(rbpVReg)))
+	cur = linkInstr(cur, m.allocateInstr().asMovRR(rspVReg, rbpVReg, true))
+
+	if !m.stackBoundsCheckDisabled { //nolint
+		// TODO: stack bounds check
+	}
+
+	if regs := m.clobberedRegs; len(regs) > 0 {
+		panic("TODO: save clobbered registers")
+	}
+
+	if size := m.spillSlotSize; size > 0 {
+		panic("TODO: allocate spill slots")
+	}
+
+	linkInstr(cur, prevInitInst)
+}
+
+// SetupEpilogue implements backend.Machine.
+func (m *machine) SetupEpilogue() {
+	ectx := m.ectx
+	for cur := ectx.RootInstr; cur != nil; cur = cur.next {
+		if cur.kind == ret {
+			m.setupEpilogueAfter(cur.prev)
+			continue
+		}
+
+		// Removes the redundant copy instruction.
+		// TODO: doing this in `SetupEpilogue` seems weird. Find a better home.
+		if cur.IsCopy() && cur.op1.r.RealReg() == cur.op2.r.RealReg() {
+			prev, next := cur.prev, cur.next
+			// Remove the copy instruction.
+			prev.next = next
+			if next != nil {
+				next.prev = prev
+			}
+		}
+	}
+}
+
+func (m *machine) setupEpilogueAfter(cur *instruction) {
+	prevNext := cur.next
+
+	// At this point, we have the stack layout as follows:
+	//
+	//            (high address)
+	//          +-----------------+
+	//          |     .......     |
+	//          |      ret Y      |
+	//          |     .......     |
+	//          |      ret 0      |
+	//          |      arg X      |
+	//          |     .......     |
+	//          |      arg 1      |
+	//          |      arg 0      |
+	//          |   ReturnAddress |
+	//          |   Caller_RBP    |
+	//          +-----------------+ <--- RBP
+	//          |    clobbered M  |
+	//          |   ............  |
+	//          |    clobbered 1  |
+	//          |    clobbered 0  |
+	//          |   spill slot N  |
+	//          |   ............  |
+	//          |   spill slot 0  |
+	//          +-----------------+ <--- RSP
+	//             (low address)
+
+	if size := m.spillSlotSize; size > 0 {
+		panic("TODO: deallocate spill slots")
+	}
+	if regs := m.clobberedRegs; len(regs) > 0 {
+		panic("TODO: restore clobbered registers")
+	}
+
+	// Now roll back the RSP to the return address, and pop the RBP.
+	// 		mov  %rbp, %rsp
+	// 		pop  %rbp
+	cur = linkInstr(cur, m.allocateInstr().asMovRR(rbpVReg, rspVReg, true))
+	cur = linkInstr(cur, m.allocateInstr().asPop64(rbpVReg))
+
+	linkInstr(cur, prevNext)
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go b/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go
index bf72976b..b6b9f273 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go
@@ -136,7 +136,6 @@ func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr reg
 
 func (m *machine) constructEntryPreamble(sig *ssa.Signature) (root *instruction) {
 	abi := backend.FunctionABI{}
-	m.ArgsResultsRegs()
 	abi.Init(sig, intParamResultRegs, floatParamResultRegs)
 
 	root = m.allocateNop()
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 2b8231e4..cc69ad19 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"context"
 	"fmt"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
@@ -9,7 +10,8 @@ import (
 )
 
 // Encode implements backend.Machine Encode.
-func (m *machine) Encode() {
+func (m *machine) Encode(ctx context.Context) {
+	m.resolveRelativeAddresses(ctx)
 	m.encode(m.executableContext.RootInstr)
 }
 
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go
index ad778552..94668446 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine.go
@@ -242,8 +242,8 @@ func (m *machine) resolveAddressingMode(arg0offset, ret0offset int64, i *instruc
 	}
 }
 
-// ResolveRelativeAddresses implements backend.Machine.
-func (m *machine) ResolveRelativeAddresses(ctx context.Context) {
+// resolveRelativeAddresses resolves the relative addresses before encoding.
+func (m *machine) resolveRelativeAddresses(ctx context.Context) {
 	if len(m.unresolvedAddressModes) > 0 {
 		arg0offset, ret0offset := m.arg0OffsetFromSP(), m.ret0OffsetFromSP()
 		for _, i := range m.unresolvedAddressModes {
@@ -311,8 +311,6 @@ func (m *machine) ResolveRelativeAddresses(ctx context.Context) {
 				wazevoapi.PerfMap.AddModuleEntry(fnIndex, offset, uint64(size), fmt.Sprintf("%s:::::%s", fn, labelStr))
 			}
 		}
-
-		pos.BinarySize = size
 		offset += size
 	}
 
@@ -336,7 +334,7 @@ func (m *machine) ResolveRelativeAddresses(ctx context.Context) {
 		}
 	}
 	if needRerun {
-		m.ResolveRelativeAddresses(ctx)
+		m.resolveRelativeAddresses(ctx)
 		if wazevoapi.PerfMapEnabled {
 			wazevoapi.PerfMap.Clear()
 		}
diff --git a/internal/engine/wazevo/backend/machine.go b/internal/engine/wazevo/backend/machine.go
index 8b7b0c47..0bbb8c91 100644
--- a/internal/engine/wazevo/backend/machine.go
+++ b/internal/engine/wazevo/backend/machine.go
@@ -62,15 +62,11 @@ type (
 		// This sets up the instructions for the inverse of SetupPrologue right before
 		SetupEpilogue()
 
-		// ResolveRelativeAddresses resolves the relative addresses after register allocations and prologue/epilogue setup.
-		// After this, the compiler is finally ready to emit machine code.
-		ResolveRelativeAddresses(ctx context.Context)
-
 		// ResolveRelocations resolves the relocations after emitting machine code.
 		ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, binary []byte, relocations []RelocationInfo)
 
 		// Encode encodes the machine instructions to the Compiler.
-		Encode()
+		Encode(ctx context.Context)
 
 		// CompileGoFunctionTrampoline compiles the trampoline function  to call a Go function of the given exit code and signature.
 		CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 4a1e50bb..a03e93b1 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -228,6 +228,9 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 		defer done()
 	}
 
+	if c.stackTop&(16-1) != 0 {
+		panic("BUG: stack must be aligned to 16 bytes")
+	}
 	entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop)
 	for {
 		switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask {
diff --git a/internal/engine/wazevo/engine.go b/internal/engine/wazevo/engine.go
index faa1330f..f2a9b9ea 100644
--- a/internal/engine/wazevo/engine.go
+++ b/internal/engine/wazevo/engine.go
@@ -292,7 +292,9 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
 	}
 
 	// Resolve relocations for local function calls.
-	machine.ResolveRelocations(e.refToBinaryOffset, executable, e.rels)
+	if len(e.rels) > 0 {
+		machine.ResolveRelocations(e.refToBinaryOffset, executable, e.rels)
+	}
 
 	if runtime.GOARCH == "arm64" {
 		// On arm64, we cannot give all of rwx at the same time, so we change it to exec.
@@ -422,7 +424,7 @@ func (e *engine) compileHostModule(ctx context.Context, module *wasm.Module, lis
 
 		be.Init()
 		machine.CompileGoFunctionTrampoline(exitCode, &sig, true)
-		be.Encode()
+		be.Finalize(ctx)
 		body := be.Buf()
 
 		if wazevoapi.PerfMapEnabled {
diff --git a/internal/engine/wazevo/entrypoint_amd64.go b/internal/engine/wazevo/entrypoint_amd64.go
new file mode 100644
index 00000000..9093673d
--- /dev/null
+++ b/internal/engine/wazevo/entrypoint_amd64.go
@@ -0,0 +1,13 @@
+package wazevo
+
+import _ "unsafe"
+
+// entrypoint is implemented by the backend.
+//
+//go:linkname entrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64.entrypoint
+func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPtr uintptr, moduleContextPtr *byte, paramResultStackPtr *uint64, goAllocatedStackSlicePtr uintptr)
+
+// entrypoint is implemented by the backend.
+//
+//go:linkname afterGoFunctionCallEntrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64.afterGoFunctionCallEntrypoint
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer uintptr)
diff --git a/internal/engine/wazevo/entrypoint_others.go b/internal/engine/wazevo/entrypoint_others.go
index 5fd25f97..0bbfc400 100644
--- a/internal/engine/wazevo/entrypoint_others.go
+++ b/internal/engine/wazevo/entrypoint_others.go
@@ -1,4 +1,4 @@
-//go:build !arm64
+//go:build !arm64 && !amd64
 
 package wazevo
 
diff --git a/internal/wasm/binary/limits.go b/internal/wasm/binary/limits.go
index 1ec8d7c3..ff2d73b5 100644
--- a/internal/wasm/binary/limits.go
+++ b/internal/wasm/binary/limits.go
@@ -10,7 +10,9 @@ import (
 // decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly 1.0 (20191205) Binary Format.
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
-func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) {
+//
+// Extended in threads proposal: https://webassembly.github.io/threads/core/binary/types.html#limits
+func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, shared bool, err error) {
 	var flag byte
 	if flag, err = r.ReadByte(); err != nil {
 		err = fmt.Errorf("read leading byte: %v", err)
@@ -18,12 +20,12 @@ func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) {
 	}
 
 	switch flag {
-	case 0x00:
+	case 0x00, 0x02:
 		min, _, err = leb128.DecodeUint32(r)
 		if err != nil {
 			err = fmt.Errorf("read min of limit: %v", err)
 		}
-	case 0x01:
+	case 0x01, 0x03:
 		min, _, err = leb128.DecodeUint32(r)
 		if err != nil {
 			err = fmt.Errorf("read min of limit: %v", err)
@@ -36,7 +38,10 @@ func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) {
 			max = &m
 		}
 	default:
-		err = fmt.Errorf("%v for limits: %#x != 0x00 or 0x01", ErrInvalidByte, flag)
+		err = fmt.Errorf("%v for limits: %#x not in (0x00, 0x01, 0x02, 0x03)", ErrInvalidByte, flag)
 	}
+
+	shared = flag == 0x02 || flag == 0x03
+
 	return
 }
diff --git a/internal/wasm/binary/memory.go b/internal/wasm/binary/memory.go
index fd6b32a6..aca61994 100644
--- a/internal/wasm/binary/memory.go
+++ b/internal/wasm/binary/memory.go
@@ -2,6 +2,7 @@ package binary
 
 import (
 	"bytes"
+	"fmt"
 
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
@@ -14,13 +15,23 @@ func decodeMemory(
 	memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32),
 	memoryLimitPages uint32,
 ) (*wasm.Memory, error) {
-	min, maxP, err := decodeLimitsType(r)
+	min, maxP, shared, err := decodeLimitsType(r)
 	if err != nil {
 		return nil, err
 	}
 
+	if shared {
+		// TODO(anuraaga): Check threads feature is enabled
+
+		// This restriction may be lifted in the future.
+		// https://webassembly.github.io/threads/core/binary/types.html#memory-types
+		if maxP == nil {
+			return nil, fmt.Errorf("shared memory requires a maximum size to be specified")
+		}
+	}
+
 	min, capacity, max := memorySizer(min, maxP)
-	mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil}
+	mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil, IsShared: shared}
 
 	return mem, mem.Validate(memoryLimitPages)
 }
diff --git a/internal/wasm/binary/table.go b/internal/wasm/binary/table.go
index cb5aa75e..353ec756 100644
--- a/internal/wasm/binary/table.go
+++ b/internal/wasm/binary/table.go
@@ -23,7 +23,8 @@ func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Ta
 		}
 	}
 
-	ret.Min, ret.Max, err = decodeLimitsType(r)
+	var shared bool
+	ret.Min, ret.Max, shared, err = decodeLimitsType(r)
 	if err != nil {
 		return fmt.Errorf("read limits: %v", err)
 	}
@@ -35,5 +36,8 @@ func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Ta
 			return fmt.Errorf("table size minimum must not be greater than maximum")
 		}
 	}
+	if shared {
+		return fmt.Errorf("tables cannot be marked as shared")
+	}
 	return
 }
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index bce037e4..38781449 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -36,6 +36,7 @@ type MemoryInstance struct {
 
 	Buffer        []byte
 	Min, Cap, Max uint32
+	Shared        bool
 	// definition is known at compile time.
 	definition api.MemoryDefinition
 }
@@ -49,6 +50,7 @@ func NewMemoryInstance(memSec *Memory) *MemoryInstance {
 		Min:    memSec.Min,
 		Cap:    memSec.Cap,
 		Max:    memSec.Max,
+		Shared: memSec.IsShared,
 	}
 }
 
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index a5aaf6ad..c019730e 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -766,6 +766,8 @@ type Memory struct {
 	Min, Cap, Max uint32
 	// IsMaxEncoded true if the Max is encoded in the original binary.
 	IsMaxEncoded bool
+	// IsShared true if the memory is shared for access from multiple agents.
+	IsShared bool
 }
 
 // Validate ensures values assigned to Min, Cap and Max are within valid thresholds.


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index 0deb91f7..e51a564f 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -2219,9 +2219,6 @@ L1 (SSA Block: blk0):
 			default:
 				t.Fail()
 			}
-
-			// Sanity check on the final binary encoding.
-			be.Encode()
 		})
 	}
 }
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble_test.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble_test.go
new file mode 100644
index 00000000..d894796c
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble_test.go
@@ -0,0 +1,41 @@
+package amd64
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestMachineCompileEntryPreamble(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		sig  *ssa.Signature
+		exp  string
+	}{
+		{
+			name: "basic",
+			sig: &ssa.Signature{
+				// execContext and moduleContext are passed in %rax and %rcx.
+				Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64},
+			},
+			exp: `
+	movq %rax, %rdx
+	mov.q %rbp, 16(%rax)
+	mov.q %rsp, 24(%rax)
+	movq %rbx, %rsp
+	callq *%rdi
+	movq 16(%rdx), %rbp
+	movq 24(%rdx), %rsp
+	ret
+`,
+		},
+		// TODO: add Wasm param, result cases.
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.ectx.RootInstr = m.compileEntryPreamble(tc.sig)
+			require.Equal(t, tc.exp, m.Format())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index c1e560ba..826fa874 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -1666,7 +1666,7 @@ func TestInstruction_format_encode(t *testing.T) {
 		{
 			setup: func(i *instruction) {
 				a := newOperandMem(newAmodeImmReg(1<<20, raxVReg))
-				i.movRM(rcxVReg, a, 1)
+				i.asMovRM(rcxVReg, a, 1)
 			},
 			want:       "888800001000",
 			wantFormat: "mov.b %rcx, 1048576(%rax)",
@@ -1674,7 +1674,7 @@ func TestInstruction_format_encode(t *testing.T) {
 		{
 			setup: func(i *instruction) {
 				a := newOperandMem(newAmodeImmReg(1<<20, raxVReg))
-				i.movRM(rdiVReg, a, 1)
+				i.asMovRM(rdiVReg, a, 1)
 			},
 			want:       "88b800001000",
 			wantFormat: "mov.b %rdi, 1048576(%rax)",
@@ -1682,7 +1682,7 @@ func TestInstruction_format_encode(t *testing.T) {
 		{
 			setup: func(i *instruction) {
 				a := newOperandMem(newAmodeRegRegShit(1<<20, raxVReg, rcxVReg, 3))
-				i.movRM(rdiVReg, a, 2)
+				i.asMovRM(rdiVReg, a, 2)
 			},
 			want:       "6689bcc800001000",
 			wantFormat: "mov.w %rdi, 1048576(%rax,%rcx,8)",
@@ -1690,7 +1690,7 @@ func TestInstruction_format_encode(t *testing.T) {
 		{
 			setup: func(i *instruction) {
 				a := newOperandMem(newAmodeRegRegShit(123, raxVReg, rcxVReg, 0))
-				i.movRM(rdiVReg, a, 4)
+				i.asMovRM(rdiVReg, a, 4)
 			},
 			want:       "897c087b",
 			wantFormat: "mov.l %rdi, 123(%rax,%rcx,1)",
@@ -1698,7 +1698,7 @@ func TestInstruction_format_encode(t *testing.T) {
 		{
 			setup: func(i *instruction) {
 				a := newOperandMem(newAmodeRegRegShit(123, raxVReg, rcxVReg, 0))
-				i.movRM(rdiVReg, a, 8)
+				i.asMovRM(rdiVReg, a, 8)
 			},
 			want:       "48897c087b",
 			wantFormat: "mov.q %rdi, 123(%rax,%rcx,1)",
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue_test.go
new file mode 100644
index 00000000..e8d15942
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue_test.go
@@ -0,0 +1,90 @@
+package amd64
+
+import (
+	"context"
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestMachine_SetupPrologue(t *testing.T) {
+	for _, tc := range []struct {
+		spillSlotSize int64
+		clobberedRegs []regalloc.VReg
+		exp           string
+		abi           backend.FunctionABI
+	}{
+		{
+			spillSlotSize: 0,
+			exp: `
+	pushq %rbp
+	movq %rsp, %rbp
+	ud2
+`,
+		},
+		// TODO: add more test cases.
+	} {
+		tc := tc
+		t.Run(tc.exp, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.DisableStackCheck()
+			m.spillSlotSize = tc.spillSlotSize
+			m.clobberedRegs = tc.clobberedRegs
+			m.currentABI = &tc.abi
+
+			root := m.allocateNop()
+			m.ectx.RootInstr = root
+			udf := m.allocateInstr()
+			udf.asUD2()
+			root.next = udf
+			udf.prev = root
+
+			m.SetupPrologue()
+			require.Equal(t, root, m.ectx.RootInstr)
+			m.Encode(context.Background())
+			require.Equal(t, tc.exp, m.Format())
+		})
+	}
+}
+
+func TestMachine_SetupEpilogue(t *testing.T) {
+	for _, tc := range []struct {
+		exp           string
+		abi           backend.FunctionABI
+		clobberedRegs []regalloc.VReg
+		spillSlotSize int64
+	}{
+		{
+			exp: `
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`,
+			spillSlotSize: 0,
+			clobberedRegs: nil,
+		},
+		// TODO: add more test cases.
+	} {
+		tc := tc
+		t.Run(tc.exp, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.spillSlotSize = tc.spillSlotSize
+			m.clobberedRegs = tc.clobberedRegs
+			m.currentABI = &tc.abi
+
+			root := m.allocateNop()
+			m.ectx.RootInstr = root
+			ret := m.allocateInstr()
+			ret.asRet(nil)
+			root.next = ret
+			ret.prev = root
+			m.SetupEpilogue()
+
+			require.Equal(t, root, m.ectx.RootInstr)
+			m.Encode(context.Background())
+			require.Equal(t, tc.exp, m.Format())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/util_test.go b/internal/engine/wazevo/backend/isa/amd64/util_test.go
index 8d2d220e..98b939c4 100644
--- a/internal/engine/wazevo/backend/isa/amd64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/util_test.go
@@ -8,6 +8,16 @@ import (
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 )
 
+func newSetupWithMockContext() (*mockCompiler, ssa.Builder, *machine) {
+	ctx := newMockCompilationContext()
+	m := NewBackend().(*machine)
+	m.SetCompiler(ctx)
+	ssaB := ssa.NewBuilder()
+	blk := ssaB.AllocateBasicBlock()
+	ssaB.SetCurrentBlock(blk)
+	return ctx, ssaB, m
+}
+
 // mockCompiler implements backend.Compiler for testing.
 type mockCompiler struct {
 	currentGID  ssa.InstructionGroupID
@@ -20,6 +30,8 @@ type mockCompiler struct {
 	buf         []byte
 }
 
+func (m *mockCompiler) BufPtr() *[]byte { return &m.buf }
+
 func (m *mockCompiler) GetFunctionABI(sig *ssa.Signature) *backend.FunctionABI {
 	// TODO implement me
 	panic("implement me")
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble_test.go b/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble_test.go
index 00ee0d87..038d9115 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble_test.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"context"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
@@ -534,7 +535,7 @@ func TestMachine_goEntryPreamblePassArg(t *testing.T) {
 			m.executableContext.RootInstr = cur
 			m.goEntryPreamblePassArg(cur, paramSlicePtr, &tc.arg, tc.argSlotBeginOffsetFromSP)
 			require.Equal(t, tc.exp, m.Format())
-			m.Encode()
+			m.Encode(context.Background())
 		})
 	}
 }
@@ -687,7 +688,7 @@ func TestMachine_goEntryPreamblePassResult(t *testing.T) {
 			m.executableContext.RootInstr = cur
 			m.goEntryPreamblePassResult(cur, paramSlicePtr, &tc.arg, tc.retStart)
 			require.Equal(t, tc.exp, m.Format())
-			m.Encode()
+			m.Encode(context.Background())
 		})
 	}
 }
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
index 99a81fca..1a1d9e25 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_call_test.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"context"
 	"sort"
 	"testing"
 
@@ -450,7 +451,7 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 
 			require.Equal(t, tc.exp, m.Format())
 
-			m.Encode()
+			m.Encode(context.Background())
 		})
 	}
 }
@@ -565,7 +566,7 @@ func Test_goFunctionCallLoadStackArg(t *testing.T) {
 				m.executableContext.RootInstr = nop
 
 				require.Equal(t, tc.exp, m.Format())
-				m.Encode()
+				m.Encode(context.Background())
 			})
 		})
 	}
@@ -631,7 +632,7 @@ func Test_goFunctionCallStoreStackResult(t *testing.T) {
 				m.executableContext.RootInstr = nop
 
 				require.Equal(t, tc.exp, m.Format())
-				m.Encode()
+				m.Encode(context.Background())
 			})
 		})
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
index 13174407..260cabc3 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue_test.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"context"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
@@ -109,7 +110,7 @@ func TestMachine_SetupPrologue(t *testing.T) {
 
 			m.SetupPrologue()
 			require.Equal(t, root, m.executableContext.RootInstr)
-			m.Encode()
+			m.Encode(context.Background())
 			require.Equal(t, tc.exp, m.Format())
 		})
 	}
@@ -222,7 +223,7 @@ func TestMachine_SetupEpilogue(t *testing.T) {
 			m.SetupEpilogue()
 
 			require.Equal(t, root, m.executableContext.RootInstr)
-			m.Encode()
+			m.Encode(context.Background())
 			require.Equal(t, tc.exp, m.Format())
 		})
 	}
@@ -267,7 +268,7 @@ func TestMachine_insertStackBoundsCheck(t *testing.T) {
 			m.executableContext.RootInstr = m.allocateInstr()
 			m.executableContext.RootInstr.asNop0()
 			m.insertStackBoundsCheck(tc.requiredStackSize, m.executableContext.RootInstr)
-			m.Encode()
+			m.Encode(context.Background())
 			require.Equal(t, tc.exp, m.Format())
 		})
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/util_test.go b/internal/engine/wazevo/backend/isa/arm64/util_test.go
index 6dd7862c..2e9fc3e1 100644
--- a/internal/engine/wazevo/backend/isa/arm64/util_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/util_test.go
@@ -61,6 +61,8 @@ type mockCompiler struct {
 	buf         []byte
 }
 
+func (m *mockCompiler) BufPtr() *[]byte { return &m.buf }
+
 func (m *mockCompiler) GetFunctionABI(sig *ssa.Signature) *backend.FunctionABI {
 	// TODO implement me
 	panic("implement me")
diff --git a/internal/engine/wazevo/backend/machine_test.go b/internal/engine/wazevo/backend/machine_test.go
index e15185d0..a80d737e 100644
--- a/internal/engine/wazevo/backend/machine_test.go
+++ b/internal/engine/wazevo/backend/machine_test.go
@@ -52,7 +52,7 @@ func (m mockMachine) CompileGoFunctionTrampoline(wazevoapi.ExitCode, *ssa.Signat
 }
 
 // Encode implements Machine.Encode.
-func (m mockMachine) Encode() {}
+func (m mockMachine) Encode(ctx context.Context) {}
 
 // ResolveRelocations implements Machine.ResolveRelocations.
 func (m mockMachine) ResolveRelocations(map[ssa.FuncRef]int, []byte, []RelocationInfo) {}
@@ -63,9 +63,6 @@ func (m mockMachine) SetupPrologue() {}
 // SetupEpilogue implements Machine.SetupEpilogue.
 func (m mockMachine) SetupEpilogue() {}
 
-// ResolveRelativeAddresses implements Machine.ResolveRelativeAddresses.
-func (m mockMachine) ResolveRelativeAddresses(ctx context.Context) {}
-
 // Function implements Machine.Function.
 func (m mockMachine) Function() (f regalloc.Function) { return }
 
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 9e6013b2..5fd5fb78 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -13,7 +13,6 @@ import (
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/experimental/logging"
-	"github.com/tetratelabs/wazero/experimental/opt"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/testcases"
 	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
@@ -50,6 +49,14 @@ func TestE2E(t *testing.T) {
 		calls       []callCase
 		skipAMD64   bool
 	}{
+		{
+			name: "empty", m: testcases.Empty.Module,
+			calls: []callCase{{expResults: []uint64{}}},
+		},
+		{
+			name: "only_return", m: testcases.OnlyReturn.Module,
+			calls: []callCase{{expResults: []uint64{}}},
+		},
 		{
 			name: "selects", m: testcases.Selects.Module,
 			skipAMD64: true,
@@ -101,7 +108,10 @@ func TestE2E(t *testing.T) {
 				{params: []uint64{30}, expResults: []uint64{0xcb228}},
 			},
 		},
-		{name: "call", m: testcases.Call.Module, calls: []callCase{{expResults: []uint64{45, 45}}}},
+		{
+			name: "call", m: testcases.Call.Module, calls: []callCase{{expResults: []uint64{45, 45}}},
+			skipAMD64: true,
+		},
 		{
 			name: "stack overflow",
 			m: &wasm.Module{
@@ -331,10 +341,10 @@ func TestE2E(t *testing.T) {
 		},
 	} {
 		tc := tc
-		if tc.skipAMD64 {
-			skipOnAmd64(t)
-		}
 		t.Run(tc.name, func(t *testing.T) {
+			if tc.skipAMD64 {
+				skipOnAmd64(t)
+			}
 			for i := 0; i < 2; i++ {
 				var name string
 				if i == 0 {
@@ -345,7 +355,7 @@ func TestE2E(t *testing.T) {
 				t.Run(name, func(t *testing.T) {
 					cache, err := wazero.NewCompilationCacheWithDir(tmp)
 					require.NoError(t, err)
-					config := opt.NewRuntimeConfigOptimizingCompiler().WithCompilationCache(cache)
+					config := newRuntimeConfigOptimizingCompiler().WithCompilationCache(cache)
 
 					ctx := context.Background()
 					r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -412,7 +422,7 @@ func TestE2E_host_functions(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := tc.ctx
 
-			config := opt.NewRuntimeConfigOptimizingCompiler()
+			config := newRuntimeConfigOptimizingCompiler()
 
 			r := wazero.NewRuntimeWithConfig(ctx, config)
 			defer func() {
@@ -495,7 +505,7 @@ func TestE2E_host_functions(t *testing.T) {
 
 func TestE2E_stores(t *testing.T) {
 	skipOnAmd64(t)
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -583,7 +593,7 @@ func TestE2E_reexported_memory(t *testing.T) {
 		CodeSection:       []wasm.Code{{Body: []byte{wasm.OpcodeI32Const, 10, wasm.OpcodeMemoryGrow, 0, wasm.OpcodeEnd}}},
 	}
 
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -633,7 +643,7 @@ func TestStackUnwind_panic_in_host(t *testing.T) {
 		},
 	}
 
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -684,7 +694,7 @@ func TestStackUnwind_unreachable(t *testing.T) {
 		},
 	}
 
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.Background()
 	r := wazero.NewRuntimeWithConfig(ctx, config)
 	defer func() {
@@ -707,7 +717,7 @@ wasm stack trace:
 func TestListener_local(t *testing.T) {
 	skipOnAmd64(t)
 	var buf bytes.Buffer
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -736,7 +746,7 @@ func TestListener_local(t *testing.T) {
 func TestListener_imported(t *testing.T) {
 	skipOnAmd64(t)
 	var buf bytes.Buffer
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -788,7 +798,7 @@ func TestListener_long(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -839,7 +849,7 @@ func TestListener_long_as_is(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -889,7 +899,7 @@ func TestListener_long_many_consts(t *testing.T) {
 	})
 
 	var buf bytes.Buffer
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
 
 	r := wazero.NewRuntimeWithConfig(ctx, config)
@@ -915,7 +925,7 @@ func TestListener_long_many_consts(t *testing.T) {
 // TestDWARF verifies that the DWARF based stack traces work as expected before/after compilation cache.
 func TestDWARF(t *testing.T) {
 	skipOnAmd64(t)
-	config := opt.NewRuntimeConfigOptimizingCompiler()
+	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.Background()
 
 	bin := dwarftestdata.ZigWasm
@@ -947,3 +957,12 @@ func TestDWARF(t *testing.T) {
 	err = r.Close(ctx)
 	require.NoError(t, err)
 }
+
+// newRuntimeConfigOptimizingCompiler is the same as opt.NewRuntimeConfigOptimizingCompiler
+// except that it doesn't check if it is on arm64 or not. Use this in tests until amd64 is completely supported.
+func newRuntimeConfigOptimizingCompiler() wazero.RuntimeConfig {
+	type enabler interface{ EnableOptimizingCompiler() }
+	c := wazero.NewRuntimeConfig()
+	c.(enabler).EnableOptimizingCompiler()
+	return c
+}
diff --git a/internal/testing/binaryencoding/import.go b/internal/testing/binaryencoding/import.go
index 0646a19c..8abffde2 100644
--- a/internal/testing/binaryencoding/import.go
+++ b/internal/testing/binaryencoding/import.go
@@ -19,13 +19,13 @@ func EncodeImport(i *wasm.Import) []byte {
 		data = append(data, leb128.EncodeUint32(i.DescFunc)...)
 	case wasm.ExternTypeTable:
 		data = append(data, wasm.RefTypeFuncref)
-		data = append(data, EncodeLimitsType(i.DescTable.Min, i.DescTable.Max)...)
+		data = append(data, EncodeLimitsType(i.DescTable.Min, i.DescTable.Max, false)...)
 	case wasm.ExternTypeMemory:
 		maxPtr := &i.DescMem.Max
 		if !i.DescMem.IsMaxEncoded {
 			maxPtr = nil
 		}
-		data = append(data, EncodeLimitsType(i.DescMem.Min, maxPtr)...)
+		data = append(data, EncodeLimitsType(i.DescMem.Min, maxPtr, i.DescMem.IsShared)...)
 	case wasm.ExternTypeGlobal:
 		g := i.DescGlobal
 		var mutable byte
diff --git a/internal/testing/binaryencoding/limits.go b/internal/testing/binaryencoding/limits.go
index 016e4dc7..ebc2421c 100644
--- a/internal/testing/binaryencoding/limits.go
+++ b/internal/testing/binaryencoding/limits.go
@@ -7,9 +7,19 @@ import (
 // EncodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly 1.0 (20191205) Binary Format.
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
-func EncodeLimitsType(min uint32, max *uint32) []byte {
-	if max == nil {
-		return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(min)...)
+//
+// Extended in threads proposal: https://webassembly.github.io/threads/core/binary/types.html#limits
+func EncodeLimitsType(min uint32, max *uint32, shared bool) []byte {
+	var flag uint32
+	if max != nil {
+		flag = 0x01
+	}
+	if shared {
+		flag |= 0x02
+	}
+	ret := append(leb128.EncodeUint32(flag), leb128.EncodeUint32(min)...)
+	if max != nil {
+		ret = append(ret, leb128.EncodeUint32(*max)...)
 	}
-	return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(min), leb128.EncodeUint32(*max)...)...)
+	return ret
 }
diff --git a/internal/testing/binaryencoding/memory.go b/internal/testing/binaryencoding/memory.go
index 938667a6..2c853330 100644
--- a/internal/testing/binaryencoding/memory.go
+++ b/internal/testing/binaryencoding/memory.go
@@ -12,5 +12,5 @@ func EncodeMemory(i *wasm.Memory) []byte {
 	if !i.IsMaxEncoded {
 		maxPtr = nil
 	}
-	return EncodeLimitsType(i.Min, maxPtr)
+	return EncodeLimitsType(i.Min, maxPtr, i.IsShared)
 }
diff --git a/internal/testing/binaryencoding/table.go b/internal/testing/binaryencoding/table.go
index 2985dbdb..57f83db7 100644
--- a/internal/testing/binaryencoding/table.go
+++ b/internal/testing/binaryencoding/table.go
@@ -8,5 +8,5 @@ import (
 //
 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
 func EncodeTable(i *wasm.Table) []byte {
-	return append([]byte{i.Type}, EncodeLimitsType(i.Min, i.Max)...)
+	return append([]byte{i.Type}, EncodeLimitsType(i.Min, i.Max, false)...)
 }
diff --git a/internal/wasm/binary/limits_test.go b/internal/wasm/binary/limits_test.go
index e6f66393..7e65b4d6 100644
--- a/internal/wasm/binary/limits_test.go
+++ b/internal/wasm/binary/limits_test.go
@@ -18,6 +18,7 @@ func TestLimitsType(t *testing.T) {
 		name     string
 		min      uint32
 		max      *uint32
+		shared   bool
 		expected []byte
 	}{
 		{
@@ -45,21 +46,52 @@ func TestLimitsType(t *testing.T) {
 			max:      &largest,
 			expected: []byte{0x1, 0xff, 0xff, 0xff, 0xff, 0xf, 0xff, 0xff, 0xff, 0xff, 0xf},
 		},
+		{
+			name:     "min 0, shared",
+			shared:   true,
+			expected: []byte{0x2, 0},
+		},
+		{
+			name:     "min 0, max 0, shared",
+			max:      &zero,
+			shared:   true,
+			expected: []byte{0x3, 0, 0},
+		},
+		{
+			name:     "min largest, shared",
+			min:      largest,
+			shared:   true,
+			expected: []byte{0x2, 0xff, 0xff, 0xff, 0xff, 0xf},
+		},
+		{
+			name:     "min 0, max largest, shared",
+			max:      &largest,
+			shared:   true,
+			expected: []byte{0x3, 0, 0xff, 0xff, 0xff, 0xff, 0xf},
+		},
+		{
+			name:     "min largest max largest, shared",
+			min:      largest,
+			max:      &largest,
+			shared:   true,
+			expected: []byte{0x3, 0xff, 0xff, 0xff, 0xff, 0xf, 0xff, 0xff, 0xff, 0xff, 0xf},
+		},
 	}
 
 	for _, tt := range tests {
 		tc := tt
 
-		b := binaryencoding.EncodeLimitsType(tc.min, tc.max)
+		b := binaryencoding.EncodeLimitsType(tc.min, tc.max, tc.shared)
 		t.Run(fmt.Sprintf("encode - %s", tc.name), func(t *testing.T) {
 			require.Equal(t, tc.expected, b)
 		})
 
 		t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) {
-			min, max, err := decodeLimitsType(bytes.NewReader(b))
+			min, max, shared, err := decodeLimitsType(bytes.NewReader(b))
 			require.NoError(t, err)
 			require.Equal(t, min, tc.min)
 			require.Equal(t, max, tc.max)
+			require.Equal(t, shared, tc.shared)
 		})
 	}
 }
diff --git a/internal/wasm/binary/memory_test.go b/internal/wasm/binary/memory_test.go
index 32bfa188..8a861336 100644
--- a/internal/wasm/binary/memory_test.go
+++ b/internal/wasm/binary/memory_test.go
@@ -163,6 +163,11 @@ func TestMemoryType(t *testing.T) {
 			memoryLimitPages: 512,
 			expected:         []byte{0x1, 0, 0x80, 0x80, 0x4},
 		},
+		{
+			name:     "min 0, max 1, shared",
+			input:    &wasm.Memory{Max: 1, IsMaxEncoded: true, IsShared: true},
+			expected: []byte{0x3, 0, 1},
+		},
 	}
 
 	for _, tt := range tests {
@@ -212,6 +217,11 @@ func TestDecodeMemoryType_Errors(t *testing.T) {
 			input:       []byte{0x1, 0, 0xff, 0xff, 0xff, 0xff, 0xf},
 			expectedErr: "max 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)",
 		},
+		{
+			name:        "shared but no max",
+			input:       []byte{0x2, 0, 0x80, 0x80, 0x4},
+			expectedErr: "shared memory requires a maximum size to be specified",
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/wasm/binary/table_test.go b/internal/wasm/binary/table_test.go
index 73ec6d4d..5f0bbaf6 100644
--- a/internal/wasm/binary/table_test.go
+++ b/internal/wasm/binary/table_test.go
@@ -93,6 +93,12 @@ func TestDecodeTableType_Errors(t *testing.T) {
 			expectedErr: "table min must be at most 134217728",
 			features:    api.CoreFeatureReferenceTypes,
 		},
+		{
+			name:        "shared",
+			input:       []byte{wasm.RefTypeFuncref, 0x2, 0},
+			expectedErr: "tables cannot be marked as shared",
+			// Shared tables are an error even if threads are enabled.
+		},
 	}
 
 	for _, tt := range tests {