Repository: tetratelabs/wazero
Pull request #1935 (2024-01-22 20:20:09)

wazevo(amd64): stack-based args/results 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/abi.go b/internal/engine/wazevo/backend/isa/amd64/abi.go
index 7b721364..00b762ef 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi.go
@@ -65,7 +65,48 @@ func (m *machine) LowerParams(args []ssa.Value) {
 		if arg.Kind == backend.ABIArgKindReg {
 			m.InsertMove(reg, arg.Reg, arg.Type)
 		} else {
-			panic("TODO: stack param")
+			//
+			//            (high address)
+			//          +-----------------+
+			//          |     .......     |
+			//          |      ret Y      |
+			//          |     .......     |
+			//          |      ret 0      |
+			//          |      arg X      |
+			//          |     .......     |
+			//          |      arg 1      |
+			//          |      arg 0      |
+			//          |   ReturnAddress |
+			//          |    Caller_RBP   |
+			//          +-----------------+ <-- RBP
+			//          |   ...........   |
+			//          |   clobbered  M  |
+			//          |   ............  |
+			//          |   clobbered  0  |
+			//          |   spill slot N  |
+			//          |   ...........   |
+			//          |   spill slot 0  |
+			//   RSP--> +-----------------+
+			//             (low address)
+
+			// Load the value from the arg stack slot above the current RBP.
+			load := m.allocateInstr()
+			mem := newOperandMem(newAmodeImmRBPReg(uint32(arg.Offset + 16)))
+			switch arg.Type {
+			case ssa.TypeI32:
+				load.asMovzxRmR(extModeLQ, mem, reg)
+			case ssa.TypeI64:
+				load.asMov64MR(mem, reg)
+			case ssa.TypeF32:
+				load.asXmmUnaryRmR(sseOpcodeMovss, mem, reg)
+			case ssa.TypeF64:
+				load.asXmmUnaryRmR(sseOpcodeMovsd, mem, reg)
+			case ssa.TypeV128:
+				load.asXmmUnaryRmR(sseOpcodeMovdqu, mem, reg)
+			default:
+				panic("BUG")
+			}
+			m.insert(load)
 		}
 	}
 }
@@ -89,7 +130,46 @@ func (m *machine) LowerReturns(rets []ssa.Value) {
 		if r.Kind == backend.ABIArgKindReg {
 			m.InsertMove(r.Reg, reg, ret.Type())
 		} else {
-			panic("TODO")
+			//
+			//            (high address)
+			//          +-----------------+
+			//          |     .......     |
+			//          |      ret Y      |
+			//          |     .......     |
+			//          |      ret 0      |
+			//          |      arg X      |
+			//          |     .......     |
+			//          |      arg 1      |
+			//          |      arg 0      |
+			//          |   ReturnAddress |
+			//          |    Caller_RBP   |
+			//          +-----------------+ <-- RBP
+			//          |   ...........   |
+			//          |   clobbered  M  |
+			//          |   ............  |
+			//          |   clobbered  0  |
+			//          |   spill slot N  |
+			//          |   ...........   |
+			//          |   spill slot 0  |
+			//   RSP--> +-----------------+
+			//             (low address)
+
+			// Store the value to the return stack slot above the current RBP.
+			store := m.allocateInstr()
+			mem := newOperandMem(newAmodeImmRBPReg(uint32(a.ArgStackSize + 16 + r.Offset)))
+			switch r.Type {
+			case ssa.TypeI32:
+				store.asMovRM(reg, mem, 4)
+			case ssa.TypeI64:
+				store.asMovRM(reg, mem, 8)
+			case ssa.TypeF32:
+				store.asXmmMovRM(sseOpcodeMovss, reg, mem)
+			case ssa.TypeF64:
+				store.asXmmMovRM(sseOpcodeMovsd, reg, mem)
+			case ssa.TypeV128:
+				store.asXmmMovRM(sseOpcodeMovdqu, reg, mem)
+			}
+			m.insert(store)
 		}
 	}
 }
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 27691fb2..6fd4a208 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s
@@ -4,11 +4,11 @@
 // 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 functionExectuable+8(FP), R14
+	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), R12
-	MOVQ goAllocatedStackSlicePtr+40(FP), BX
+	MOVQ goAllocatedStackSlicePtr+40(FP), R13
 	JMP  R11
 
 TEXT ·afterGoFunctionCallEntrypoint(SB), NOSPLIT|NOFRAME, $0-24
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 e54e6f5d..efb2ac76 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go
@@ -9,14 +9,20 @@ import (
 
 var (
 	executionContextPtrReg = raxVReg
+
+	// Followings are callee saved registers. They can be used freely in the entry preamble
+	// since the preamble is called via Go assembly function which has stack-based ABI.
+
 	// 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 must match with entrypoint function in abi_entry_amd64.s.
 	paramResultSlicePtr = r12VReg
-	// goAllocatedStackPtr is not used in the epilogue.
-	goAllocatedStackPtr = rbxVReg
-	// functionExecutable is not used in the epilogue.
-	functionExecutable = rdiVReg
+	// goAllocatedStackPtr must match with entrypoint function in abi_entry_amd64.s.
+	goAllocatedStackPtr = r13VReg
+	// functionExecutable must match with entrypoint function in abi_entry_amd64.s.
+	functionExecutable = r14VReg
+	tmpIntReg          = r15VReg
+	tmpXmmReg          = xmm15VReg
 )
 
 // CompileEntryPreamble implements backend.Machine.
@@ -82,7 +88,7 @@ func (m *machine) compileEntryPreamble(sig *ssa.Signature) *instruction {
 	offset = 0
 	for i := range abi.Rets {
 		r := &abi.Rets[i]
-		cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, offset, r)
+		cur = m.goEntryPreamblePassResult(cur, paramResultSlicePtr, offset, r, uint32(abi.ArgStackSize))
 		if r.Type == ssa.TypeV128 {
 			offset += 16
 		} else {
@@ -123,10 +129,20 @@ func (m *machine) linkUD2(cur *instruction) *instruction { //nolint
 }
 
 func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regalloc.VReg, offsetInParamSlice uint32, arg *backend.ABIArg) *instruction {
-	dst := arg.Reg
+	var dst regalloc.VReg
+	argTyp := arg.Type
 	if arg.Kind == backend.ABIArgKindStack {
-		// Temporary reg.
-		panic("TODO")
+		// Caller saved registers ca
+		switch argTyp {
+		case ssa.TypeI32, ssa.TypeI64:
+			dst = tmpIntReg
+		case ssa.TypeF32, ssa.TypeF64, ssa.TypeV128:
+			dst = tmpXmmReg
+		default:
+			panic("BUG")
+		}
+	} else {
+		dst = arg.Reg
 	}
 
 	load := m.allocateInstr()
@@ -144,23 +160,61 @@ func (m *machine) goEntryPreamblePassArg(cur *instruction, paramSlicePtr regallo
 		load.asXmmUnaryRmR(sseOpcodeMovdqu, a, dst)
 	}
 
+	cur = linkInstr(cur, load)
 	if arg.Kind == backend.ABIArgKindStack {
-		// store back.
-		panic("TODO")
+		// Store back to the stack.
+		store := m.allocateInstr()
+		a := newOperandMem(newAmodeImmReg(uint32(arg.Offset), rspVReg))
+		switch arg.Type {
+		case ssa.TypeI32:
+			store.asMovRM(dst, a, 4)
+		case ssa.TypeI64:
+			store.asMovRM(dst, a, 8)
+		case ssa.TypeF32:
+			store.asXmmMovRM(sseOpcodeMovss, dst, a)
+		case ssa.TypeF64:
+			store.asXmmMovRM(sseOpcodeMovsd, dst, a)
+		case ssa.TypeV128:
+			store.asXmmMovRM(sseOpcodeMovdqu, dst, a)
+		}
+		cur = linkInstr(cur, store)
 	}
-
-	return linkInstr(cur, load)
+	return cur
 }
 
-func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, offset uint32, result *backend.ABIArg) *instruction {
+func (m *machine) goEntryPreamblePassResult(cur *instruction, resultSlicePtr regalloc.VReg, offsetInResultSlice uint32, result *backend.ABIArg, resultStackSlotBeginOffset uint32) *instruction {
+	var r regalloc.VReg
 	if result.Kind == backend.ABIArgKindStack {
-		panic("TODO")
+		// Load the value to the temporary.
+		load := m.allocateInstr()
+		offset := resultStackSlotBeginOffset + uint32(result.Offset)
+		a := newOperandMem(newAmodeImmReg(offset, rspVReg))
+		switch result.Type {
+		case ssa.TypeI32:
+			r = tmpIntReg
+			load.asMovzxRmR(extModeLQ, a, r)
+		case ssa.TypeI64:
+			r = tmpIntReg
+			load.asMov64MR(a, r)
+		case ssa.TypeF32:
+			r = tmpXmmReg
+			load.asXmmUnaryRmR(sseOpcodeMovss, a, r)
+		case ssa.TypeF64:
+			r = tmpXmmReg
+			load.asXmmUnaryRmR(sseOpcodeMovsd, a, r)
+		case ssa.TypeV128:
+			r = tmpXmmReg
+			load.asXmmUnaryRmR(sseOpcodeMovdqu, a, r)
+		default:
+			panic("BUG")
+		}
+		cur = linkInstr(cur, load)
+	} else {
+		r = result.Reg
 	}
 
-	r := result.Reg
-
 	store := m.allocateInstr()
-	a := newOperandMem(newAmodeImmReg(offset, resultSlicePtr))
+	a := newOperandMem(newAmodeImmReg(offsetInResultSlice, resultSlicePtr))
 	switch result.Type {
 	case ssa.TypeI32:
 		store.asMovRM(r, a, 4)
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 284b8f34..4fb0e137 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -360,7 +360,7 @@ func (i *instruction) AssignDef(reg regalloc.VReg) {
 // IsCopy implements regalloc.Instr.
 func (i *instruction) IsCopy() bool {
 	k := i.kind
-	return k == movRR || k == xmmUnaryRmR
+	return k == movRR || (k == xmmUnaryRmR && i.op1.kind == operandKindReg)
 }
 
 func resetInstruction(i *instruction) {
@@ -1514,9 +1514,12 @@ var defKinds = [instrMax]defKind{
 	ret:         defKindNone,
 	movRR:       defKindOp2,
 	movRM:       defKindNone,
+	xmmMovRM:    defKindNone,
 	aluRmiR:     defKindNone,
 	imm:         defKindOp2,
 	xmmUnaryRmR: defKindOp2,
+	mov64MR:     defKindOp2,
+	movzxRmR:    defKindOp2,
 	gprToXmm:    defKindOp2,
 	call:        defKindCall,
 }
@@ -1549,9 +1552,12 @@ var useKinds = [instrMax]useKind{
 	ret:         useKindNone,
 	movRR:       useKindOp1,
 	movRM:       useKindOp1,
+	xmmMovRM:    useKindOp1,
 	aluRmiR:     useKindOp1Op2,
 	imm:         useKindNone,
 	xmmUnaryRmR: useKindOp1,
+	mov64MR:     useKindOp1,
+	movzxRmR:    useKindOp1,
 	gprToXmm:    useKindOp1,
 	call:        useKindCall,
 }
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index a4b63695..36456fdd 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -1057,7 +1057,7 @@ func encodeEncMem(
 	)
 
 	switch m.kind {
-	case amodeImmReg:
+	case amodeImmReg, amodeImmRBP:
 		base := m.base.RealReg()
 		baseEnc := regEncodings[base]
 
@@ -1140,6 +1140,8 @@ func encodeEncMem(
 		// https://wiki.osdev.org/X86-64_Instruction_Encoding#32.2F64-bit_addressing
 		c.EmitByte(encodeModRM(0b00, regEncoding(r), 0b101))
 		c.Emit4Bytes(m.imm32)
+	default:
+		panic("BUG: invalid addressing mode")
 	}
 }
 
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_constant.go b/internal/engine/wazevo/backend/isa/amd64/lower_constant.go
index 3f2e60af..9d32392b 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_constant.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_constant.go
@@ -38,7 +38,7 @@ func (m *machine) InsertLoadConstant(instr *ssa.Instruction, vr regalloc.VReg) {
 
 func (m *machine) lowerFconst(dst regalloc.VReg, c uint64, _64 bool) {
 	if c == 0 {
-		xor := m.allocateInstr().asXmmRmR(sseOpcodeXorpd, operand{kind: operandKindReg, r: dst}, dst, _64)
+		xor := m.allocateInstr().asXmmRmR(sseOpcodeXorpd, newOperandReg(dst), dst, _64)
 		m.insert(xor)
 	} else {
 		var tmpType ssa.Type
@@ -51,7 +51,7 @@ func (m *machine) lowerFconst(dst regalloc.VReg, c uint64, _64 bool) {
 		loadToGP := m.allocateInstr().asImm(tmpInt, c, _64)
 		m.insert(loadToGP)
 
-		movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, operand{kind: operandKindReg, r: tmpInt}, dst, _64)
+		movToXmm := m.allocateInstr().asGprToXmm(sseOpcodeMovq, newOperandReg(tmpInt), dst, _64)
 		m.insert(movToXmm)
 	}
 }
@@ -59,7 +59,7 @@ func (m *machine) lowerFconst(dst regalloc.VReg, c uint64, _64 bool) {
 func (m *machine) lowerIconst(dst regalloc.VReg, c uint64, _64 bool) {
 	i := m.allocateInstr()
 	if c == 0 {
-		i.asAluRmiR(aluRmiROpcodeXor, operand{kind: operandKindReg, r: dst}, dst, _64)
+		i.asAluRmiR(aluRmiROpcodeXor, newOperandReg(dst), dst, _64)
 	} else {
 		i.asImm(dst, c, _64)
 	}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index c8df65e9..557a2e46 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -19,8 +19,9 @@ func NewBackend() backend.Machine {
 		asNop,
 	)
 	return &machine{
-		ectx:     ectx,
-		regAlloc: regalloc.NewAllocator(regInfo),
+		ectx:       ectx,
+		regAlloc:   regalloc.NewAllocator(regInfo),
+		spillSlots: map[regalloc.VRegID]int64{},
 	}
 }
 
@@ -36,6 +37,7 @@ type (
 		regAllocStarted bool
 
 		spillSlotSize int64
+		spillSlots    map[regalloc.VRegID]int64
 		currentABI    *backend.FunctionABI
 		clobberedRegs []regalloc.VReg
 
@@ -52,12 +54,24 @@ type (
 
 // Reset implements backend.Machine.
 func (m *machine) Reset() {
+	m.clobberedRegs = m.clobberedRegs[:0]
+	for key := range m.spillSlots {
+		m.clobberedRegs = append(m.clobberedRegs, regalloc.VReg(key))
+	}
+	for _, key := range m.clobberedRegs {
+		delete(m.spillSlots, regalloc.VRegID(key))
+	}
+
 	m.stackBoundsCheckDisabled = false
 	m.ectx.Reset()
 
 	m.regAllocFn.Reset()
 	m.regAlloc.Reset()
 	m.regAllocStarted = false
+	m.clobberedRegs = m.clobberedRegs[:0]
+
+	m.spillSlotSize = 0
+	m.maxRequiredStackSizeForCalls = 0
 }
 
 // ExecutableContext implements backend.Machine.
@@ -364,14 +378,11 @@ func (m *machine) Encode(context.Context) {
 // ResolveRelocations implements backend.Machine.
 func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, binary []byte, relocations []backend.RelocationInfo) {
 	for _, r := range relocations {
-		instrOffset := r.Offset
+		offset := r.Offset
 		calleeFnOffset := refToBinaryOffset[r.FuncRef]
-		// calleeFnOffset points to the beginning of call target function.
-		// call is 5 bytes where the last 4 bytes represent the signed 32-bit offset. See the encoding of `call` instruction.
-		// instrOffset is the offset of the last 4 bytes.
-		callInstrOffsetBytes := binary[instrOffset : instrOffset+4]
-		diff := int64(calleeFnOffset) - (instrOffset)
-		// We backpatch in-place the relative value `diff`.
+		// offset is the offset of the last 4 bytes of the call instruction.
+		callInstrOffsetBytes := binary[offset : offset+4]
+		diff := int64(calleeFnOffset) - (offset)
 		callInstrOffsetBytes[0] = byte(diff)
 		callInstrOffsetBytes[1] = byte(diff >> 8)
 		callInstrOffsetBytes[2] = byte(diff >> 16)
@@ -409,3 +420,13 @@ func (m *machine) allocateBrTarget() (nop *instruction, l backend.Label) { //nol
 	ectx.LabelPositions[l] = pos
 	return
 }
+
+func (m *machine) getVRegSpillSlotOffsetFromSP(id regalloc.VRegID, size byte) int64 {
+	offset, ok := m.spillSlots[id]
+	if !ok {
+		offset = m.spillSlotSize
+		m.spillSlots[id] = offset
+		m.spillSlotSize += int64(size)
+	}
+	return offset
+}
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 2c485381..0a5f56ef 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
@@ -95,7 +95,34 @@ func (m *machine) SetupPrologue() {
 	}
 
 	if size := m.spillSlotSize; size > 0 {
-		panic("TODO: allocate spill slots")
+		// Simply decrease the RSP to allocate the spill slots.
+		// 		sub $size, %rsp
+		cur = linkInstr(cur, m.allocateInstr().asAluRmiR(aluRmiROpcodeSub, newOperandImm32(uint32(size)), rspVReg, true))
+
+		// 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)
 	}
 
 	linkInstr(cur, prevInitInst)
@@ -152,7 +179,9 @@ func (m *machine) setupEpilogueAfter(cur *instruction) {
 	//             (low address)
 
 	if size := m.spillSlotSize; size > 0 {
-		panic("TODO: deallocate spill slots")
+		// Simply increase the RSP to free the spill slots.
+		// 		add $size, %rsp
+		cur = linkInstr(cur, m.allocateInstr().asAluRmiR(aluRmiROpcodeAdd, newOperandImm32(uint32(size)), rspVReg, true))
 	}
 
 	//
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
index ccf58e63..8f961301 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
@@ -14,14 +14,74 @@ func (m *machine) InsertMoveBefore(dst, src regalloc.VReg, instr *instruction) {
 
 // InsertStoreRegisterAt implements backend.RegAllocFunctionMachine.
 func (m *machine) InsertStoreRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction {
-	// TODO implement me
-	panic("implement me")
+	if !v.IsRealReg() {
+		panic("BUG: VReg must be backed by real reg to be stored")
+	}
+
+	typ := m.c.TypeOf(v)
+
+	var prevNext, cur *instruction
+	if after {
+		cur, prevNext = instr, instr.next
+	} else {
+		cur, prevNext = instr.prev, instr
+	}
+
+	offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size())
+	store := m.allocateInstr()
+	mem := newOperandMem(newAmodeImmReg(uint32(offsetFromSP), rspVReg))
+	switch typ {
+	case ssa.TypeI32:
+		store.asMovRM(v, mem, 4)
+	case ssa.TypeI64:
+		store.asMovRM(v, mem, 8)
+	case ssa.TypeF32:
+		store.asXmmMovRM(sseOpcodeMovss, v, mem)
+	case ssa.TypeF64:
+		store.asXmmMovRM(sseOpcodeMovsd, v, mem)
+	case ssa.TypeV128:
+		store.asXmmMovRM(sseOpcodeMovdqu, v, mem)
+	}
+
+	cur = linkInstr(cur, store)
+	return linkInstr(cur, prevNext)
 }
 
 // InsertReloadRegisterAt implements backend.RegAllocFunctionMachine.
 func (m *machine) InsertReloadRegisterAt(v regalloc.VReg, instr *instruction, after bool) *instruction {
-	// TODO implement me
-	panic("implement me")
+	if !v.IsRealReg() {
+		panic("BUG: VReg must be backed by real reg to be stored")
+	}
+
+	typ := m.c.TypeOf(v)
+	var prevNext, cur *instruction
+	if after {
+		cur, prevNext = instr, instr.next
+	} else {
+		cur, prevNext = instr.prev, instr
+	}
+
+	// Load the value to the temporary.
+	load := m.allocateInstr()
+	offsetFromSP := m.getVRegSpillSlotOffsetFromSP(v.ID(), typ.Size())
+	a := newOperandMem(newAmodeImmReg(uint32(offsetFromSP), rspVReg))
+	switch typ {
+	case ssa.TypeI32:
+		load.asMovzxRmR(extModeLQ, a, v)
+	case ssa.TypeI64:
+		load.asMov64MR(a, v)
+	case ssa.TypeF32:
+		load.asXmmUnaryRmR(sseOpcodeMovss, a, v)
+	case ssa.TypeF64:
+		load.asXmmUnaryRmR(sseOpcodeMovsd, a, v)
+	case ssa.TypeV128:
+		load.asXmmUnaryRmR(sseOpcodeMovdqu, a, v)
+	default:
+		panic("BUG")
+	}
+
+	cur = linkInstr(cur, load)
+	return linkInstr(cur, prevNext)
 }
 
 // ClobberedRegisters implements backend.RegAllocFunctionMachine.
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
index abac7abd..20cbee47 100644
--- a/internal/engine/wazevo/backend/isa/amd64/operands.go
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -103,6 +103,11 @@ const (
 	// amodeRegRegShift calculates sign-extend-32-to-64(Immediate) + base
 	amodeImmReg amodeKind = iota + 1
 
+	// amodeImmRBP is the same as amodeImmReg, but the base register is fixed to RBP.
+	// The only differece is that it doesn't tell the register allocator to use RBP which is distracting for the
+	// register allocator.
+	amodeImmRBP
+
 	// amodeRegRegShift calculates sign-extend-32-to-64(Immediate) + base + (Register2 << Shift)
 	amodeRegRegShift
 
@@ -118,8 +123,7 @@ func (a *amode) uses(rs *[]regalloc.VReg) {
 		*rs = append(*rs, a.base)
 	case amodeRegRegShift:
 		*rs = append(*rs, a.base, a.index)
-	case amodeRipRelative:
-		// nothing
+	case amodeRipRelative, amodeImmRBP:
 	default:
 		panic("BUG: invalid amode kind")
 	}
@@ -131,7 +135,7 @@ func (a *amode) nregs() int {
 		return 1
 	case amodeRegRegShift:
 		return 2
-	case amodeRipRelative:
+	case amodeRipRelative, amodeImmRBP:
 		return 0
 	default:
 		panic("BUG: invalid amode kind")
@@ -164,6 +168,10 @@ func newAmodeImmReg(imm32 uint32, base regalloc.VReg) amode {
 	return amode{kind: amodeImmReg, imm32: imm32, base: base}
 }
 
+func newAmodeImmRBPReg(imm32 uint32) amode {
+	return amode{kind: amodeImmRBP, imm32: imm32, base: rbpVReg}
+}
+
 func newAmodeRegRegShift(imm32 uint32, base, index regalloc.VReg, shift byte) amode {
 	if shift > 3 {
 		panic(fmt.Sprintf("BUG: invalid shift (must be 3>=): %d", shift))
@@ -189,7 +197,7 @@ func newAmodeRipRelative(label backend.Label) amode {
 // String implements fmt.Stringer.
 func (a *amode) String() string {
 	switch a.kind {
-	case amodeImmReg:
+	case amodeImmReg, amodeImmRBP:
 		if a.imm32 == 0 {
 			return fmt.Sprintf("(%s)", formatVRegSized(a.base, true))
 		}
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi.go b/internal/engine/wazevo/backend/isa/arm64/abi.go
index de386771..54accac7 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi.go
@@ -89,14 +89,12 @@ func (m *machine) LowerParams(args []ssa.Value) {
 			//          |   ReturnAddress |      |
 			//          +-----------------+      |
 			//          |   ...........   |      |
-			//          |   spill slot M  |      |   argStackOffset: is unknown at this point of compilation.
+			//          |   clobbered  M  |      |   argStackOffset: is unknown at this point of compilation.
 			//          |   ............  |      |
-			//          |   spill slot 2  |      |
-			//          |   spill slot 1  |      |
-			//          |   clobbered 0   |      |
-			//          |   clobbered 1   |      |
+			//          |   clobbered  0  |      |
+			//          |   spill slot N  |      |
 			//          |   ...........   |      |
-			//          |   clobbered N   |      |
+			//          |   spill slot 0  |      |
 			//   SP---> +-----------------+    <-+
 			//             (low address)
 


Test patch:
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
index d429f56b..63b81db8 100644
--- a/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble_test.go
@@ -23,8 +23,8 @@ func TestMachineCompileEntryPreamble(t *testing.T) {
 	movq %rax, %rdx
 	mov.q %rbp, 16(%rax)
 	mov.q %rsp, 24(%rax)
-	movq %rbx, %rsp
-	callq *%rdi
+	movq %r13, %rsp
+	callq *%r14
 	movq 16(%rdx), %rbp
 	movq 24(%rdx), %rsp
 	ret
@@ -40,14 +40,14 @@ func TestMachineCompileEntryPreamble(t *testing.T) {
 	movq %rax, %rdx
 	mov.q %rbp, 16(%rax)
 	mov.q %rsp, 24(%rax)
-	movq %rbx, %rsp
+	movq %r13, %rsp
 	movzx.lq (%r12), %rbx
 	movq 8(%r12), %rsi
 	movss 16(%r12), %xmm0
 	movsd 24(%r12), %xmm1
 	movdqu 32(%r12), %xmm2
 	movq 48(%r12), %rdi
-	callq *%rdi
+	callq *%r14
 	movq 16(%rdx), %rbp
 	movq 24(%rdx), %rsp
 	ret
@@ -64,8 +64,8 @@ func TestMachineCompileEntryPreamble(t *testing.T) {
 	movq %rax, %rdx
 	mov.q %rbp, 16(%rax)
 	mov.q %rsp, 24(%rax)
-	movq %rbx, %rsp
-	callq *%rdi
+	movq %r13, %rsp
+	callq *%r14
 	mov.l %rax, (%r12)
 	movdqu %xmm0, 8(%r12)
 	mov.q %rcx, 24(%r12)
@@ -87,14 +87,14 @@ func TestMachineCompileEntryPreamble(t *testing.T) {
 	movq %rax, %rdx
 	mov.q %rbp, 16(%rax)
 	mov.q %rsp, 24(%rax)
-	movq %rbx, %rsp
+	movq %r13, %rsp
 	movzx.lq (%r12), %rbx
 	movq 8(%r12), %rsi
 	movss 16(%r12), %xmm0
 	movsd 24(%r12), %xmm1
 	movdqu 32(%r12), %xmm2
 	movq 48(%r12), %rdi
-	callq *%rdi
+	callq *%r14
 	mov.l %rax, (%r12)
 	movdqu %xmm0, 8(%r12)
 	mov.q %rcx, 24(%r12)
@@ -105,7 +105,198 @@ func TestMachineCompileEntryPreamble(t *testing.T) {
 	ret
 `,
 		},
-		// TODO: add stack based param/results.
+		{
+			name: "many args",
+			sig: &ssa.Signature{
+				// execContext and moduleContext are passed in %rax and %rcx.
+				Params: []ssa.Type{
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+				},
+			},
+			exp: `
+	movq %rax, %rdx
+	mov.q %rbp, 16(%rax)
+	mov.q %rsp, 24(%rax)
+	movq %r13, %rsp
+	sub $64, %rsp
+	movzx.lq (%r12), %rbx
+	movq 8(%r12), %rsi
+	movss 16(%r12), %xmm0
+	movsd 24(%r12), %xmm1
+	movdqu 32(%r12), %xmm2
+	movq 48(%r12), %rdi
+	movq 56(%r12), %r8
+	movq 64(%r12), %r9
+	movzx.lq 72(%r12), %r10
+	movq 80(%r12), %r11
+	movss 88(%r12), %xmm3
+	movsd 96(%r12), %xmm4
+	movdqu 104(%r12), %xmm5
+	movq 120(%r12), %r15
+	mov.q %r15, (%rsp)
+	movq 128(%r12), %r15
+	mov.q %r15, 8(%rsp)
+	movq 136(%r12), %r15
+	mov.q %r15, 16(%rsp)
+	movzx.lq 144(%r12), %r15
+	mov.l %r15, 24(%rsp)
+	movq 152(%r12), %r15
+	mov.q %r15, 32(%rsp)
+	movss 160(%r12), %xmm6
+	movsd 168(%r12), %xmm7
+	movdqu 176(%r12), %xmm15
+	movdqu %xmm15, 40(%rsp)
+	movq 192(%r12), %r15
+	mov.q %r15, 56(%rsp)
+	callq *%r14
+	movq 16(%rdx), %rbp
+	movq 24(%rdx), %rsp
+	ret
+`,
+		},
+		{
+			name: "many results",
+			sig: &ssa.Signature{
+				// execContext and moduleContext are passed in %rax and %rcx.
+				Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64},
+				Results: []ssa.Type{
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+				},
+			},
+			exp: `
+	movq %rax, %rdx
+	mov.q %rbp, 16(%rax)
+	mov.q %rsp, 24(%rax)
+	movq %r13, %rsp
+	sub $64, %rsp
+	callq *%r14
+	mov.q %rax, (%r12)
+	mov.q %rcx, 8(%r12)
+	mov.l %rbx, 16(%r12)
+	mov.q %rsi, 24(%r12)
+	movss %xmm0, 32(%r12)
+	movsd %xmm1, 40(%r12)
+	movdqu %xmm2, 48(%r12)
+	mov.q %rdi, 64(%r12)
+	mov.q %r8, 72(%r12)
+	mov.q %r9, 80(%r12)
+	mov.l %r10, 88(%r12)
+	mov.q %r11, 96(%r12)
+	movss %xmm3, 104(%r12)
+	movsd %xmm4, 112(%r12)
+	movdqu %xmm5, 120(%r12)
+	movq (%rsp), %r15
+	mov.q %r15, 136(%r12)
+	movq 8(%rsp), %r15
+	mov.q %r15, 144(%r12)
+	movq 16(%rsp), %r15
+	mov.q %r15, 152(%r12)
+	movzx.lq 24(%rsp), %r15
+	mov.l %r15, 160(%r12)
+	movq 32(%rsp), %r15
+	mov.q %r15, 168(%r12)
+	movss %xmm6, 176(%r12)
+	movsd %xmm7, 184(%r12)
+	movdqu 40(%rsp), %xmm15
+	movdqu %xmm15, 192(%r12)
+	movq 56(%rsp), %r15
+	mov.q %r15, 208(%r12)
+	movq 16(%rdx), %rbp
+	movq 24(%rdx), %rsp
+	ret
+`,
+		},
+		{
+			name: "many args results",
+			sig: &ssa.Signature{
+				// execContext and moduleContext are passed in %rax and %rcx.
+				Params: []ssa.Type{
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+				},
+				Results: []ssa.Type{
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+					ssa.TypeI64, ssa.TypeI64, ssa.TypeI32, ssa.TypeI64, ssa.TypeF32, ssa.TypeF64, ssa.TypeV128, ssa.TypeI64,
+				},
+			},
+			exp: `
+	movq %rax, %rdx
+	mov.q %rbp, 16(%rax)
+	mov.q %rsp, 24(%rax)
+	movq %r13, %rsp
+	sub $128, %rsp
+	movzx.lq (%r12), %rbx
+	movq 8(%r12), %rsi
+	movss 16(%r12), %xmm0
+	movsd 24(%r12), %xmm1
+	movdqu 32(%r12), %xmm2
+	movq 48(%r12), %rdi
+	movq 56(%r12), %r8
+	movq 64(%r12), %r9
+	movzx.lq 72(%r12), %r10
+	movq 80(%r12), %r11
+	movss 88(%r12), %xmm3
+	movsd 96(%r12), %xmm4
+	movdqu 104(%r12), %xmm5
+	movq 120(%r12), %r15
+	mov.q %r15, (%rsp)
+	movq 128(%r12), %r15
+	mov.q %r15, 8(%rsp)
+	movq 136(%r12), %r15
+	mov.q %r15, 16(%rsp)
+	movzx.lq 144(%r12), %r15
+	mov.l %r15, 24(%rsp)
+	movq 152(%r12), %r15
+	mov.q %r15, 32(%rsp)
+	movss 160(%r12), %xmm6
+	movsd 168(%r12), %xmm7
+	movdqu 176(%r12), %xmm15
+	movdqu %xmm15, 40(%rsp)
+	movq 192(%r12), %r15
+	mov.q %r15, 56(%rsp)
+	callq *%r14
+	mov.q %rax, (%r12)
+	mov.q %rcx, 8(%r12)
+	mov.l %rbx, 16(%r12)
+	mov.q %rsi, 24(%r12)
+	movss %xmm0, 32(%r12)
+	movsd %xmm1, 40(%r12)
+	movdqu %xmm2, 48(%r12)
+	mov.q %rdi, 64(%r12)
+	mov.q %r8, 72(%r12)
+	mov.q %r9, 80(%r12)
+	mov.l %r10, 88(%r12)
+	mov.q %r11, 96(%r12)
+	movss %xmm3, 104(%r12)
+	movsd %xmm4, 112(%r12)
+	movdqu %xmm5, 120(%r12)
+	movq 64(%rsp), %r15
+	mov.q %r15, 136(%r12)
+	movq 72(%rsp), %r15
+	mov.q %r15, 144(%r12)
+	movq 80(%rsp), %r15
+	mov.q %r15, 152(%r12)
+	movzx.lq 88(%rsp), %r15
+	mov.l %r15, 160(%r12)
+	movq 96(%rsp), %r15
+	mov.q %r15, 168(%r12)
+	movss %xmm6, 176(%r12)
+	movsd %xmm7, 184(%r12)
+	movdqu 104(%rsp), %xmm15
+	movdqu %xmm15, 192(%r12)
+	movq 120(%rsp), %r15
+	mov.q %r15, 208(%r12)
+	movq 16(%rdx), %rbp
+	movq 24(%rdx), %rsp
+	ret
+`,
+		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			_, _, m := newSetupWithMockContext()
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
index a5ba3984..7ea8c6e2 100644
--- 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
@@ -39,15 +39,26 @@ func TestMachine_SetupPrologue(t *testing.T) {
 	ud2
 `,
 			spillSlotSize: 0,
-			clobberedRegs: []regalloc.VReg{
-				raxVReg,
-				rcxVReg,
-				xmm0VReg,
-				xmm1VReg,
-				xmm15VReg,
-			},
+			clobberedRegs: []regalloc.VReg{raxVReg, rcxVReg, xmm0VReg, xmm1VReg, xmm15VReg},
+		},
+		{
+			exp: `
+	pushq %rbp
+	movq %rsp, %rbp
+	sub $16, %rsp
+	movdqu %xmm15, (%rsp)
+	sub $16, %rsp
+	movdqu %xmm1, (%rsp)
+	sub $16, %rsp
+	movdqu %xmm0, (%rsp)
+	pushq %rcx
+	pushq %rax
+	sub $48, %rsp
+	ud2
+`,
+			spillSlotSize: 48,
+			clobberedRegs: []regalloc.VReg{raxVReg, rcxVReg, xmm0VReg, xmm1VReg, xmm15VReg},
 		},
-		// TODO: add more test cases.
 	} {
 		tc := tc
 		t.Run(tc.exp, func(t *testing.T) {
@@ -103,15 +114,26 @@ func TestMachine_SetupEpilogue(t *testing.T) {
 	ret
 `,
 			spillSlotSize: 0,
-			clobberedRegs: []regalloc.VReg{
-				raxVReg,
-				rcxVReg,
-				xmm0VReg,
-				xmm1VReg,
-				xmm15VReg,
-			},
+			clobberedRegs: []regalloc.VReg{raxVReg, rcxVReg, xmm0VReg, xmm1VReg, xmm15VReg},
+		},
+		{
+			exp: `
+	add $160, %rsp
+	popq %rax
+	popq %rcx
+	movdqu (%rsp), %xmm0
+	add $16, %rsp
+	movdqu (%rsp), %xmm1
+	add $16, %rsp
+	movdqu (%rsp), %xmm15
+	add $16, %rsp
+	movq %rbp, %rsp
+	popq %rbp
+	ret
+`,
+			spillSlotSize: 160,
+			clobberedRegs: []regalloc.VReg{raxVReg, rcxVReg, xmm0VReg, xmm1VReg, xmm15VReg},
 		},
-		// TODO: add more test cases.
 	} {
 		tc := tc
 		t.Run(tc.exp, func(t *testing.T) {
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
new file mode 100644
index 00000000..802d0363
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
@@ -0,0 +1,125 @@
+package amd64
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
+	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+func TestMachine_InsertStoreRegisterAt(t *testing.T) {
+	for _, tc := range []struct {
+		spillSlotSize int64
+		expected      string
+	}{
+		{
+			spillSlotSize: 16,
+			expected: `
+	ud2
+	movsd 16(%rsp), %xmm1
+	movq 24(%rsp), %rax
+	ret
+`,
+		},
+		{
+			spillSlotSize: 160,
+			expected: `
+	ud2
+	movsd 160(%rsp), %xmm1
+	movq 168(%rsp), %rax
+	ret
+`,
+		},
+	} {
+		t.Run(tc.expected, func(t *testing.T) {
+			ctx, _, m := newSetupWithMockContext()
+			m.spillSlotSize = tc.spillSlotSize
+
+			for _, after := range []bool{false, true} {
+				var name string
+				if after {
+					name = "after"
+				} else {
+					name = "before"
+				}
+				t.Run(name, func(t *testing.T) {
+					ctx.typeOf = map[regalloc.VRegID]ssa.Type{
+						raxVReg.ID(): ssa.TypeI64, xmm1VReg.ID(): ssa.TypeF64,
+					}
+					i1, i2 := m.allocateInstr().asUD2(), m.allocateInstr().asRet(nil)
+					i1.next = i2
+					i2.prev = i1
+
+					if after {
+						m.InsertReloadRegisterAt(raxVReg, i1, after)
+						m.InsertReloadRegisterAt(xmm1VReg, i1, after)
+					} else {
+						m.InsertReloadRegisterAt(xmm1VReg, i2, after)
+						m.InsertReloadRegisterAt(raxVReg, i2, after)
+					}
+					m.ectx.RootInstr = i1
+					require.Equal(t, tc.expected, m.Format())
+				})
+			}
+		})
+	}
+}
+
+func TestMachine_InsertReloadRegisterAt(t *testing.T) {
+	for _, tc := range []struct {
+		spillSlotSize int64
+		expected      string
+	}{
+		{
+			spillSlotSize: 16,
+			expected: `
+	ud2
+	movq 16(%rsp), %rax
+	movdqu 24(%rsp), %xmm1
+	ret
+`,
+		},
+		{
+			spillSlotSize: 160,
+			expected: `
+	ud2
+	movq 160(%rsp), %rax
+	movdqu 168(%rsp), %xmm1
+	ret
+`,
+		},
+	} {
+		t.Run(tc.expected, func(t *testing.T) {
+			ctx, _, m := newSetupWithMockContext()
+			m.spillSlotSize = tc.spillSlotSize
+
+			for _, after := range []bool{false, true} {
+				var name string
+				if after {
+					name = "after"
+				} else {
+					name = "before"
+				}
+				t.Run(name, func(t *testing.T) {
+					ctx.typeOf = map[regalloc.VRegID]ssa.Type{
+						raxVReg.ID(): ssa.TypeI64, xmm1VReg.ID(): ssa.TypeV128,
+					}
+					i1, i2 := m.allocateInstr().asUD2(), m.allocateInstr().asRet(nil)
+					i1.next = i2
+					i2.prev = i1
+
+					if after {
+						m.InsertReloadRegisterAt(xmm1VReg, i1, after)
+						m.InsertReloadRegisterAt(raxVReg, i1, after)
+					} else {
+						m.InsertReloadRegisterAt(raxVReg, i2, after)
+						m.InsertReloadRegisterAt(xmm1VReg, i2, after)
+					}
+					m.ectx.RootInstr = i1
+					require.Equal(t, tc.expected, m.Format())
+				})
+			}
+		})
+	}
+}
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 0563d667..b20952ee 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -104,6 +104,26 @@ func TestE2E(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "many_params_many_results",
+			m:    testcases.ManyParamsManyResults.Module,
+			calls: []callCase{
+				{
+					params: []uint64{
+						1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+						1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+						1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+						1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+					},
+					expResults: []uint64{
+						10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
+						10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
+						10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
+						10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
+					},
+				},
+			},
+		},
 		{
 			name: "fibonacci_recursive", m: testcases.FibonacciRecursive.Module,
 			skipAMD64: true,