Repository: tetratelabs/wazero (go)

Issue:
flaky test: internal/engine/wazevo.TestE2E on amd64
To repro:
```
$GOARCH=amd64 go test ./internal/engine/wazevo -race -run=TestE2E -count=1000
```



Pull request description:
wazevo(amd64): stack grow support
closes #1944 

Patch:
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 9ab65c85..34c51653 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go
@@ -14,7 +14,7 @@ var calleeSavedVRegs = []regalloc.VReg{
 
 // CompileGoFunctionTrampoline implements backend.Machine.
 func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *ssa.Signature, needModuleContextPtr bool) []byte {
-	exct := m.ectx
+	ectx := m.ectx
 	argBegin := 1 // Skips exec context by default.
 	if needModuleContextPtr {
 		argBegin++
@@ -25,7 +25,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	m.currentABI = abi
 
 	cur := m.allocateNop()
-	exct.RootInstr = cur
+	ectx.RootInstr = cur
 
 	// Execution context is always the first argument.
 	execCtrPtr := raxVReg
@@ -49,9 +49,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	cur = m.setupRBPRSP(cur)
 
 	goSliceSizeAligned, goSliceSizeAlignedUnaligned := backend.GoFunctionCallRequiredStackSize(sig, argBegin)
-	if !m.stackBoundsCheckDisabled { //nolint
-		// TODO: stack bounds check
-	}
+	cur = m.insertStackBoundsCheck(goSliceSizeAligned+8 /* size of the Go slice */, cur)
 
 	// Save the callee saved registers.
 	cur = m.saveRegistersInExecutionContext(cur, execCtrPtr, calleeSavedVRegs)
@@ -208,7 +206,7 @@ func (m *machine) CompileGoFunctionTrampoline(exitCode wazevoapi.ExitCode, sig *
 	cur = m.revertRBPRSP(cur)
 	linkInstr(cur, m.allocateInstr().asRet(nil))
 
-	m.encodeWithoutSSA(exct.RootInstr)
+	m.encodeWithoutSSA(ectx.RootInstr)
 	return m.c.Buf()
 }
 
@@ -270,3 +268,110 @@ func (m *machine) storeReturnAddressAndExit(cur *instruction, execCtx regalloc.V
 	readRip.asLEA(newAmodeRipRelative(l), ripReg)
 	return cur
 }
+
+// saveRequiredRegs is the set of registers that must be saved/restored during growing stack when there's insufficient
+// stack space left. Basically this is the all allocatable registers except for RSP and RBP, and RAX which contains the
+// execution context pointer. ExecCtx pointer is always the first argument so we don't need to save it.
+var stackGrowSaveVRegs = []regalloc.VReg{
+	rdxVReg, r12VReg, r13VReg, r14VReg, r15VReg,
+	rcxVReg, rbxVReg, rsiVReg, rdiVReg, r8VReg, r9VReg, r10VReg, r11VReg,
+	xmm8VReg, xmm9VReg, xmm10VReg, xmm11VReg, xmm12VReg, xmm13VReg, xmm14VReg, xmm15VReg,
+	xmm0VReg, xmm1VReg, xmm2VReg, xmm3VReg, xmm4VReg, xmm5VReg, xmm6VReg, xmm7VReg,
+}
+
+// CompileStackGrowCallSequence implements backend.Machine.
+func (m *machine) CompileStackGrowCallSequence() []byte {
+	ectx := m.ectx
+
+	cur := m.allocateNop()
+	ectx.RootInstr = cur
+
+	cur = m.setupRBPRSP(cur)
+
+	// Execution context is always the first argument.
+	execCtrPtr := raxVReg
+
+	// Save the callee saved and argument registers.
+	cur = m.saveRegistersInExecutionContext(cur, execCtrPtr, stackGrowSaveVRegs)
+
+	// Load the exitCode to the register.
+	exitCodeReg := r12VReg // Already saved.
+	cur = linkInstr(cur, m.allocateInstr().asImm(exitCodeReg, uint64(wazevoapi.ExitCodeGrowStack), 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 exit, restore the saved registers.
+	cur = m.restoreRegistersInExecutionContext(cur, execCtrPtr, stackGrowSaveVRegs)
+
+	// Finally ready to return.
+	cur = m.revertRBPRSP(cur)
+	linkInstr(cur, m.allocateInstr().asRet(nil))
+
+	m.encodeWithoutSSA(ectx.RootInstr)
+	return m.c.Buf()
+}
+
+// insertStackBoundsCheck will insert the instructions after `cur` to check the
+// stack bounds, and if there's no sufficient spaces required for the function,
+// exit the execution and try growing it in Go world.
+func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instruction) *instruction {
+	//		add $requiredStackSize, %rsp ;; Temporarily update the sp.
+	// 		cmp ExecutionContextOffsetStackBottomPtr(%rax), %rsp ;; Compare the stack bottom and the sp.
+	// 		ja .ok
+	//		sub $requiredStackSize, %rsp ;; Reverse the temporary update.
+	//      pushq r15 ;; save the temporary.
+	//		mov $requiredStackSize, %r15
+	//		mov %15, ExecutionContextOffsetStackGrowRequiredSize(%rax) ;; Set the required size in the execution context.
+	//      popq r15 ;; restore the temporary.
+	//		callq *ExecutionContextOffsetStackGrowCallTrampolineAddress(%rax) ;; Call the Go function to grow the stack.
+	//		jmp .cont
+	// .ok:
+	//		sub $requiredStackSize, %rsp ;; Reverse the temporary update.
+	// .cont:
+	cur = m.addRSP(-int32(requiredStackSize), cur)
+	cur = linkInstr(cur, m.allocateInstr().asCmpRmiR(true,
+		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackBottomPtr.U32(), raxVReg)),
+		rspVReg, true))
+
+	ja := m.allocateInstr()
+	cur = linkInstr(cur, ja)
+
+	cur = m.addRSP(int32(requiredStackSize), cur)
+
+	// Save the temporary.
+
+	cur = linkInstr(cur, m.allocateInstr().asPush64(newOperandReg(r15VReg)))
+	// Load the required size to the temporary.
+	cur = linkInstr(cur, m.allocateInstr().asImm(r15VReg, uint64(requiredStackSize), true))
+	// Set the required size in the execution context.
+	cur = linkInstr(cur, m.allocateInstr().asMovRM(r15VReg,
+		newOperandMem(newAmodeImmReg(wazevoapi.ExecutionContextOffsetStackGrowRequiredSize.U32(), raxVReg)), 8))
+	// Restore the temporary.
+	cur = linkInstr(cur, m.allocateInstr().asPop64(r15VReg))
+	// Call the Go function to grow the stack.
+	cur = linkInstr(cur, m.allocateInstr().asCallIndirect(newOperandMem(newAmodeImmReg(
+		wazevoapi.ExecutionContextOffsetStackGrowCallTrampolineAddress.U32(), raxVReg)), nil))
+	// Jump to the continuation.
+	jmpToCont := m.allocateInstr()
+	cur = linkInstr(cur, jmpToCont)
+
+	// .ok:
+	okInstr, ok := m.allocateBrTarget()
+	cur = linkInstr(cur, okInstr)
+	ja.asJmpIf(condNBE, newOperandLabel(ok))
+	// On the ok path, we only need to reverse the temporary update.
+	cur = m.addRSP(int32(requiredStackSize), cur)
+
+	// .cont:
+	contInstr, cont := m.allocateBrTarget()
+	cur = linkInstr(cur, contInstr)
+	jmpToCont.asJmp(newOperandLabel(cont))
+
+	return cur
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 9726fb48..0f6e5b81 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -674,7 +674,7 @@ func (m *machine) lowerCall(si *ssa.Instruction) {
 
 	stackSlotSize := calleeABI.AlignedArgResultStackSlotSize()
 	if m.maxRequiredStackSizeForCalls < stackSlotSize+16 {
-		m.maxRequiredStackSizeForCalls = stackSlotSize + 16 // return address frame.
+		m.maxRequiredStackSizeForCalls = stackSlotSize + 16 // 16 == return address + RBP.
 	}
 
 	for i, arg := range args {
@@ -945,3 +945,22 @@ func (m *machine) copyToTmp(v regalloc.VReg) regalloc.VReg {
 	m.copyTo(v, tmp)
 	return tmp
 }
+
+func (m *machine) requiredStackSize() int64 {
+	return m.maxRequiredStackSizeForCalls +
+		m.frameSize() +
+		16 + // Need for stack checking.
+		16 // return address and the caller RBP.
+}
+
+func (m *machine) frameSize() int64 {
+	s := m.clobberedRegSlotSize() + m.spillSlotSize
+	if s&0xf != 0 {
+		panic(fmt.Errorf("BUG: frame size %d is not 16-byte aligned", s))
+	}
+	return s
+}
+
+func (m *machine) clobberedRegSlotSize() int64 {
+	return int64(len(m.clobberedRegs) * 16)
+}
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 f12eb494..5ba414cb 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
@@ -2,14 +2,6 @@ package amd64
 
 import "github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 
-// CompileStackGrowCallSequence implements backend.Machine.
-func (m *machine) CompileStackGrowCallSequence() []byte {
-	// TODO
-	ud2 := m.allocateInstr().asUD2()
-	m.encodeWithoutSSA(ud2)
-	return m.c.Buf()
-}
-
 // SetupPrologue implements backend.Machine.
 func (m *machine) SetupPrologue() {
 	cur := m.ectx.RootInstr
@@ -49,8 +41,8 @@ func (m *machine) SetupPrologue() {
 	//
 	cur = m.setupRBPRSP(cur)
 
-	if !m.stackBoundsCheckDisabled { //nolint
-		// TODO: stack bounds check
+	if !m.stackBoundsCheckDisabled {
+		cur = m.insertStackBoundsCheck(m.requiredStackSize(), cur)
 	}
 
 	//
diff --git a/internal/engine/wazevo/backend/isa/amd64/stack.go b/internal/engine/wazevo/backend/isa/amd64/stack.go
index 50449244..f113ec75 100644
--- a/internal/engine/wazevo/backend/isa/amd64/stack.go
+++ b/internal/engine/wazevo/backend/isa/amd64/stack.go
@@ -83,8 +83,8 @@ func GoCallStackView(stackPointerBeforeGoCall *uint64) []uint64 {
 func AdjustStackAfterGrown(oldRsp, rsp, rbp, top uintptr) {
 	diff := uint64(rsp - oldRsp)
 
-	stackBuf := stackView(rbp, top)
-	for i := uint64(0); i < uint64(len(stackBuf)); {
+	newBuf := stackView(rbp, top)
+	for i := uint64(0); i < uint64(len(newBuf)); {
 		//       (high address)
 		//    +-----------------+
 		//    |     .......     |
@@ -110,9 +110,13 @@ func AdjustStackAfterGrown(oldRsp, rsp, rbp, top uintptr) {
 		//    +-----------------+ <---- RBP
 		//       (low address)
 
-		callerRBP := binary.LittleEndian.Uint64(stackBuf[i:])
+		callerRBP := binary.LittleEndian.Uint64(newBuf[i:])
+		if callerRBP == 0 {
+			// End of stack.
+			break
+		}
 		adjustedCallerRBP := callerRBP + diff
-		binary.LittleEndian.PutUint64(stackBuf[i:], adjustedCallerRBP)
+		binary.LittleEndian.PutUint64(newBuf[i:], adjustedCallerRBP)
 		i = adjustedCallerRBP - uint64(rbp)
 	}
 }
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
index 3ec69038..46841e94 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go
@@ -345,7 +345,7 @@ func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instructi
 		cur = linkInstr(cur, sub)
 	}
 
-	tmp2 := x11VReg // Callee save, so it is safe to use it here in the prologue.
+	tmp2 := x11VReg // Caller save, so it is safe to use it here in the prologue.
 
 	// ldr tmp2, [executionContext #StackBottomPtr]
 	ldr := m.allocateInstr()
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 05815d2f..a302d390 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -86,7 +86,7 @@ type (
 )
 
 func (c *callEngine) requiredInitialStackSize() int {
-	const initialStackSizeDefault = 512
+	const initialStackSizeDefault = 10240
 	stackSize := initialStackSizeDefault
 	paramResultInBytes := c.sizeOfParamResultSlice * 8 * 2 // * 8 because uint64 is 8 bytes, and *2 because we need both separated param/result slots.
 	required := paramResultInBytes + 32 + 16               // 32 is enough to accommodate the call frame info, and 16 exists just in case when []byte is not aligned to 16 bytes.
@@ -254,7 +254,7 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
 			if err != nil {
 				return err
 			}
-			adjustStackAfterGrown(oldsp, oldsp, newsp, newfp)
+			adjustStackAfterGrown(oldsp, newsp, newfp, c.stackTop)
 			c.execCtx.exitCode = wazevoapi.ExitCodeOK
 			afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, newsp, newfp)
 		case wazevoapi.ExitCodeGrowMemory:


Test patch:
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
index 309dcc05..5e401696 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_go_call_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_go_call_test.go
@@ -2,6 +2,7 @@ package amd64
 
 import (
 	"context"
+	"sort"
 	"testing"
 
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
@@ -37,6 +38,19 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 			exp: `
 	pushq %rbp
 	movq %rsp, %rbp
+	sub $40, %rsp
+	cmpq 40(%rax), %rsp
+	jnbe L1
+	add $40, %rsp
+	pushq %r15
+	movabsq $40, %r15
+	mov.q %r15, 64(%rax)
+	popq %r15
+	callq *80(%rax)
+	jmp L2
+L1:
+	add $40, %rsp
+L2:
 	mov.q %rdx, 96(%rax)
 	mov.q %r12, 112(%rax)
 	mov.q %r13, 128(%rax)
@@ -58,10 +72,10 @@ func TestMachine_CompileGoFunctionTrampoline(t *testing.T) {
 	mov.l %r12, (%rax)
 	mov.q %rsp, 56(%rax)
 	mov.q %rbp, 1152(%rax)
-	lea L1(%rip), %r12
+	lea L3(%rip), %r12
 	mov.q %r12, 48(%rax)
 	exit_sequence %rax
-L1:
+L3:
 	movq 96(%rax), %rdx
 	movq 112(%rax), %r12
 	movq 128(%rax), %r13
@@ -96,6 +110,19 @@ L1:
 			exp: `
 	pushq %rbp
 	movq %rsp, %rbp
+	sub $40, %rsp
+	cmpq 40(%rax), %rsp
+	jnbe L1
+	add $40, %rsp
+	pushq %r15
+	movabsq $40, %r15
+	mov.q %r15, 64(%rax)
+	popq %r15
+	callq *80(%rax)
+	jmp L2
+L1:
+	add $40, %rsp
+L2:
 	mov.q %rdx, 96(%rax)
 	mov.q %r12, 112(%rax)
 	mov.q %r13, 128(%rax)
@@ -120,10 +147,10 @@ L1:
 	mov.l %r12, (%rax)
 	mov.q %rsp, 56(%rax)
 	mov.q %rbp, 1152(%rax)
-	lea L1(%rip), %r12
+	lea L3(%rip), %r12
 	mov.q %r12, 48(%rax)
 	exit_sequence %rax
-L1:
+L3:
 	movq 96(%rax), %rdx
 	movq 112(%rax), %r12
 	movq 128(%rax), %r13
@@ -153,6 +180,19 @@ L1:
 			exp: `
 	pushq %rbp
 	movq %rsp, %rbp
+	sub $24, %rsp
+	cmpq 40(%rax), %rsp
+	jnbe L1
+	add $24, %rsp
+	pushq %r15
+	movabsq $24, %r15
+	mov.q %r15, 64(%rax)
+	popq %r15
+	callq *80(%rax)
+	jmp L2
+L1:
+	add $24, %rsp
+L2:
 	mov.q %rdx, 96(%rax)
 	mov.q %r12, 112(%rax)
 	mov.q %r13, 128(%rax)
@@ -173,10 +213,10 @@ L1:
 	mov.l %r12, (%rax)
 	mov.q %rsp, 56(%rax)
 	mov.q %rbp, 1152(%rax)
-	lea L1(%rip), %r12
+	lea L3(%rip), %r12
 	mov.q %r12, 48(%rax)
 	exit_sequence %rax
-L1:
+L3:
 	movq 96(%rax), %rdx
 	movq 112(%rax), %r12
 	movq 128(%rax), %r13
@@ -208,3 +248,152 @@ L1:
 		})
 	}
 }
+
+func Test_stackGrowSaveVRegs(t *testing.T) {
+	var exp []regalloc.VReg
+	for _, rs := range regInfo.AllocatableRegisters {
+		for _, r := range rs {
+			if r != rsp && r != rbp && r != rax {
+				exp = append(exp, regInfo.RealRegToVReg[r])
+			}
+		}
+	}
+	// Copy stackGrowSaveVRegs to avoid modifying the original.
+	var actual []regalloc.VReg
+	actual = append(actual, stackGrowSaveVRegs...)
+	sort.Slice(exp, func(i, j int) bool { return exp[i].ID() < exp[j].ID() })
+	sort.Slice(actual, func(i, j int) bool { return actual[i].ID() < actual[j].ID() })
+	require.Equal(t, exp, actual)
+}
+
+func TestMachine_CompileStackGrowCallSequence(t *testing.T) {
+	_, _, m := newSetupWithMockContext()
+	_ = m.CompileStackGrowCallSequence()
+
+	require.Equal(t, `
+	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)
+	mov.q %rcx, 176(%rax)
+	mov.q %rbx, 192(%rax)
+	mov.q %rsi, 208(%rax)
+	mov.q %rdi, 224(%rax)
+	mov.q %r8, 240(%rax)
+	mov.q %r9, 256(%rax)
+	mov.q %r10, 272(%rax)
+	mov.q %r11, 288(%rax)
+	movdqu %xmm8, 304(%rax)
+	movdqu %xmm9, 320(%rax)
+	movdqu %xmm10, 336(%rax)
+	movdqu %xmm11, 352(%rax)
+	movdqu %xmm12, 368(%rax)
+	movdqu %xmm13, 384(%rax)
+	movdqu %xmm14, 400(%rax)
+	movdqu %xmm15, 416(%rax)
+	movdqu %xmm0, 432(%rax)
+	movdqu %xmm1, 448(%rax)
+	movdqu %xmm2, 464(%rax)
+	movdqu %xmm3, 480(%rax)
+	movdqu %xmm4, 496(%rax)
+	movdqu %xmm5, 512(%rax)
+	movdqu %xmm6, 528(%rax)
+	movdqu %xmm7, 544(%rax)
+	movl $1, %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
+	movq 176(%rax), %rcx
+	movq 192(%rax), %rbx
+	movq 208(%rax), %rsi
+	movq 224(%rax), %rdi
+	movq 240(%rax), %r8
+	movq 256(%rax), %r9
+	movq 272(%rax), %r10
+	movq 288(%rax), %r11
+	movdqu 304(%rax), %xmm8
+	movdqu 320(%rax), %xmm9
+	movdqu 336(%rax), %xmm10
+	movdqu 352(%rax), %xmm11
+	movdqu 368(%rax), %xmm12
+	movdqu 384(%rax), %xmm13
+	movdqu 400(%rax), %xmm14
+	movdqu 416(%rax), %xmm15
+	movdqu 432(%rax), %xmm0
+	movdqu 448(%rax), %xmm1
+	movdqu 464(%rax), %xmm2
+	movdqu 480(%rax), %xmm3
+	movdqu 496(%rax), %xmm4
+	movdqu 512(%rax), %xmm5
+	movdqu 528(%rax), %xmm6
+	movdqu 544(%rax), %xmm7
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`, m.Format())
+}
+
+func TestMachine_insertStackBoundsCheck(t *testing.T) {
+	for _, tc := range []struct {
+		exp               string
+		requiredStackSize int64
+	}{
+		{
+			requiredStackSize: 0x1000,
+			exp: `
+	sub $4096, %rsp
+	cmpq 40(%rax), %rsp
+	jnbe L1
+	add $4096, %rsp
+	pushq %r15
+	movabsq $4096, %r15
+	mov.q %r15, 64(%rax)
+	popq %r15
+	callq *80(%rax)
+	jmp L2
+L1:
+	add $4096, %rsp
+L2:
+`,
+		},
+		{
+			requiredStackSize: 0x10,
+			exp: `
+	sub $16, %rsp
+	cmpq 40(%rax), %rsp
+	jnbe L1
+	add $16, %rsp
+	pushq %r15
+	movabsq $16, %r15
+	mov.q %r15, 64(%rax)
+	popq %r15
+	callq *80(%rax)
+	jmp L2
+L1:
+	add $16, %rsp
+L2:
+`,
+		},
+	} {
+		tc := tc
+		t.Run(tc.exp, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			m.ectx.RootInstr = m.allocateNop()
+			m.insertStackBoundsCheck(tc.requiredStackSize, m.ectx.RootInstr)
+			m.Encode(context.Background())
+			require.Equal(t, tc.exp, m.Format())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/call_engine_test.go b/internal/engine/wazevo/call_engine_test.go
index 0a3e2438..9d790801 100644
--- a/internal/engine/wazevo/call_engine_test.go
+++ b/internal/engine/wazevo/call_engine_test.go
@@ -59,9 +59,9 @@ func TestCallEngine_growStack(t *testing.T) {
 
 func TestCallEngine_requiredInitialStackSize(t *testing.T) {
 	c := &callEngine{}
-	require.Equal(t, 512, c.requiredInitialStackSize())
+	require.Equal(t, 10240, c.requiredInitialStackSize())
 	c.sizeOfParamResultSlice = 10
-	require.Equal(t, 512, c.requiredInitialStackSize())
-	c.sizeOfParamResultSlice = 120
-	require.Equal(t, 120*16+32+16, c.requiredInitialStackSize())
+	require.Equal(t, 10240, c.requiredInitialStackSize())
+	c.sizeOfParamResultSlice = 1000
+	require.Equal(t, 1000*16+32+16, c.requiredInitialStackSize())
 }
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index b5688c7c..0435029e 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -172,7 +172,6 @@ func TestE2E(t *testing.T) {
 				CodeSection:     []wasm.Code{{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}},
 				ExportSection:   []wasm.Export{{Name: testcases.ExportedFunctionName, Index: 0, Type: wasm.ExternTypeFunc}},
 			},
-			skipAMD64: true,
 			calls: []callCase{
 				{expErr: "stack overflow"}, {expErr: "stack overflow"}, {expErr: "stack overflow"}, {expErr: "stack overflow"},
 			},
@@ -750,7 +749,6 @@ wasm stack trace:
 }
 
 func TestListener_local(t *testing.T) {
-	skipOnAmd64(t)
 	var buf bytes.Buffer
 	config := newRuntimeConfigOptimizingCompiler()
 	ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf))
@@ -958,7 +956,6 @@ 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 := newRuntimeConfigOptimizingCompiler()
 	ctx := context.Background()