Repository: tetratelabs/wazero
Pull request #1940 (2024-01-23 15:45:18)

wazevo(amd64): amode lowering
add address mode lowering/folding with tests


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/cond.go b/internal/engine/wazevo/backend/isa/amd64/cond.go
index a17b9b9e..c422e72a 100644
--- a/internal/engine/wazevo/backend/isa/amd64/cond.go
+++ b/internal/engine/wazevo/backend/isa/amd64/cond.go
@@ -78,7 +78,7 @@ func (c cond) String() string {
 	}
 }
 
-func condFromSSAIntCmpCond(origin ssa.IntegerCmpCond) cond { // nolint:unused
+func condFromSSAIntCmpCond(origin ssa.IntegerCmpCond) cond {
 	switch origin {
 	case ssa.IntegerCmpCondEqual:
 		return condZ
@@ -127,3 +127,42 @@ func condFromSSAFloatCmpCond(origin ssa.FloatCmpCond) cond { // nolint:unused
 func (c cond) encoding() byte {
 	return byte(c)
 }
+
+func (c cond) invert() cond {
+	switch c {
+	case condO:
+		return condNO
+	case condNO:
+		return condO
+	case condB:
+		return condNB
+	case condNB:
+		return condB
+	case condZ:
+		return condNZ
+	case condNZ:
+		return condZ
+	case condBE:
+		return condNBE
+	case condNBE:
+		return condBE
+	case condS:
+		return condNS
+	case condNS:
+		return condS
+	case condP:
+		return condNP
+	case condNP:
+		return condP
+	case condL:
+		return condNL
+	case condNL:
+		return condL
+	case condLE:
+		return condNLE
+	case condNLE:
+		return condLE
+	default:
+		panic("unreachable")
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 6c95b8a9..a20c90a4 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -688,6 +688,13 @@ func (i *instruction) asJmp(target operand) *instruction {
 	return i
 }
 
+func (i *instruction) jmpLabel() backend.Label {
+	if i.kind != jmp && i.kind != jmpIf {
+		panic("BUG")
+	}
+	return i.op1.label()
+}
+
 func (i *instruction) asLEA(a amode, rd regalloc.VReg) *instruction {
 	i.kind = lea
 	i.op1 = newOperandMem(a)
@@ -886,14 +893,14 @@ func (i *instruction) asXmmRmiReg(op sseOpcode, rm operand, rd regalloc.VReg) *i
 	return i
 }
 
-func (i *instruction) asCmpRmiR(cmpOrTest bool, rm operand, rd regalloc.VReg, _64 bool) *instruction {
+func (i *instruction) asCmpRmiR(cmp bool, rm operand, rd regalloc.VReg, _64 bool) *instruction {
 	if rm.kind != operandKindReg && rm.kind != operandKindImm32 && rm.kind != operandKindMem {
 		panic("BUG")
 	}
 	i.kind = cmpRmiR
 	i.op1 = rm
 	i.op2 = newOperandReg(rd)
-	if cmpOrTest {
+	if cmp {
 		i.u1 = 1
 	}
 	i.b1 = _64
@@ -1527,6 +1534,9 @@ var defKinds = [instrMax]defKind{
 	gprToXmm:    defKindOp2,
 	call:        defKindCall,
 	ud2:         defKindNone,
+	jmp:         defKindNone,
+	jmpIf:       defKindNone,
+	cmpRmiR:     defKindNone,
 }
 
 // String implements fmt.Stringer.
@@ -1569,6 +1579,9 @@ var useKinds = [instrMax]useKind{
 	gprToXmm:    useKindOp1,
 	call:        useKindCall,
 	ud2:         useKindNone,
+	jmpIf:       useKindOp1,
+	jmp:         useKindOp1,
+	cmpRmiR:     useKindOp1Op2Reg,
 }
 
 func (u useKind) String() string {
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index 36456fdd..c38bb683 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -907,7 +907,8 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		op := i.op1
 		switch op.kind {
 		case operandKindLabel:
-			panic("BUG: at this point label should have been resolved to imm32")
+			needsLabelResolution = true
+			fallthrough
 		case operandKindImm32:
 			c.EmitByte(0xe9)
 			c.Emit4Bytes(op.imm32)
@@ -936,7 +937,7 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		switch op.kind {
 		case operandKindLabel:
 			needsLabelResolution = true
-			panic("BUG: at this point label should have been resolved to imm32")
+			fallthrough
 		case operandKindImm32:
 			c.EmitByte(0x0f)
 			c.EmitByte(0x80 | cond(i.u1).encoding())
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
index 5a58ca1d..9c4aecb6 100644
--- a/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem.go
@@ -1,10 +1,156 @@
 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"
 )
 
+var addendsMatchOpcodes = [...]ssa.Opcode{ssa.OpcodeUExtend, ssa.OpcodeSExtend, ssa.OpcodeIadd, ssa.OpcodeIconst, ssa.OpcodeIshl}
+
+type (
+	addend struct {
+		r     regalloc.VReg
+		off   int64
+		shift byte
+	}
+)
+
 // lowerToAddressMode converts a pointer to an addressMode that can be used as an operand for load/store instructions.
 func (m *machine) lowerToAddressMode(ptr ssa.Value, offsetBase uint32) (am amode) {
-	panic("TODO")
+	offBase := int32(offsetBase)
+	def := m.c.ValueDefinition(ptr)
+	if op := m.c.MatchInstrOneOf(def, addendsMatchOpcodes[:]); op == ssa.OpcodeIadd {
+		x, y := def.Instr.Arg2()
+		xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y)
+		ax := m.lowerAddend(xDef)
+		ay := m.lowerAddend(yDef)
+		return m.lowerAddendsToAmode(ax, ay, offBase)
+	} else {
+		// If it is not an Iadd, then we lower the one addend.
+		a := m.lowerAddend(def)
+		// off is always 0 if r is valid.
+		if a.r != regalloc.VRegInvalid {
+			if a.shift != 0 {
+				tmpReg := m.c.AllocateVReg(ssa.TypeI64)
+				m.lowerIconst(tmpReg, 0, true)
+				return newAmodeRegRegShift(offsetBase, tmpReg, a.r, a.shift)
+			}
+			return newAmodeImmReg(offsetBase, a.r)
+		} else {
+			off64 := a.off + int64(offBase)
+			tmpReg := m.c.AllocateVReg(ssa.TypeI64)
+			m.lowerIconst(tmpReg, uint64(off64), true)
+			return newAmodeImmReg(0, tmpReg)
+		}
+	}
+}
+
+func (m *machine) lowerAddendsToAmode(x, y addend, offBase int32) amode {
+	if x.r != regalloc.VRegInvalid && x.off != 0 || y.r != regalloc.VRegInvalid && y.off != 0 {
+		panic("invalid input")
+	}
+	u64 := uint64(int64(offBase) + x.off + y.off)
+	if u64 != 0 && !lower32willSignExtendTo64(u64) {
+		tmpReg := m.c.AllocateVReg(ssa.TypeI64)
+		m.lowerIconst(tmpReg, u64, true)
+		// Blank u64 as it has been already lowered.
+		u64 = 0
+		// We already know that either rx or ry is invalid,
+		// so we overwrite it with the temporary register.
+		if x.r == regalloc.VRegInvalid {
+			x.r = tmpReg
+		} else {
+			y.r = tmpReg
+		}
+	}
+
+	u32 := uint32(u64)
+	switch {
+	// We assume rx, ry are valid iff offx, offy are 0.
+	case x.r != regalloc.VRegInvalid && y.r != regalloc.VRegInvalid:
+		switch {
+		case x.shift != 0 && y.shift != 0:
+			// Cannot absorb two shifted registers, must lower one to a shift instruction.
+			shifted := m.allocateInstr()
+			shifted.asShiftR(shiftROpShiftLeft, newOperandImm32(uint32(x.shift)), x.r, true)
+			m.insert(shifted)
+
+			return newAmodeRegRegShift(u32, x.r, y.r, y.shift)
+		case x.shift != 0 && y.shift == 0:
+			// Swap base and index.
+			x, y = y, x
+			fallthrough
+		default:
+			return newAmodeRegRegShift(u32, x.r, y.r, y.shift)
+		}
+	case x.r == regalloc.VRegInvalid && y.r != regalloc.VRegInvalid:
+		x, y = y, x
+		fallthrough
+	case x.r != regalloc.VRegInvalid && y.r == regalloc.VRegInvalid:
+		if x.shift != 0 {
+			zero := m.c.AllocateVReg(ssa.TypeI64)
+			m.lowerIconst(zero, 0, true)
+			return newAmodeRegRegShift(u32, zero, x.r, x.shift)
+		}
+		return newAmodeImmReg(u32, x.r)
+	default: // Both are invalid: use the offset.
+		tmpReg := m.c.AllocateVReg(ssa.TypeI64)
+		m.lowerIconst(tmpReg, u64, true)
+		return newAmodeImmReg(0, tmpReg)
+	}
+}
+
+func (m *machine) lowerAddend(x *backend.SSAValueDefinition) addend {
+	if x.IsFromBlockParam() {
+		return addend{x.BlkParamVReg, 0, 0}
+	}
+	// Ensure the addend is not referenced in multiple places; we will discard nested Iadds.
+	op := m.c.MatchInstrOneOf(x, addendsMatchOpcodes[:])
+	if op != ssa.OpcodeInvalid && op != ssa.OpcodeIadd {
+		return m.lowerAddendFromInstr(x.Instr)
+	}
+	return addend{m.getOperand_Reg(x).r, 0, 0}
+}
+
+// lowerAddendFromInstr takes an instruction returns a Vreg and an offset that can be used in an address mode.
+// The Vreg is regalloc.VRegInvalid if the addend cannot be lowered to a register.
+// The offset is 0 if the addend can be lowered to a register.
+func (m *machine) lowerAddendFromInstr(instr *ssa.Instruction) addend {
+	instr.MarkLowered()
+	switch op := instr.Opcode(); op {
+	case ssa.OpcodeIconst:
+		u64 := instr.ConstantVal()
+		if instr.Return().Type().Bits() == 32 {
+			return addend{regalloc.VRegInvalid, int64(int32(u64)), 0} // sign-extend.
+		} else {
+			return addend{regalloc.VRegInvalid, int64(u64), 0}
+		}
+	case ssa.OpcodeUExtend, ssa.OpcodeSExtend:
+		input := instr.Arg()
+		inputDef := m.c.ValueDefinition(input)
+		switch input.Type().Bits() {
+		case 64:
+			return addend{m.getOperand_Reg(inputDef).r, 0, 0}
+		case 32:
+			constInst := inputDef.IsFromInstr() && inputDef.Instr.Constant()
+			switch {
+			case constInst && op == ssa.OpcodeSExtend:
+				return addend{regalloc.VRegInvalid, int64(uint32(inputDef.Instr.ConstantVal())), 0}
+			case constInst && op == ssa.OpcodeUExtend:
+				return addend{regalloc.VRegInvalid, int64(int32(inputDef.Instr.ConstantVal())), 0} // sign-extend!
+			default:
+				return addend{m.getOperand_Reg(inputDef).r, 0, 0}
+			}
+		}
+	case ssa.OpcodeIshl:
+		// If the addend is a shift, we can only handle it if the shift amount is a constant.
+		x, amount := instr.Arg2()
+		amountDef := m.c.ValueDefinition(amount)
+		if amountDef.IsFromInstr() && amountDef.Instr.Constant() && amountDef.Instr.ConstantVal() <= 3 {
+			return addend{m.getOperand_Reg(m.c.ValueDefinition(x)).r, 0, uint8(amountDef.Instr.ConstantVal())}
+		}
+		return addend{m.getOperand_Reg(m.c.ValueDefinition(x)).r, 0, 0}
+	}
+	panic("BUG: invalid opcode")
 }
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 8059d9e9..eaa7aa9f 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -2,6 +2,7 @@ package amd64
 
 import (
 	"context"
+	"encoding/binary"
 	"fmt"
 	"strings"
 
@@ -134,10 +135,57 @@ func (m *machine) LowerSingleBranch(b *ssa.Instruction) {
 	}
 }
 
+var condBranchMatches = [...]ssa.Opcode{ssa.OpcodeIcmp, ssa.OpcodeFcmp}
+
 // LowerConditionalBranch implements backend.Machine.
 func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
-	// TODO implement me
-	panic("implement me")
+	exctx := m.ectx
+	cval, args, targetBlk := b.BranchData()
+	if len(args) > 0 {
+		panic(fmt.Sprintf(
+			"conditional branch shouldn't have args; likely a bug in critical edge splitting: from %s to %s",
+			exctx.CurrentSSABlk,
+			targetBlk,
+		))
+	}
+
+	target := exctx.GetOrAllocateSSABlockLabel(targetBlk)
+	cvalDef := m.c.ValueDefinition(cval)
+
+	switch m.c.MatchInstrOneOf(cvalDef, condBranchMatches[:]) {
+	case ssa.OpcodeIcmp:
+		cvalInstr := cvalDef.Instr
+		x, y, c := cvalInstr.IcmpData()
+
+		cc := condFromSSAIntCmpCond(c)
+		if b.Opcode() == ssa.OpcodeBrz {
+			cc = cc.invert()
+		}
+
+		// First, perform the comparison and set the flag.
+		xd, yd := m.c.ValueDefinition(x), m.c.ValueDefinition(y)
+		m.lowerIcmpToFlag(xd, yd, x.Type() == ssa.TypeI64)
+
+		// Then perform the conditional branch.
+		m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target)))
+		cvalDef.Instr.MarkLowered()
+	case ssa.OpcodeFcmp:
+		panic("TODO")
+	default:
+		v := m.getOperand_Reg(cvalDef)
+
+		var cc cond
+		if b.Opcode() == ssa.OpcodeBrz {
+			cc = condZ
+		} else {
+			cc = condNZ
+		}
+
+		// Perform test %v, %v to set the flag.
+		cmp := m.allocateInstr().asCmpRmiR(false, v, v.r, false)
+		m.insert(cmp)
+		m.insert(m.allocateInstr().asJmpIf(cc, newOperandLabel(target)))
+	}
 }
 
 // LowerInstr implements backend.Machine.
@@ -356,7 +404,7 @@ func (m *machine) Encode(context.Context) {
 			needLabelResolution := cur.encode(m.c)
 			if needLabelResolution {
 				m.labelResolutionPends = append(m.labelResolutionPends,
-					labelResolutionPend{instr: cur, offset: int64(offset)},
+					labelResolutionPend{instr: cur, offset: offset},
 				)
 			}
 		}
@@ -364,11 +412,17 @@ func (m *machine) Encode(context.Context) {
 
 	for i := range m.labelResolutionPends {
 		p := &m.labelResolutionPends[i]
+		target := p.instr.jmpLabel()
+		targetOffset := ectx.LabelPositions[target].BinaryOffset
 		switch p.instr.kind {
 		case jmp:
-			panic("TODO")
+			imm32Offset := p.offset + 1                       // +1 because p.offset is the beginning of the instruction.
+			jmpOffset := int32(targetOffset - (p.offset + 5)) // +5 because jmp is 5 bytes long.
+			binary.LittleEndian.PutUint32((*bufPtr)[imm32Offset:], uint32(jmpOffset))
 		case jmpIf:
-			panic("TODO")
+			imm32Offset := p.offset + 2                       // +2 because p.offset is the beginning of the instruction.
+			jmpOffset := int32(targetOffset - (p.offset + 6)) // +6 because jmpIf is 6 bytes long.
+			binary.LittleEndian.PutUint32((*bufPtr)[imm32Offset:], uint32(jmpOffset))
 		default:
 			panic("BUG")
 		}
@@ -390,6 +444,13 @@ func (m *machine) ResolveRelocations(refToBinaryOffset map[ssa.FuncRef]int, bina
 	}
 }
 
+func (m *machine) lowerIcmpToFlag(xd, yd *backend.SSAValueDefinition, _64 bool) {
+	x := m.getOperand_Reg(xd)
+	y := m.getOperand_Mem_Imm32_Reg(yd)
+	cmp := m.allocateInstr().asCmpRmiR(true, y, x.r, _64)
+	m.insert(cmp)
+}
+
 // allocateInstr allocates an instruction.
 func (m *machine) allocateInstr() *instruction {
 	instr := m.ectx.InstructionPool.Allocate()
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
index 8f961301..01759d08 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go
@@ -8,8 +8,22 @@ import (
 
 // InsertMoveBefore implements backend.RegAllocFunctionMachine.
 func (m *machine) InsertMoveBefore(dst, src regalloc.VReg, instr *instruction) {
-	// TODO implement me
-	panic("implement me")
+	typ := src.RegType()
+	if typ != dst.RegType() {
+		panic("BUG: src and dst must have the same type")
+	}
+
+	mov := m.allocateInstr()
+	if typ == regalloc.RegTypeInt {
+		mov.asMovRR(src, dst, true)
+	} else {
+		mov.asXmmUnaryRmR(sseOpcodeMovdqu, newOperandReg(src), dst)
+	}
+
+	cur := instr.prev
+	prevNext := cur.next
+	cur = linkInstr(cur, mov)
+	linkInstr(cur, prevNext)
 }
 
 // InsertStoreRegisterAt implements backend.RegAllocFunctionMachine.
@@ -97,8 +111,19 @@ func (m *machine) Swap(cur *instruction, x1, x2, tmp regalloc.VReg) {
 
 // LastInstrForInsertion implements backend.RegAllocFunctionMachine.
 func (m *machine) LastInstrForInsertion(begin, end *instruction) *instruction {
-	// TODO implement me
-	panic("implement me")
+	cur := end
+	for cur.kind == nop0 {
+		cur = cur.prev
+		if cur == begin {
+			return end
+		}
+	}
+	switch cur.kind {
+	case jmp:
+		return cur
+	default:
+		return end
+	}
 }
 
 // SSABlockLabel implements backend.RegAllocFunctionMachine.
diff --git a/internal/engine/wazevo/backend/isa/amd64/operands.go b/internal/engine/wazevo/backend/isa/amd64/operands.go
index 20cbee47..077def4c 100644
--- a/internal/engine/wazevo/backend/isa/amd64/operands.go
+++ b/internal/engine/wazevo/backend/isa/amd64/operands.go
@@ -69,6 +69,13 @@ func newOperandLabel(label backend.Label) operand { //nolint:unused
 	return operand{kind: operandKindLabel, imm32: uint32(label)}
 }
 
+func (o *operand) label() backend.Label {
+	if o.kind != operandKindLabel {
+		panic("BUG: invalid operand kind")
+	}
+	return backend.Label(o.imm32)
+}
+
 func newOperandReg(r regalloc.VReg) operand {
 	return operand{kind: operandKindReg, r: r}
 }
@@ -226,21 +233,12 @@ func (m *machine) getOperand_Mem_Imm32_Reg(def *backend.SSAValueDefinition) (op
 		return newOperandReg(def.BlkParamVReg)
 	}
 
-	if opcode := m.c.MatchInstrOneOf(def, []ssa.Opcode{
-		ssa.OpcodeLoad,
-		ssa.OpcodeUload8, ssa.OpcodeUload16, ssa.OpcodeUload32,
-		ssa.OpcodeSload8, ssa.OpcodeSload16, ssa.OpcodeSload32,
-	}); opcode != ssa.OpcodeInvalid {
-		switch opcode {
-		case ssa.OpcodeLoad, ssa.OpcodeUload32, ssa.OpcodeSload32:
-			instr := def.Instr
-			ptr, offset, _ := instr.LoadData()
-			op = newOperandMem(m.lowerToAddressMode(ptr, offset))
-			instr.MarkLowered()
-			return op
-		default:
-			panic("TODO: " + opcode.String())
-		}
+	if m.c.MatchInstr(def, ssa.OpcodeLoad) {
+		instr := def.Instr
+		ptr, offset, _ := instr.LoadData()
+		op = newOperandMem(m.lowerToAddressMode(ptr, offset))
+		instr.MarkLowered()
+		return op
 	}
 	return m.getOperand_Imm32_Reg(def)
 }
diff --git a/internal/engine/wazevo/backend/regalloc/api.go b/internal/engine/wazevo/backend/regalloc/api.go
index 941808ad..cb3ad7b7 100644
--- a/internal/engine/wazevo/backend/regalloc/api.go
+++ b/internal/engine/wazevo/backend/regalloc/api.go
@@ -72,9 +72,10 @@ type (
 		EndInstr() Instr
 		// LastInstrForInsertion returns the last instruction in this block where instructions will be inserted before it.
 		// Such insertions only happen when we need to insert spill/reload instructions to adjust the merge edges.
-		// If the very last instruction is the unconditional branching, then the returned instruction is the one before it.
-		// Note that at the time of register allocation, all the critical edges are already split, so there is no need
+		// At the time of register allocation, all the critical edges are already split, so there is no need
 		// to worry about the case where branching instruction has multiple successors.
+		// Therefore, usually, it is the nop instruction, but if the block ends with an unconditional branching, then it returns
+		// the unconditional branch, not the nop. In other words it is either nop or unconditional branch.
 		LastInstrForInsertion() Instr
 		// Preds returns the number of predecessors of this block in the CFG.
 		Preds() int


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
new file mode 100644
index 00000000..4a008539
--- /dev/null
+++ b/internal/engine/wazevo/backend/isa/amd64/lower_mem_test.go
@@ -0,0 +1,363 @@
+package amd64
+
+import (
+	"strings"
+	"testing"
+
+	"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/testing/require"
+)
+
+func Test_machine_lowerToAddressMode(t *testing.T) {
+	nextVReg, nextNextVReg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), regalloc.VReg(101).SetRegType(regalloc.RegTypeInt)
+	_, _ = nextVReg, nextNextVReg
+	for _, tc := range []struct {
+		name  string
+		in    func(*mockCompiler, ssa.Builder, *machine) (ptr ssa.Value, offset uint32)
+		insts []string
+		am    amode
+	}{
+		{
+			name: "iadd const, const; offset != 0",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				iconst1 := b.AllocateInstruction().AsIconst32(1).Insert(b)
+				iconst2 := b.AllocateInstruction().AsIconst32(2).Insert(b)
+				iadd := b.AllocateInstruction().AsIadd(iconst1.Return(), iconst2.Return()).Insert(b)
+				ptr = iadd.Return()
+				offset = 3
+				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
+				ctx.definitions[iconst2.Return()] = &backend.SSAValueDefinition{Instr: iconst2}
+				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				return
+			},
+			insts: []string{
+				"movabsq $6, %r100?",
+			},
+			am: newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "iadd const, param; offset != 0",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				iconst1 := b.AllocateInstruction().AsIconst32(1).Insert(b)
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				iadd := b.AllocateInstruction().AsIadd(iconst1.Return(), p).Insert(b)
+				ptr = iadd.Return()
+				offset = 3
+				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				return
+			},
+			am: newAmodeImmReg(1+3, raxVReg),
+		},
+		{
+			name: "iadd param, param; offset != 0",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				p1 := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				p2 := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				iadd := b.AllocateInstruction().AsIadd(p1, p2).Insert(b)
+				ptr = iadd.Return()
+				offset = 3
+				ctx.definitions[p1] = &backend.SSAValueDefinition{BlockParamValue: p1, BlkParamVReg: raxVReg}
+				ctx.definitions[p2] = &backend.SSAValueDefinition{BlockParamValue: p2, BlkParamVReg: rcxVReg}
+				ctx.definitions[ptr] = &backend.SSAValueDefinition{Instr: iadd}
+				return
+			},
+			am: newAmodeRegRegShift(3, raxVReg, rcxVReg, 0),
+		},
+
+		// The other iadd cases are covered by TestMachine_lowerAddendsToAmode.
+		{
+			name: "uextend const32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
+				uextend := b.AllocateInstruction().AsUExtend(iconst32.Return(), 32, 64).Insert(b)
+				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				return uextend.Return(), 0
+			},
+			insts: []string{
+				"movabsq $123, %r100?",
+			},
+			am: newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "redundant uextend const64",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				iconst32 := b.AllocateInstruction().AsIconst64(123).Insert(b)
+				uextend := b.AllocateInstruction().AsUExtend(iconst32.Return(), 64, 64).Insert(b)
+				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				return uextend.Return(), 0
+			},
+			insts: []string{
+				"movabsq $123, %r100?",
+			},
+			am: newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "redundant uextend param64",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				uextend := b.AllocateInstruction().AsUExtend(p, 64, 64).Insert(b)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				return uextend.Return(), 1 << 30
+			},
+			am: newAmodeImmReg(1<<30, raxVReg),
+		},
+		{
+			name: "Ishl param64, const",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				iconst64 := b.AllocateInstruction().AsIconst64(2).Insert(b)
+				ishl := b.AllocateInstruction().AsIshl(p, iconst64.Return()).Insert(b)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+				ctx.definitions[iconst64.Return()] = &backend.SSAValueDefinition{Instr: iconst64}
+				ctx.definitions[ishl.Return()] = &backend.SSAValueDefinition{Instr: ishl}
+				return ishl.Return(), 1 << 30
+			},
+			insts: []string{
+				"xor %r100?, %r100?",
+			},
+			am: newAmodeRegRegShift(1<<30, nextVReg, raxVReg, 2),
+		},
+		{
+			name: "add Iconst, (Ishl param64, const)",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) (ptr ssa.Value, offset uint32) {
+				p1 := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				p2 := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				const2 := b.AllocateInstruction().AsIconst64(2).Insert(b)
+				ishl := b.AllocateInstruction().AsIshl(p1, const2.Return()).Insert(b)
+				iadd := b.AllocateInstruction().AsIadd(p2, ishl.Return()).Insert(b)
+				ctx.definitions[p1] = &backend.SSAValueDefinition{BlockParamValue: p1, BlkParamVReg: raxVReg}
+				ctx.definitions[p2] = &backend.SSAValueDefinition{BlockParamValue: p2, BlkParamVReg: rcxVReg}
+				ctx.definitions[const2.Return()] = &backend.SSAValueDefinition{Instr: const2}
+				ctx.definitions[ishl.Return()] = &backend.SSAValueDefinition{Instr: ishl}
+				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+				return iadd.Return(), 1 << 30
+			},
+			am: newAmodeRegRegShift(1<<30, rcxVReg, raxVReg, 2),
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx, b, m := newSetupWithMockContext()
+			ctx.vRegCounter = int(nextVReg.ID()) - 1
+			ptr, offset := tc.in(ctx, b, m)
+			actual := m.lowerToAddressMode(ptr, offset)
+			require.Equal(t, strings.Join(tc.insts, "\n"), formatEmittedInstructionsInCurrentBlock(m))
+			require.Equal(t, tc.am, actual, actual.String())
+		})
+	}
+}
+
+func TestMachine_lowerAddendFromInstr(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		in   func(*mockCompiler, ssa.Builder, *machine) *ssa.Instruction
+		exp  addend
+	}{
+		{
+			name: "iconst64",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				return b.AllocateInstruction().AsIconst64(123 << 32).Insert(b)
+			},
+			exp: addend{regalloc.VRegInvalid, 123 << 32, 0},
+		},
+		{
+			name: "iconst32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				return b.AllocateInstruction().AsIconst32(123).Insert(b)
+			},
+			exp: addend{regalloc.VRegInvalid, 123, 0},
+		},
+		{
+			name: "uextend const32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
+				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				return b.AllocateInstruction().AsUExtend(iconst32.Return(), 32, 64).Insert(b)
+			},
+			exp: addend{regalloc.VRegInvalid, 123, 0},
+		},
+		{
+			name: "uextend const64",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				return b.AllocateInstruction().AsUExtend(p, 32, 64).Insert(b)
+			},
+			exp: addend{raxVReg, 0, 0},
+		},
+		{
+			name: "uextend param i32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				return b.AllocateInstruction().AsUExtend(p, 32, 64).Insert(b)
+			},
+			exp: addend{raxVReg, 0, 0},
+		},
+		{
+			name: "sextend const32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				iconst32 := b.AllocateInstruction().AsIconst32(123).Insert(b)
+				ctx.definitions[iconst32.Return()] = &backend.SSAValueDefinition{Instr: iconst32}
+				return b.AllocateInstruction().AsSExtend(iconst32.Return(), 32, 64).Insert(b)
+			},
+			exp: addend{regalloc.VRegInvalid, 123, 0},
+		},
+		{
+			name: "sextend const64",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI64)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				return b.AllocateInstruction().AsSExtend(p, 32, 64).Insert(b)
+			},
+			exp: addend{raxVReg, 0, 0},
+		},
+		{
+			name: "sextend param i32",
+			in: func(ctx *mockCompiler, b ssa.Builder, m *machine) *ssa.Instruction {
+				p := b.CurrentBlock().AddParam(b, ssa.TypeI32)
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlkParamVReg: raxVReg, BlockParamValue: p}
+				return b.AllocateInstruction().AsSExtend(p, 32, 64).Insert(b)
+			},
+			exp: addend{raxVReg, 0, 0},
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx, b, m := newSetupWithMockContext()
+			a := m.lowerAddendFromInstr(tc.in(ctx, b, m))
+			require.Equal(t, tc.exp, a)
+		})
+	}
+}
+
+func TestMachine_lowerAddendsToAmode(t *testing.T) {
+	x1, x2, x3 := raxVReg, rcxVReg, rdxVReg
+	_, _, _ = x1, x2, x3
+
+	nextVReg, nextNextVReg := regalloc.VReg(100).SetRegType(regalloc.RegTypeInt), regalloc.VReg(101).SetRegType(regalloc.RegTypeInt)
+	_ = nextNextVReg
+	for _, tc := range []struct {
+		name   string
+		x, y   addend
+		offset int32
+		exp    amode
+		insts  []string
+	}{
+		{
+			name: "only offset",
+			x:    addend{r: regalloc.VRegInvalid}, y: addend{r: regalloc.VRegInvalid},
+			offset: 4095,
+			insts:  []string{"movabsq $4095, %r100?"},
+			exp:    newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "only offset, offx, offy",
+			x:    addend{r: regalloc.VRegInvalid, off: 1}, y: addend{r: regalloc.VRegInvalid, off: 2},
+			offset: 4095,
+			insts:  []string{"movabsq $4098, %r100?"},
+			exp:    newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "only offset, offx, offy; not fitting",
+			x:    addend{r: regalloc.VRegInvalid, off: 1 << 30}, y: addend{r: regalloc.VRegInvalid, off: 2 << 30},
+			offset: 4095,
+			insts:  []string{"movabsq $3221229567, %r100?"},
+			exp:    newAmodeImmReg(0, nextVReg),
+		},
+		{
+			name: "one a64 with imm32",
+			x:    addend{r: x1}, y: addend{r: regalloc.VRegInvalid},
+			offset: 4095,
+			exp:    newAmodeImmReg(4095, x1),
+		},
+		{
+			name: "one a64 with imm32",
+			x:    addend{r: x1}, y: addend{r: regalloc.VRegInvalid},
+			offset: 1 << 16,
+			exp:    newAmodeImmReg(1<<16, x1),
+		},
+		{
+			name: "two a64 with imm32",
+			x:    addend{r: x1}, y: addend{r: x2},
+			offset: 1 << 16,
+			exp:    newAmodeRegRegShift(1<<16, x1, x2, 0),
+		},
+		{
+			name: "two a64 with offset fitting",
+			x:    addend{r: x1}, y: addend{r: x2},
+			offset: 1 << 30,
+			exp:    newAmodeRegRegShift(1<<30, x1, x2, 0),
+		},
+		{
+			name: "rx with offset not fitting",
+			x:    addend{r: x1}, y: addend{r: regalloc.VRegInvalid, off: 1 << 30},
+			offset: 1 << 30,
+			insts: []string{
+				"movabsq $2147483648, %r100?",
+			},
+			exp: newAmodeRegRegShift(0, x1, nextVReg, 0),
+		},
+		{
+			name: "ry with offset not fitting",
+			x:    addend{r: regalloc.VRegInvalid, off: 1 << 30}, y: addend{r: x1},
+			offset: 1 << 30,
+			insts: []string{
+				"movabsq $2147483648, %r100?",
+			},
+			exp: newAmodeRegRegShift(0, nextVReg, x1, 0),
+		},
+		{
+			name: "rx with shift, ry with shift, offset != 0",
+			x:    addend{r: x1, shift: 2}, y: addend{r: x2, shift: 3},
+			offset: 1 << 30,
+			insts: []string{
+				"shlq $2, %rax",
+			},
+			exp: newAmodeRegRegShift(1<<30, x1, x2, 3),
+		},
+		{
+			name: "rx, ry with shift, offset != 0",
+			x:    addend{r: x1}, y: addend{r: x2, shift: 3},
+			offset: 1 << 30,
+			exp:    newAmodeRegRegShift(1<<30, x1, x2, 3),
+		},
+		{
+			name: "rx with shift, ry, offset != 0",
+			x:    addend{r: x1, shift: 3}, y: addend{r: x2},
+			offset: 1 << 30,
+			exp:    newAmodeRegRegShift(1<<30, x2, x1, 3),
+		},
+		{
+			name: "rx with shift, ry invalid, offset != 0",
+			x:    addend{r: x1, shift: 3}, y: addend{r: regalloc.VRegInvalid},
+			offset: 1 << 30,
+			insts: []string{
+				"xor %r100?, %r100?",
+			},
+			exp: newAmodeRegRegShift(1<<30, nextVReg, x1, 3),
+		},
+		{
+			name: "rx invalid, rx with shift, offset != 0",
+			x:    addend{r: regalloc.VRegInvalid}, y: addend{r: x1, shift: 3},
+			offset: 1 << 30,
+			insts: []string{
+				"xor %r100?, %r100?",
+			},
+			exp: newAmodeRegRegShift(1<<30, nextVReg, x1, 3),
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx, _, m := newSetupWithMockContext()
+			ctx.vRegCounter = int(nextVReg.ID()) - 1
+			actual := m.lowerAddendsToAmode(tc.x, tc.y, tc.offset)
+			require.Equal(t, strings.Join(tc.insts, "\n"), formatEmittedInstructionsInCurrentBlock(m))
+			require.Equal(t, tc.exp, actual, actual.String())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
index 802d0363..5d01c018 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_regalloc_test.go
@@ -17,8 +17,8 @@ func TestMachine_InsertStoreRegisterAt(t *testing.T) {
 			spillSlotSize: 16,
 			expected: `
 	ud2
-	movsd 16(%rsp), %xmm1
-	movq 24(%rsp), %rax
+	movsd %xmm1, 16(%rsp)
+	mov.q %rax, 24(%rsp)
 	ret
 `,
 		},
@@ -26,8 +26,8 @@ func TestMachine_InsertStoreRegisterAt(t *testing.T) {
 			spillSlotSize: 160,
 			expected: `
 	ud2
-	movsd 160(%rsp), %xmm1
-	movq 168(%rsp), %rax
+	movsd %xmm1, 160(%rsp)
+	mov.q %rax, 168(%rsp)
 	ret
 `,
 		},
@@ -52,11 +52,11 @@ func TestMachine_InsertStoreRegisterAt(t *testing.T) {
 					i2.prev = i1
 
 					if after {
-						m.InsertReloadRegisterAt(raxVReg, i1, after)
-						m.InsertReloadRegisterAt(xmm1VReg, i1, after)
+						m.InsertStoreRegisterAt(raxVReg, i1, after)
+						m.InsertStoreRegisterAt(xmm1VReg, i1, after)
 					} else {
-						m.InsertReloadRegisterAt(xmm1VReg, i2, after)
-						m.InsertReloadRegisterAt(raxVReg, i2, after)
+						m.InsertStoreRegisterAt(xmm1VReg, i2, after)
+						m.InsertStoreRegisterAt(raxVReg, i2, after)
 					}
 					m.ectx.RootInstr = i1
 					require.Equal(t, tc.expected, m.Format())
@@ -123,3 +123,40 @@ func TestMachine_InsertReloadRegisterAt(t *testing.T) {
 		})
 	}
 }
+
+func TestMachine_InsertMoveBefore(t *testing.T) {
+	for _, tc := range []struct {
+		src, dst regalloc.VReg
+		expected string
+	}{
+		{
+			src: raxVReg,
+			dst: rdxVReg,
+			expected: `
+	ud2
+	movq %rax, %rdx
+	ret
+`,
+		},
+		{
+			src: xmm1VReg,
+			dst: xmm2VReg,
+			expected: `
+	ud2
+	movdqu %xmm1, %xmm2
+	ret
+`,
+		},
+	} {
+		t.Run(tc.expected, func(t *testing.T) {
+			_, _, m := newSetupWithMockContext()
+			i1, i2 := m.allocateInstr().asUD2(), m.allocateInstr().asRet(nil)
+			i1.next = i2
+			i2.prev = i1
+
+			m.InsertMoveBefore(tc.dst, tc.src, i2)
+			m.ectx.RootInstr = i1
+			require.Equal(t, tc.expected, m.Format())
+		})
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_test.go b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
index 2efbefdc..7ae1a1d0 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_test.go
@@ -1,6 +1,7 @@
 package amd64
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -136,6 +137,109 @@ func Test_machine_getOperand_Mem_Imm32_Reg(t *testing.T) {
 			},
 			exp: newOperandReg(raxVReg),
 		},
+		{
+			name: "amode with block param",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				blk := builder.CurrentBlock()
+				ptr := blk.AddParam(builder, ssa.TypeI64)
+				ctx.definitions[ptr] = &backend.SSAValueDefinition{BlockParamValue: ptr, BlkParamVReg: raxVReg}
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(ptr, 123, ssa.TypeI64).Insert(builder)
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			exp: newOperandMem(newAmodeImmReg(123, raxVReg)),
+		},
+		{
+			name: "amode with iconst",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(iconst.Return(), 123, ssa.TypeI64).Insert(builder)
+				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			instructions: []string{
+				"movabsq $579, %r1?", // r1 := 123+456
+			},
+			exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))),
+		},
+		{
+			name: "amode with iconst and extend",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				iconst := builder.AllocateInstruction().AsIconst32(0xffffff).Insert(builder)
+				uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder)
+
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder)
+
+				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			instructions: []string{
+				fmt.Sprintf("movabsq $%d, %%r1?", 0xffffff+123),
+			},
+			exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))),
+		},
+		{
+			name: "amode with iconst and extend",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				iconst := builder.AllocateInstruction().AsIconst32(456).Insert(builder)
+				uextend := builder.AllocateInstruction().AsUExtend(iconst.Return(), 32, 64).Insert(builder)
+
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(uextend.Return(), 123, ssa.TypeI64).Insert(builder)
+
+				ctx.definitions[uextend.Return()] = &backend.SSAValueDefinition{Instr: uextend}
+				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			instructions: []string{
+				fmt.Sprintf("movabsq $%d, %%r1?", 456+123),
+			},
+			exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))),
+		},
+		{
+			name: "amode with iconst and add",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				p := builder.CurrentBlock().AddParam(builder, ssa.TypeI64)
+				iconst := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
+				iadd := builder.AllocateInstruction().AsIadd(iconst.Return(), p).Insert(builder)
+
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder)
+
+				ctx.definitions[p] = &backend.SSAValueDefinition{BlockParamValue: p, BlkParamVReg: raxVReg}
+				ctx.definitions[iconst.Return()] = &backend.SSAValueDefinition{Instr: iconst}
+				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			exp: newOperandMem(newAmodeImmReg(456+789, raxVReg)),
+		},
+		{
+			name: "amode with iconst, block param and add",
+			setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) *backend.SSAValueDefinition {
+				iconst1 := builder.AllocateInstruction().AsIconst64(456).Insert(builder)
+				iconst2 := builder.AllocateInstruction().AsIconst64(123).Insert(builder)
+				iadd := builder.AllocateInstruction().AsIadd(iconst1.Return(), iconst2.Return()).Insert(builder)
+
+				instr := builder.AllocateInstruction()
+				instr.AsLoad(iadd.Return(), 789, ssa.TypeI64).Insert(builder)
+
+				ctx.definitions[iconst1.Return()] = &backend.SSAValueDefinition{Instr: iconst1}
+				ctx.definitions[iconst2.Return()] = &backend.SSAValueDefinition{Instr: iconst2}
+				ctx.definitions[iadd.Return()] = &backend.SSAValueDefinition{Instr: iadd}
+
+				return &backend.SSAValueDefinition{Instr: instr, N: 0}
+			},
+			instructions: []string{
+				fmt.Sprintf("movabsq $%d, %%r1?", 123+456+789),
+			},
+			exp: newOperandMem(newAmodeImmReg(0, regalloc.VReg(1).SetRegType(regalloc.RegTypeInt))),
+		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx, b, m := newSetupWithMockContext()
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 5404bfaa..1fc83580 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -79,7 +79,6 @@ func TestE2E(t *testing.T) {
 		},
 		{
 			name: "swap", m: testcases.SwapParamAndReturn.Module,
-			skipAMD64: true,
 			calls: []callCase{
 				{params: []uint64{math.MaxUint32, math.MaxInt32}, expResults: []uint64{math.MaxInt32, math.MaxUint32}},
 			},
@@ -126,7 +125,6 @@ func TestE2E(t *testing.T) {
 		},
 		{
 			name: "fibonacci_recursive", m: testcases.FibonacciRecursive.Module,
-			skipAMD64: true,
 			calls: []callCase{
 				{params: []uint64{0}, expResults: []uint64{0}},
 				{params: []uint64{1}, expResults: []uint64{1}},
@@ -297,9 +295,8 @@ func TestE2E(t *testing.T) {
 			},
 		},
 		{
-			name:      "multi_predecessor_local_ref",
-			m:         testcases.MultiPredecessorLocalRef.Module,
-			skipAMD64: true,
+			name: "multi_predecessor_local_ref",
+			m:    testcases.MultiPredecessorLocalRef.Module,
 			calls: []callCase{
 				{params: []uint64{0, 100}, expResults: []uint64{100}},
 				{params: []uint64{1, 100}, expResults: []uint64{1}},