Repository: tetratelabs/wazero
Pull request #1961 (2024-01-25 19:01:12)

wazevo(amd64): Go function calls


Patch:
diff --git a/internal/engine/wazevo/backend/go_call.go b/internal/engine/wazevo/backend/go_call.go
new file mode 100644
index 00000000..6fe6d7b3
--- /dev/null
+++ b/internal/engine/wazevo/backend/go_call.go
@@ -0,0 +1,33 @@
+package backend
+
+import "github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+
+// GoFunctionCallRequiredStackSize returns the size of the stack required for the Go function call.
+// argBegin is the index of the first argument in the signature which is not either execution context or module context.
+func GoFunctionCallRequiredStackSize(sig *ssa.Signature, argBegin int) (ret, retUnaligned int64) {
+	var paramNeededInBytes, resultNeededInBytes int64
+	for _, p := range sig.Params[argBegin:] {
+		s := int64(p.Size())
+		if s < 8 {
+			s = 8 // We use uint64 for all basic types, except SIMD v128.
+		}
+		paramNeededInBytes += s
+	}
+	for _, r := range sig.Results {
+		s := int64(r.Size())
+		if s < 8 {
+			s = 8 // We use uint64 for all basic types, except SIMD v128.
+		}
+		resultNeededInBytes += s
+	}
+
+	if paramNeededInBytes > resultNeededInBytes {
+		ret = paramNeededInBytes
+	} else {
+		ret = resultNeededInBytes
+	}
+	retUnaligned = ret
+	// Align to 16 bytes.
+	ret = (ret + 15) &^ 15
+	return
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go
index 989292b4..cbf1cfdc 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go
@@ -6,4 +6,4 @@ func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPt
 
 // 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)
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer 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
index 6fd4a208..81c29dda 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
@@ -11,5 +11,19 @@ TEXT ·entrypoint(SB), NOSPLIT|NOFRAME, $0-48
 	MOVQ goAllocatedStackSlicePtr+40(FP), R13
 	JMP  R11
 
-TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-24
-	UD2 // TODO!
+// afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr)
+TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-32
+	MOVQ executable+0(FP), CX
+	MOVQ executionContextPtr+8(FP), AX // First argument is passed in AX.
+
+	// Save the stack pointer and frame pointer.
+	MOVQ BP, 16(AX) // 16 == ExecutionContextOffsetOriginalFramePointer
+	MOVQ SP, 24(AX) // 24 == ExecutionContextOffsetOriginalStackPointer
+
+	// Then set the stack pointer and frame pointer to the values we got from the Go runtime.
+	MOVQ framePointer+24(FP), BP
+
+	// WARNING: do not update SP before BP, because the Go translates (FP) as (SP) + 8.
+	MOVQ stackPointer+16(FP), SP
+
+	JMP CX
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
index efb2ac76..fcc898e6 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
@@ -28,7 +28,7 @@ var (
 // CompileEntryPreamble implements backend.Machine.
 func (m *machine) CompileEntryPreamble(sig *ssa.Signature) []byte {
 	root := m.compileEntryPreamble(sig)
-	m.encodeWithoutRelResolution(root)
+	m.encodeWithoutSSA(root)
 	buf := m.c.Buf()
 	return buf
 }
@@ -46,10 +46,7 @@ func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction {
 	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)
+	cur = m.saveOriginalRSPRBP(cur)
 
 	// Now set the RSP to the Go-allocated stack pointer.
 	// 		mov %goAllocatedStackPtr, %rsp
@@ -97,16 +94,31 @@ func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction {
 	}
 
 	// 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)
+	cur = m.restoreOriginalRSPRBP(cur)
 
 	ret := m.allocateInstr().asRet(&abi)
 	linkInstr(cur, ret)
 	return root
 }
 
+// saveOriginalRSPRBP saves the original RSP and RBP into the execution context.
+func (m *machine) saveOriginalRSPRBP(cur *instruction) *instruction {
+	// 		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)
+	return cur
+}
+
+// restoreOriginalRSPRBP restores the original RSP and RBP from the execution context.
+func (m *machine) restoreOriginalRSPRBP(cur *instruction) *instruction {
+	// 		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)
+	return cur
+}
+
 func (m *machine) move64(src, dst regalloc.VReg, prev *instruction) *instruction {
 	mov := m.allocateInstr().asMovRR(src, dst, true)
 	return linkInstr(prev, mov)
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
index 69946355..9ab65c85 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
@@ -1,14 +1,272 @@
 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 calleeSavedVRegs = []regalloc.VReg{
+	rdxVReg, r12VReg, r13VReg, r14VReg, r15VReg,
+	xmm8VReg, xmm9VReg, xmm10VReg, xmm11VReg, xmm12VReg, xmm13VReg, xmm14VReg, xmm15VReg,
+}
+
 // 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)
+	exct := m.ectx
+	argBegin := 1 // Skips exec context by default.
+	if needModuleContextPtr {
+		argBegin++
+	}
+
+	abi := &backend.FunctionABI{}
+	abi.Init(sig, intArgResultRegs, floatArgResultRegs)
+	m.currentABI = abi
+
+	cur := m.allocateNop()
+	exct.RootInstr = cur
+
+	// Execution context is always the first argument.
+	execCtrPtr := raxVReg
+
+	// First we update RBP and RSP just like the normal prologue.
+	//
+	//                   (high address)                     (high address)
+	//       RBP ----> +-----------------+                +-----------------+
+	//                 |     .......     |                |     .......     |
+	//                 |      ret Y      |                |      ret Y      |
+	//                 |     .......     |                |     .......     |
+	//                 |      ret 0      |                |      ret 0      |
+	//                 |      arg X      |                |      arg X      |
+	//                 |     .......     |     ====>      |     .......     |
+	//                 |      arg 1      |                |      arg 1      |
+	//                 |      arg 0      |                |      arg 0      |
+	//                 |   Return Addr   |                |   Return Addr   |
+	//       RSP ----> +-----------------+                |    Caller_RBP   |
+	//                    (low address)                   +-----------------+ <----- RSP, RBP
+	//
+	cur = m.setupRBPRSP(cur)
+
+	goSliceSizeAligned, goSliceSizeAlignedUnaligned := backend.GoFunctionCallRequiredStackSize(sig, argBegin)
+	if !m.stackBoundsCheckDisabled { //nolint
+		// TODO: stack bounds check
+	}
+
+	// Save the callee saved registers.
+	cur = m.saveRegistersInExecutionContext(cur, execCtrPtr, calleeSavedVRegs)
+
+	if needModuleContextPtr {
+		moduleCtrPtr := rcxVReg // Module context is always the second argument.
+		mem := newAmodeImmReg(
+			wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque.U32(),
+			execCtrPtr)
+		store := m.allocateInstr().asMovRM(moduleCtrPtr, newOperandMem(mem), 8)
+		cur = linkInstr(cur, store)
+	}
+
+	// Now let's advance the RSP to the stack slot for the arguments.
+	//
+	//                (high address)                     (high address)
+	//              +-----------------+               +-----------------+
+	//              |     .......     |               |     .......     |
+	//              |      ret Y      |               |      ret Y      |
+	//              |     .......     |               |     .......     |
+	//              |      ret 0      |               |      ret 0      |
+	//              |      arg X      |               |      arg X      |
+	//              |     .......     |   =======>    |     .......     |
+	//              |      arg 1      |               |      arg 1      |
+	//              |      arg 0      |               |      arg 0      |
+	//              |   Return Addr   |               |   Return Addr   |
+	//              |    Caller_RBP   |               |    Caller_RBP   |
+	//  RBP,RSP --> +-----------------+               +-----------------+ <----- RBP
+	//                 (low address)                  |  arg[N]/ret[M]  |
+	//                                                |    ..........   |
+	//                                                |  arg[1]/ret[1]  |
+	//                                                |  arg[0]/ret[0]  |
+	//                                                +-----------------+ <----- RSP
+	//                                                   (low address)
+	//
+	// where the region of "arg[0]/ret[0] ... arg[N]/ret[M]" is the stack used by the Go functions,
+	// therefore will be accessed as the usual []uint64. So that's where we need to pass/receive
+	// the arguments/return values to/from Go function.
+	cur = m.addRSP(-int32(goSliceSizeAligned), cur)
+
+	// Next, we need to store all the arguments to the stack in the typical Wasm stack style.
+	var offsetInGoSlice int32
+	for i := range abi.Args[argBegin:] {
+		arg := &abi.Args[argBegin+i]
+		var v regalloc.VReg
+		if arg.Kind == backend.ABIArgKindReg {
+			v = arg.Reg
+		} else {
+			panic("TODO: stack arguments")
+		}
+
+		store := m.allocateInstr()
+		mem := newOperandMem(newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
+		switch arg.Type {
+		case ssa.TypeI32:
+			store.asMovRM(v, mem, 4)
+			offsetInGoSlice += 8 // always uint64 rep.
+		case ssa.TypeI64:
+			store.asMovRM(v, mem, 8)
+			offsetInGoSlice += 8
+		case ssa.TypeF32:
+			store.asXmmMovRM(sseOpcodeMovss, v, mem)
+			offsetInGoSlice += 8 // always uint64 rep.
+		case ssa.TypeF64:
+			store.asXmmMovRM(sseOpcodeMovsd, v, mem)
+			offsetInGoSlice += 8
+		case ssa.TypeV128:
+			store.asXmmMovRM(sseOpcodeMovdqu, v, mem)
+			offsetInGoSlice += 16
+		default:
+			panic("BUG")
+		}
+		cur = linkInstr(cur, store)
+	}
+
+	// Finally we push the size of the slice to the stack so the stack looks like:
+	//
+	//          (high address)
+	//       +-----------------+
+	//       |     .......     |
+	//       |      ret Y      |
+	//       |     .......     |
+	//       |      ret 0      |
+	//       |      arg X      |
+	//       |     .......     |
+	//       |      arg 1      |
+	//       |      arg 0      |
+	//       |   Return Addr   |
+	//       |    Caller_RBP   |
+	//       +-----------------+ <----- RBP
+	//       |  arg[N]/ret[M]  |
+	//       |    ..........   |
+	//       |  arg[1]/ret[1]  |
+	//       |  arg[0]/ret[0]  |
+	//       |    slice size   |
+	//       +-----------------+ <----- RSP
+	//         (low address)
+	//
+	// 		push $sliceSize
+	cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandImm32(uint32(goSliceSizeAlignedUnaligned))))
+
+	// Load the exitCode to the register.
+	exitCodeReg := r12VReg // Callee saved which is already saved.
+	cur = linkInstr(cur, m.allocateInstr().asImm(exitCodeReg, uint64(exitCode), false))
+
+	setExitCode, saveRsp, saveRbp := m.allocateExitInstructions(execCtrPtr, exitCodeReg)
+	cur = linkInstr(cur, setExitCode)
+	cur = linkInstr(cur, saveRsp)
+	cur = linkInstr(cur, saveRbp)
+
+	// Ready to exit the execution.
+	cur = m.storeReturnAddressAndExit(cur, execCtrPtr)
+
+	// After the call, we need to restore the callee saved registers.
+	cur = m.restoreRegistersInExecutionContext(cur, execCtrPtr, calleeSavedVRegs)
+
+	// We don't need the slice size anymore, so pop it.
+	cur = m.addRSP(8, cur)
+
+	// Ready to set up the results.
+	offsetInGoSlice = 0
+	for i := range abi.Rets {
+		r := &abi.Rets[i]
+		if r.Kind == backend.ABIArgKindReg {
+			v := r.Reg
+			load := m.allocateInstr()
+			mem := newOperandMem(newAmodeImmReg(uint32(offsetInGoSlice), rspVReg))
+			switch r.Type {
+			case ssa.TypeI32:
+				load.asMovzxRmR(extModeLQ, mem, v)
+				offsetInGoSlice += 8 // always uint64 rep.
+			case ssa.TypeI64:
+				load.asMov64MR(mem, v)
+				offsetInGoSlice += 8
+			case ssa.TypeF32:
+				load.asXmmUnaryRmR(sseOpcodeMovss, mem, v)
+				offsetInGoSlice += 8 // always uint64 rep.
+			case ssa.TypeF64:
+				load.asXmmUnaryRmR(sseOpcodeMovsd, mem, v)
+				offsetInGoSlice += 8
+			case ssa.TypeV128:
+				load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, v)
+				offsetInGoSlice += 16
+			default:
+				panic("BUG")
+			}
+			cur = linkInstr(cur, load)
+		} else {
+			panic("TODO: stack results")
+		}
+	}
+
+	// Finally ready to return.
+	cur = m.revertRBPRSP(cur)
+	linkInstr(cur, m.allocateInstr().asRet(nil))
+
+	m.encodeWithoutSSA(exct.RootInstr)
 	return m.c.Buf()
 }
+
+func (m *machine) saveRegistersInExecutionContext(cur *instruction, execCtx regalloc.VReg, regs []regalloc.VReg) *instruction {
+	offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64()
+	for _, v := range regs {
+		store := m.allocateInstr()
+		mem := newOperandMem(newAmodeImmReg(uint32(offset), execCtx))
+		switch v.RegType() {
+		case regalloc.RegTypeInt:
+			store.asMovRM(v, mem, 8)
+		case regalloc.RegTypeFloat:
+			store.asXmmMovRM(sseOpcodeMovdqu, v, mem)
+		default:
+			panic("BUG")
+		}
+		cur = linkInstr(cur, store)
+		offset += 16 // See execution context struct. Each register is 16 bytes-aligned unconditionally.
+	}
+	return cur
+}
+
+func (m *machine) restoreRegistersInExecutionContext(cur *instruction, execCtx regalloc.VReg, regs []regalloc.VReg) *instruction {
+	offset := wazevoapi.ExecutionContextOffsetSavedRegistersBegin.I64()
+	for _, v := range regs {
+		load := m.allocateInstr()
+		mem := newOperandMem(newAmodeImmReg(uint32(offset), execCtx))
+		switch v.RegType() {
+		case regalloc.RegTypeInt:
+			load.asMov64MR(mem, v)
+		case regalloc.RegTypeFloat:
+			load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, v)
+		default:
+			panic("BUG")
+		}
+		cur = linkInstr(cur, load)
+		offset += 16 // See execution context struct. Each register is 16 bytes-aligned unconditionally.
+	}
+	return cur
+}
+
+func (m *machine) storeReturnAddressAndExit(cur *instruction, execCtx regalloc.VReg) *instruction {
+	readRip := m.allocateInstr()
+	cur = linkInstr(cur, readRip)
+
+	ripReg := r12VReg // Callee saved which is already saved.
+	saveRip := m.allocateInstr().asMovRM(
+		ripReg,
+		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetGoCallReturnAddress.U32(), execCtx)),
+		8,
+	)
+	cur = linkInstr(cur, saveRip)
+
+	exit := m.allocateInstr().asExitSeq(execCtx)
+	cur = linkInstr(cur, exit)
+
+	nop, l := m.allocateBrTarget()
+	cur = linkInstr(cur, nop)
+	readRip.asLEA(newAmodeRipRelative(l), ripReg)
+	return cur
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index b058cf26..9726fb48 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -519,30 +519,36 @@ func (m *machine) lowerExitIfTrueWithCode(execCtx regalloc.VReg, cond ssa.Value,
 	jmpIf.asJmpIf(condFromSSAIntCmpCond(c).invert(), newOperandLabel(l))
 }
 
-func (m *machine) lowerExitWithCode(execCtx regalloc.VReg, code wazevoapi.ExitCode) (afterLabel backend.Label) {
-	// First we set the exit code in the execution context.
-	exitCodeReg := m.c.AllocateVReg(ssa.TypeI32)
-	m.lowerIconst(exitCodeReg, uint64(code), false)
-
-	setExitCode := m.allocateInstr().asMovRM(
+func (m *machine) allocateExitInstructions(execCtx, exitCodeReg regalloc.VReg) (setExitCode, saveRsp, saveRbp *instruction) {
+	setExitCode = m.allocateInstr().asMovRM(
 		exitCodeReg,
 		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetExitCodeOffset.U32(), execCtx)),
 		4,
 	)
-	m.insert(setExitCode)
-
-	// Next is to save RBP and RBP.
-	saveRsp := m.allocateInstr().asMovRM(
+	saveRsp = m.allocateInstr().asMovRM(
 		rspVReg,
 		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackPointerBeforeGoCall.U32(), execCtx)),
 		8,
 	)
-	m.insert(saveRsp)
-	saveRbp := m.allocateInstr().asMovRM(
+
+	saveRbp = m.allocateInstr().asMovRM(
 		rbpVReg,
 		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetFramePointerBeforeGoCall.U32(), execCtx)),
 		8,
 	)
+	return
+}
+
+func (m *machine) lowerExitWithCode(execCtx regalloc.VReg, code wazevoapi.ExitCode) (afterLabel backend.Label) {
+	// First we set the exit code in the execution context.
+	exitCodeReg := m.c.AllocateVReg(ssa.TypeI32)
+	m.lowerIconst(exitCodeReg, uint64(code), false)
+
+	setExitCode, saveRsp, saveRbp := m.allocateExitInstructions(execCtx, exitCodeReg)
+
+	// Set exit code, save RSP and RBP.
+	m.insert(setExitCode)
+	m.insert(saveRsp)
 	m.insert(saveRbp)
 
 	// Next is to save the return address.
@@ -621,8 +627,6 @@ func (m *machine) lowerShiftR(si *ssa.Instruction, op shiftROp) {
 		m.insert(alu)
 
 	} else {
-		println("::::::SONO QUI")
-
 		alu := m.allocateInstr()
 		alu.asShiftR(op, opAmt, tmpDst, _64)
 		m.insert(alu)
@@ -782,9 +786,40 @@ func (m *machine) Format() string {
 	return "\n" + strings.Join(lines, "\n") + "\n"
 }
 
-func (m *machine) encodeWithoutRelResolution(root *instruction) {
+func (m *machine) encodeWithoutSSA(root *instruction) {
+	m.labelResolutionPends = m.labelResolutionPends[:0]
+	ectx := m.ectx
+
+	bufPtr := m.c.BufPtr()
 	for cur := root; cur != nil; cur = cur.next {
-		cur.encode(m.c)
+		offset := int64(len(*bufPtr))
+		if cur.kind == nop0 {
+			l := cur.nop0Label()
+			if pos, ok := ectx.LabelPositions[l]; ok {
+				pos.BinaryOffset = offset
+			}
+		}
+
+		needLabelResolution := cur.encode(m.c)
+		if needLabelResolution {
+			m.labelResolutionPends = append(m.labelResolutionPends,
+				labelResolutionPend{instr: cur, imm32Offset: int64(len(*bufPtr)) - 4},
+			)
+		}
+	}
+
+	for i := range m.labelResolutionPends {
+		p := &m.labelResolutionPends[i]
+		switch p.instr.kind {
+		case jmp, jmpIf, lea:
+			target := p.instr.jmpLabel()
+			targetOffset := ectx.LabelPositions[target].BinaryOffset
+			imm32Offset := p.imm32Offset
+			jmpOffset := int32(targetOffset - (p.imm32Offset + 4)) // +4 because RIP points to the next instruction.
+			binary.LittleEndian.PutUint32((*bufPtr)[imm32Offset:], uint32(jmpOffset))
+		default:
+			panic("BUG")
+		}
 	}
 }
 
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
index 0a5f56ef..f12eb494 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go
@@ -6,7 +6,7 @@ import "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 func (m *machine) CompileStackGrowCallSequence() []byte {
 	// TODO
 	ud2 := m.allocateInstr().asUD2()
-	m.encodeWithoutRelResolution(ud2)
+	m.encodeWithoutSSA(ud2)
 	return m.c.Buf()
 }
 
@@ -47,10 +47,7 @@ func (m *machine) SetupPrologue() {
 	//       RSP ----> +-----------------+                |    Caller_RBP   |
 	//                    (low address)                   +-----------------+ <----- RSP, RBP
 	//
-	// 		push %rbp
-	// 		mov %rsp, %rbp
-	cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(rbpVReg)))
-	cur = linkInstr(cur, m.allocateInstr().asMovRR(rspVReg, rbpVReg, true))
+	cur = m.setupRBPRSP(cur)
 
 	if !m.stackBoundsCheckDisabled { //nolint
 		// TODO: stack bounds check
@@ -84,8 +81,7 @@ func (m *machine) SetupPrologue() {
 				cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(r)))
 			} else {
 				// Push the XMM register is not supported by the PUSH instruction.
-				spDec := m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(uint32(16)), rspVReg, true)
-				cur = linkInstr(cur, spDec)
+				cur = m.addRSP(-16, cur)
 				push := m.allocateInstr().asXmmMovRM(
 					sseOpcodeMovdqu, r, newOperandMem(newAmodeImmReg(0, rspVReg)),
 				)
@@ -215,19 +211,37 @@ func (m *machine) setupEpilogueAfter(cur *instruction) {
 					sseOpcodeMovdqu, newOperandMem(newAmodeImmReg(0, rspVReg)), r,
 				)
 				cur = linkInstr(cur, pop)
-				spInc := m.allocateInstr().asAluRmiR(
-					aluRmiROpcodeAdd, newOperandImm32(uint32(16)), rspVReg, true,
-				)
-				cur = linkInstr(cur, spInc)
+				cur = m.addRSP(16, cur)
 			}
 		}
 	}
 
 	// Now roll back the RSP to RBP, and pop the caller's RBP.
-	// 		mov  %rbp, %rsp
-	// 		pop  %rbp
-	cur = linkInstr(cur, m.allocateInstr().asMovRR(rbpVReg, rspVReg, true))
-	cur = linkInstr(cur, m.allocateInstr().asPop64(rbpVReg))
+	cur = m.revertRBPRSP(cur)
 
 	linkInstr(cur, prevNext)
 }
+
+func (m *machine) addRSP(offset int32, cur *instruction) *instruction {
+	if offset == 0 {
+		return cur
+	}
+	opcode := aluRmiROpcodeAdd
+	if offset < 0 {
+		opcode = aluRmiROpcodeSub
+		offset = -offset
+	}
+	return linkInstr(cur, m.allocateInstr().asAluRmiR(opcode, newOperandImm32(uint32(offset)), rspVReg, true))
+}
+
+func (m *machine) setupRBPRSP(cur *instruction) *instruction {
+	cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(rbpVReg)))
+	cur = linkInstr(cur, m.allocateInstr().asMovRR(rspVReg, rbpVReg, true))
+	return cur
+}
+
+func (m *machine) revertRBPRSP(cur *instruction) *instruction {
+	cur = linkInstr(cur, m.allocateInstr().asMovRR(rbpVReg, rspVReg, true))
+	cur = linkInstr(cur, m.allocateInstr().asPop64(rbpVReg))
+	return cur
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
index abccf553..6e7845ad 100644
--- a/internal/engine/wazevo/backend/isa/amd64/operands.go
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -65,10 +65,6 @@ func (o *operand) format(_64 bool) string {
 	}
 }
 
-func newOperandLabel(label backend.Label) operand { //nolint:unused
-	return operand{kind: operandKindLabel, imm32: uint32(label)}
-}
-
 func (o *operand) label() backend.Label {
 	switch o.kind {
 	case operandKindLabel:
@@ -81,16 +77,20 @@ func (o *operand) label() backend.Label {
 	panic("BUG: invalid operand kind")
 }
 
+func newOperandLabel(label backend.Label) operand {
+	return operand{kind: operandKindLabel, imm32: uint32(label), r: regalloc.VRegInvalid}
+}
+
 func newOperandReg(r regalloc.VReg) operand {
 	return operand{kind: operandKindReg, r: r}
 }
 
 func newOperandImm32(imm32 uint32) operand {
-	return operand{kind: operandKindImm32, imm32: imm32}
+	return operand{kind: operandKindImm32, imm32: imm32, r: regalloc.VRegInvalid}
 }
 
 func newOperandMem(amode amode) operand {
-	return operand{kind: operandKindMem, amode: amode}
+	return operand{kind: operandKindMem, amode: amode, r: regalloc.VRegInvalid}
 }
 
 // amode is a memory operand (addressing mode).
diff --git a/internal/engine/wazevo/backend/isa/amd64/stack.go b/internal/engine/wazevo/backend/isa/amd64/stack.go
index a695f05b..50449244 100644
--- a/internal/engine/wazevo/backend/isa/amd64/stack.go
+++ b/internal/engine/wazevo/backend/isa/amd64/stack.go
@@ -59,7 +59,25 @@ func UnwindStack(_, rbp, top uintptr, returnAddresses []uintptr) []uintptr {
 
 // GoCallStackView implements wazevo.goCallStackView.
 func GoCallStackView(stackPointerBeforeGoCall *uint64) []uint64 {
-	panic("implement me")
+	//                  (high address)
+	//              +-----------------+ <----+
+	//              |   xxxxxxxxxxx   |      | ;; optional unused space to make it 16-byte aligned.
+	//           ^  |  arg[N]/ret[M]  |      |
+	// sliceSize |  |  ............   |      | SizeInBytes/8
+	//           |  |  arg[1]/ret[1]  |      |
+	//           v  |  arg[0]/ret[0]  | <----+
+	//              |   SizeInBytes   |
+	//              +-----------------+ <---- stackPointerBeforeGoCall
+	//                 (low address)
+	size := *stackPointerBeforeGoCall / 8
+	var view []uint64
+	{
+		sh := (*reflect.SliceHeader)(unsafe.Pointer(&view))
+		sh.Data = uintptr(unsafe.Pointer(stackPointerBeforeGoCall)) + 8 // skips the(sliceSize.
+		sh.Len = int(size)
+		sh.Cap = int(size)
+	}
+	return view
 }
 
 func AdjustStackAfterGrown(oldRsp, rsp, rbp, top uintptr) {
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go b/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go
index 89fb433a..5f0c613d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go
@@ -6,4 +6,4 @@ func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPt
 
 // 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)
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr)
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s b/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s
index 082954ca..0b579f85 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s
@@ -13,7 +13,7 @@ TEXT ·entrypoint(SB), NOSPLIT|NOFRAME, $0-48
 	MOVD goAllocatedStackSlicePtr+40(FP), R26
 	JMP  (R27)
 
-TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-24
+TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-32
 	MOVD goCallReturnAddress+0(FP), R20
 	MOVD executionContextPtr+8(FP), R0
 	MOVD stackPointer+16(FP), R19
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go
index 7b10a821..5cf9e1b9 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go
@@ -66,7 +66,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	const frameInfoSize = 16 // == frame_size + sliceSize.
 
 	// Next, we should allocate the stack for the Go function call if necessary.
-	goCallStackSize, sliceSizeInBytes := goFunctionCallRequiredStackSize(sig, argBegin)
+	goCallStackSize, sliceSizeInBytes := backend.GoFunctionCallRequiredStackSize(sig, argBegin)
 	cur = m.insertStackBoundsCheck(goCallStackSize+frameInfoSize, cur)
 
 	originalArg0Reg := x17VReg // Caller save, so we can use it for whatever we want.
@@ -78,7 +78,6 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	// Save the callee saved registers.
 	cur = m.saveRegistersInExecutionContext(cur, calleeSavedRegistersSorted)
 
-	// Next, we need to store all the arguments to the stack in the typical Wasm stack style.
 	if needModuleContextPtr {
 		offset := wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque.I64()
 		if !offsetFitsInAddressModeKindRegUnsignedImm12(64, offset) {
@@ -102,6 +101,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	copySp.asMove64(arg0ret0AddrReg, spVReg)
 	cur = linkInstr(cur, copySp)
 
+	// Next, we need to store all the arguments to the stack in the typical Wasm stack style.
 	for i := range abi.Args[argBegin:] {
 		arg := &abi.Args[argBegin+i]
 		store := m.allocateInstr()
@@ -373,35 +373,6 @@ func (m *machine) saveCurrentStackPointer(cur *instruction, execCtr regalloc.VRe
 	return cur
 }
 
-// goFunctionCallRequiredStackSize returns the size of the stack required for the Go function call.
-func goFunctionCallRequiredStackSize(sig *ssa.Signature, argBegin int) (ret, retUnaligned int64) {
-	var paramNeededInBytes, resultNeededInBytes int64
-	for _, p := range sig.Params[argBegin:] {
-		s := int64(p.Size())
-		if s < 8 {
-			s = 8 // We use uint64 for all basic types, except SIMD v128.
-		}
-		paramNeededInBytes += s
-	}
-	for _, r := range sig.Results {
-		s := int64(r.Size())
-		if s < 8 {
-			s = 8 // We use uint64 for all basic types, except SIMD v128.
-		}
-		resultNeededInBytes += s
-	}
-
-	if paramNeededInBytes > resultNeededInBytes {
-		ret = paramNeededInBytes
-	} else {
-		ret = resultNeededInBytes
-	}
-	retUnaligned = ret
-	// Align to 16 bytes.
-	ret = (ret + 15) &^ 15
-	return
-}
-
 func (m *machine) goFunctionCallLoadStackArg(cur *instruction, originalArg0Reg regalloc.VReg, arg *backend.ABIArg, intVReg, floatVReg regalloc.VReg) (*instruction, regalloc.VReg) {
 	load := m.allocateInstr()
 	var result regalloc.VReg
diff --git a/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go b/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go
index adb44b26..f9550420 100644
--- a/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go
+++ b/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go
@@ -72,11 +72,12 @@ func GoCallStackView(stackPointerBeforeGoCall *uint64) []uint64 {
 	//              |   frame_size    |
 	//              +-----------------+ <---- stackPointerBeforeGoCall
 	//                 (low address)
-	size := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(stackPointerBeforeGoCall)) + 8))
+	ptr := unsafe.Pointer(stackPointerBeforeGoCall)
+	size := *(*uint64)(unsafe.Add(ptr, 8))
 	var view []uint64
 	{
 		sh := (*reflect.SliceHeader)(unsafe.Pointer(&view))
-		sh.Data = uintptr(unsafe.Pointer(stackPointerBeforeGoCall)) + 16 // skips the (frame_size, sliceSize).
+		sh.Data = uintptr(unsafe.Add(ptr, 16)) // skips the (frame_size, sliceSize).
 		sh.Len = int(size)
 		sh.Cap = int(size)
 	}
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 475dbf5b..05815d2f 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -256,7 +256,7 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			}
 			adjustStackAfterGrown(oldsp, oldsp, newsp, newfp)
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp)
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp, newfp)
 		case wazevoapi.ExitCodeGrowMemory:
 			mod := c.callerModuleInstance()
 			mem := mod.MemoryInstance
@@ -277,7 +277,7 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 				}
 			}
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeTableGrow:
 			mod := c.callerModuleInstance()
 			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
@@ -285,14 +285,16 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			table := mod.Tables[tableIndex]
 			s[0] = uint64(uint32(int32(table.Grow(num, ref))))
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallGoFunction:
 			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
 			f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
 			f.Call(ctx, goCallStackView(c.execCtx.stackPointerBeforeGoCall))
 			// Back to the native code.
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallGoFunctionWithListener:
 			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
 			f := hostModuleGoFuncFromOpaque[api.GoFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
@@ -310,7 +312,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			listener.After(ctx, callerModule, def, s)
 			// Back to the native code.
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallGoModuleFunction:
 			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
 			f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
@@ -318,7 +321,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			f.Call(ctx, mod, goCallStackView(c.execCtx.stackPointerBeforeGoCall))
 			// Back to the native code.
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallGoModuleFunctionWithListener:
 			index := wazevoapi.GoFunctionIndexFromExitCode(ec)
 			f := hostModuleGoFuncFromOpaque[api.GoModuleFunction](index, c.execCtx.goFunctionCallCalleeModuleContextOpaque)
@@ -336,7 +340,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			listener.After(ctx, callerModule, def, s)
 			// Back to the native code.
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallListenerBefore:
 			stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
 			index := stack[0]
@@ -345,7 +350,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			def := mod.Source.FunctionDefinition(wasm.Index(index))
 			listener.Before(ctx, mod, def, stack[1:], c.stackIterator(false))
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCallListenerAfter:
 			stack := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
 			index := stack[0]
@@ -354,7 +360,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			def := mod.Source.FunctionDefinition(wasm.Index(index))
 			listener.After(ctx, mod, def, stack[1:])
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeCheckModuleExitCode:
 			// Note: this operation must be done in Go, not native code. The reason is that
 			// native code cannot be preempted and that means it can block forever if there are not
@@ -363,7 +370,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 				panic(err)
 			}
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeRefFunc:
 			mod := c.callerModuleInstance()
 			s := goCallStackView(c.execCtx.stackPointerBeforeGoCall)
@@ -371,7 +379,8 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			ref := mod.Engine.FunctionInstanceReference(wasm.Index(funcIndex))
 			s[0] = uint64(ref)
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
-			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)))
+			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr,
+				uintptr(unsafe.Pointer(c.execCtx.stackPointerBeforeGoCall)), c.execCtx.framePointerBeforeGoCall)
 		case wazevoapi.ExitCodeUnreachable:
 			panic(wasmruntime.ErrRuntimeUnreachable)
 		case wazevoapi.ExitCodeMemoryOutOfBounds:
diff --git a/internal/engine/wazevo/entrypoint_amd64.go b/internal/engine/wazevo/entrypoint_amd64.go
index 9093673d..6cb0c457 100644
--- a/internal/engine/wazevo/entrypoint_amd64.go
+++ b/internal/engine/wazevo/entrypoint_amd64.go
@@ -10,4 +10,4 @@ func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPt
 // 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)
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr)
diff --git a/internal/engine/wazevo/entrypoint_arm64.go b/internal/engine/wazevo/entrypoint_arm64.go
index 95ceeff5..0b8b5533 100644
--- a/internal/engine/wazevo/entrypoint_arm64.go
+++ b/internal/engine/wazevo/entrypoint_arm64.go
@@ -10,4 +10,4 @@ func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPt
 // entrypoint is implemented by the backend.
 //
 //go:linkname afterGoFunctionCallEntrypoint github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64.afterGoFunctionCallEntrypoint
-func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer uintptr)
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr)
diff --git a/internal/engine/wazevo/entrypoint_others.go b/internal/engine/wazevo/entrypoint_others.go
index 0bbfc400..f6d3a6b3 100644
--- a/internal/engine/wazevo/entrypoint_others.go
+++ b/internal/engine/wazevo/entrypoint_others.go
@@ -10,6 +10,6 @@ func entrypoint(preambleExecutable, functionExecutable *byte, executionContextPt
 	panic(runtime.GOARCH)
 }
 
-func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer uintptr) {
+func afterGoFunctionCallEntrypoint(executable *byte, executionContextPtr uintptr, stackPointer, framePointer uintptr) {
 	panic(runtime.GOARCH)
 }


Test patch:
diff --git a/internal/engine/wazevo/backend/go_call_test.go b/internal/engine/wazevo/backend/go_call_test.go
new file mode 100644
index 00000000..9b50b5a3
--- /dev/null
+++ b/internal/engine/wazevo/backend/go_call_test.go
@@ -0,0 +1,55 @@
+package backend
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func Test_goFunctionCallRequiredStackSize(t *testing.T) {
+	for _, tc := range []struct {
+		name     string
+		sig      *ssa.Signature
+		argBegin int
+		exp      int64
+	}{
+		{
+			name: "no param",
+			sig:  &ssa.Signature{},
+			exp:  0,
+		},
+		{
+			name: "only param",
+			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128}},
+			exp:  32,
+		},
+		{
+			name: "only result",
+			sig:  &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}},
+			exp:  32,
+		},
+		{
+			name: "param < result",
+			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}},
+			exp:  32,
+		},
+		{
+			name: "param > result",
+			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128}},
+			exp:  32,
+		},
+		{
+			name:     "param < result / argBegin=2",
+			argBegin: 2,
+			sig:      &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64}},
+			exp:      16,
+		},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			requiredSize, _ := GoFunctionCallRequiredStackSize(tc.sig, tc.argBegin)
+			require.Equal(t, tc.exp, requiredSize)
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi_go_call_test.go b/internal/engine/wazevo/backend/isa/amd64/abi_go_call_test.go
new file mode 100644
index 00000000..309dcc05
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call_test.go
@@ -0,0 +1,210 @@
+package amd64
+
+import (
+	"context"
+	"testing"
+
+	"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"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func Test_calleeSavedVRegs(t *testing.T) {
+	var exp []regalloc.VReg
+	regInfo.CalleeSavedRegisters.Range(func(r regalloc.RealReg) {
+		exp = append(exp, regInfo.RealRegToVReg[r])
+	})
+	require.Equal(t, exp, calleeSavedVRegs)
+}
+
+func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
+	for _, tc := range []struct {
+		name                 string
+		exitCode             wazevoapi.ExitCode
+		sig                  *ssa.Signature
+		needModuleContextPtr bool
+		exp                  string
+	}{
+		{
+			name:     "go call",
+			exitCode: wazevoapi.ExitCodeCallGoFunctionWithIndex(100, false),
+			sig: &ssa.Signature{
+				Params:  []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeF64},
+				Results: []ssa.Type{ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64},
+			},
+			needModuleContextPtr: true,
+			exp: `
+	pushq %rbp
+	movq %rsp, %rbp
+	mov.q %rdx, 96(%rax)
+	mov.q %r12, 112(%rax)
+	mov.q %r13, 128(%rax)
+	mov.q %r14, 144(%rax)
+	mov.q %r15, 160(%rax)
+	movdqu %xmm8, 176(%rax)
+	movdqu %xmm9, 192(%rax)
+	movdqu %xmm10, 208(%rax)
+	movdqu %xmm11, 224(%rax)
+	movdqu %xmm12, 240(%rax)
+	movdqu %xmm13, 256(%rax)
+	movdqu %xmm14, 272(%rax)
+	movdqu %xmm15, 288(%rax)
+	mov.q %rcx, 1120(%rax)
+	sub $32, %rsp
+	movsd %xmm0, (%rsp)
+	pushq $32
+	movl $25606, %r12d
+	mov.l %r12, (%rax)
+	mov.q %rsp, 56(%rax)
+	mov.q %rbp, 1152(%rax)
+	lea L1(%rip), %r12
+	mov.q %r12, 48(%rax)
+	exit_sequence %rax
+L1:
+	movq 96(%rax), %rdx
+	movq 112(%rax), %r12
+	movq 128(%rax), %r13
+	movq 144(%rax), %r14
+	movq 160(%rax), %r15
+	movdqu 176(%rax), %xmm8
+	movdqu 192(%rax), %xmm9
+	movdqu 208(%rax), %xmm10
+	movdqu 224(%rax), %xmm11
+	movdqu 240(%rax), %xmm12
+	movdqu 256(%rax), %xmm13
+	movdqu 272(%rax), %xmm14
+	movdqu 288(%rax), %xmm15
+	add $8, %rsp
+	movzx.lq (%rsp), %rax
+	movq 8(%rsp), %rcx
+	movss 16(%rsp), %xmm0
+	movsd 24(%rsp), %xmm1
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`,
+		},
+		{
+			name:     "go call",
+			exitCode: wazevoapi.ExitCodeCallGoFunctionWithIndex(100, false),
+			sig: &ssa.Signature{
+				Params:  []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeF64, ssa.TypeF64, ssa.TypeI32, ssa.TypeI32},
+				Results: []ssa.Type{},
+			},
+			needModuleContextPtr: true,
+			exp: `
+	pushq %rbp
+	movq %rsp, %rbp
+	mov.q %rdx, 96(%rax)
+	mov.q %r12, 112(%rax)
+	mov.q %r13, 128(%rax)
+	mov.q %r14, 144(%rax)
+	mov.q %r15, 160(%rax)
+	movdqu %xmm8, 176(%rax)
+	movdqu %xmm9, 192(%rax)
+	movdqu %xmm10, 208(%rax)
+	movdqu %xmm11, 224(%rax)
+	movdqu %xmm12, 240(%rax)
+	movdqu %xmm13, 256(%rax)
+	movdqu %xmm14, 272(%rax)
+	movdqu %xmm15, 288(%rax)
+	mov.q %rcx, 1120(%rax)
+	sub $32, %rsp
+	movsd %xmm0, (%rsp)
+	movsd %xmm1, 8(%rsp)
+	mov.l %rbx, 16(%rsp)
+	mov.l %rsi, 24(%rsp)
+	pushq $32
+	movl $25606, %r12d
+	mov.l %r12, (%rax)
+	mov.q %rsp, 56(%rax)
+	mov.q %rbp, 1152(%rax)
+	lea L1(%rip), %r12
+	mov.q %r12, 48(%rax)
+	exit_sequence %rax
+L1:
+	movq 96(%rax), %rdx
+	movq 112(%rax), %r12
+	movq 128(%rax), %r13
+	movq 144(%rax), %r14
+	movq 160(%rax), %r15
+	movdqu 176(%rax), %xmm8
+	movdqu 192(%rax), %xmm9
+	movdqu 208(%rax), %xmm10
+	movdqu 224(%rax), %xmm11
+	movdqu 240(%rax), %xmm12
+	movdqu 256(%rax), %xmm13
+	movdqu 272(%rax), %xmm14
+	movdqu 288(%rax), %xmm15
+	add $8, %rsp
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`,
+		},
+		{
+			name:     "grow memory",
+			exitCode: wazevoapi.ExitCodeGrowMemory,
+			sig: &ssa.Signature{
+				Params:  []ssa.Type{ssa.TypeI32, ssa.TypeI32},
+				Results: []ssa.Type{ssa.TypeI32},
+			},
+			exp: `
+	pushq %rbp
+	movq %rsp, %rbp
+	mov.q %rdx, 96(%rax)
+	mov.q %r12, 112(%rax)
+	mov.q %r13, 128(%rax)
+	mov.q %r14, 144(%rax)
+	mov.q %r15, 160(%rax)
+	movdqu %xmm8, 176(%rax)
+	movdqu %xmm9, 192(%rax)
+	movdqu %xmm10, 208(%rax)
+	movdqu %xmm11, 224(%rax)
+	movdqu %xmm12, 240(%rax)
+	movdqu %xmm13, 256(%rax)
+	movdqu %xmm14, 272(%rax)
+	movdqu %xmm15, 288(%rax)
+	sub $16, %rsp
+	mov.l %rcx, (%rsp)
+	pushq $8
+	movl $2, %r12d
+	mov.l %r12, (%rax)
+	mov.q %rsp, 56(%rax)
+	mov.q %rbp, 1152(%rax)
+	lea L1(%rip), %r12
+	mov.q %r12, 48(%rax)
+	exit_sequence %rax
+L1:
+	movq 96(%rax), %rdx
+	movq 112(%rax), %r12
+	movq 128(%rax), %r13
+	movq 144(%rax), %r14
+	movq 160(%rax), %r15
+	movdqu 176(%rax), %xmm8
+	movdqu 192(%rax), %xmm9
+	movdqu 208(%rax), %xmm10
+	movdqu 224(%rax), %xmm11
+	movdqu 240(%rax), %xmm12
+	movdqu 256(%rax), %xmm13
+	movdqu 272(%rax), %xmm14
+	movdqu 288(%rax), %xmm15
+	add $8, %rsp
+	movzx.lq (%rsp), %rax
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.CompileGoFunctionTrampoline(tc.exitCode, tc.sig, tc.needModuleContextPtr)
+
+			require.Equal(t, tc.exp, m.Format())
+
+			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 1a1d9e25..29fe5c83 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
@@ -456,53 +456,6 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 	}
 }
 
-func Test_goFunctionCallRequiredStackSize(t *testing.T) {
-	for _, tc := range []struct {
-		name     string
-		sig      *ssa.Signature
-		argBegin int
-		exp      int64
-	}{
-		{
-			name: "no param",
-			sig:  &ssa.Signature{},
-			exp:  0,
-		},
-		{
-			name: "only param",
-			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128}},
-			exp:  32,
-		},
-		{
-			name: "only result",
-			sig:  &ssa.Signature{Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}},
-			exp:  32,
-		},
-		{
-			name: "param < result",
-			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}},
-			exp:  32,
-		},
-		{
-			name: "param > result",
-			sig:  &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeV128}},
-			exp:  32,
-		},
-		{
-			name:     "param < result / argBegin=2",
-			argBegin: 2,
-			sig:      &ssa.Signature{Params: []ssa.Type{ssa.TypeI64, ssa.TypeV128, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64, ssa.TypeF64}},
-			exp:      16,
-		},
-	} {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			requiredSize, _ := goFunctionCallRequiredStackSize(tc.sig, tc.argBegin)
-			require.Equal(t, tc.exp, requiredSize)
-		})
-	}
-}
-
 func Test_goFunctionCallLoadStackArg(t *testing.T) {
 	originalArg0Reg := x17VReg
 	intVReg := x11VReg
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index d7af892f..0ec43076 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -258,17 +258,15 @@ func TestE2E(t *testing.T) {
 			}}},
 		},
 		{
-			name:      "memory_size_grow",
-			m:         testcases.MemorySizeGrow.Module,
-			skipAMD64: true,
-			calls:     []callCase{{expResults: []uint64{1, 2, 0xffffffff}}},
+			name:  "memory_size_grow",
+			m:     testcases.MemorySizeGrow.Module,
+			calls: []callCase{{expResults: []uint64{1, 2, 0xffffffff}}},
 		},
 		{
-			name:      "imported_memory_grow",
-			imported:  testcases.ImportedMemoryGrow.Imported,
-			m:         testcases.ImportedMemoryGrow.Module,
-			skipAMD64: true,
-			calls:     []callCase{{expResults: []uint64{1, 1, 11, 11}}},
+			name:     "imported_memory_grow",
+			imported: testcases.ImportedMemoryGrow.Imported,
+			m:        testcases.ImportedMemoryGrow.Module,
+			calls:    []callCase{{expResults: []uint64{1, 1, 11, 11}}},
 		},
 		{
 			name: "call_indirect",