Repository: tetratelabs/wazero
Pull request #1664 (2023-08-28 01:45:53)

wazevo: passes i32/i64 spectests 
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/cond.go b/internal/engine/wazevo/backend/isa/arm64/cond.go
index f3eef1a9..6f6cdd1b 100644
--- a/internal/engine/wazevo/backend/isa/arm64/cond.go
+++ b/internal/engine/wazevo/backend/isa/arm64/cond.go
@@ -213,86 +213,3 @@ func condFlagFromSSAFloatCmpCond(c ssa.FloatCmpCond) condFlag {
 		panic(c)
 	}
 }
-
-// vecArrangement is the arrangement of data within a vector register.
-type vecArrangement byte
-
-const (
-	// vecArrangementNone is an arrangement indicating no data is stored.
-	vecArrangementNone vecArrangement = iota
-	// vecArrangement8B is an arrangement of 8 bytes (64-bit vector)
-	vecArrangement8B
-	// vecArrangement16B is an arrangement of 16 bytes (128-bit vector)
-	vecArrangement16B
-	// vecArrangement4H is an arrangement of 4 half precisions (64-bit vector)
-	vecArrangement4H
-	// vecArrangement8H is an arrangement of 8 half precisions (128-bit vector)
-	vecArrangement8H
-	// vecArrangement2S is an arrangement of 2 single precisions (64-bit vector)
-	vecArrangement2S
-	// vecArrangement4S is an arrangement of 4 single precisions (128-bit vector)
-	vecArrangement4S
-	// vecArrangement1D is an arrangement of 1 double precision (64-bit vector)
-	vecArrangement1D
-	// vecArrangement2D is an arrangement of 2 double precisions (128-bit vector)
-	vecArrangement2D
-
-	// Assign each vector size specifier to a vector arrangement ID.
-	// Instructions can only have an arrangement or a size specifier, but not both, so it
-	// simplifies the internal representation of vector instructions by being able to
-	// store either into the same field.
-
-	// vecArrangementB is a size specifier of byte
-	vecArrangementB
-	// vecArrangementH is a size specifier of word (16-bit)
-	vecArrangementH
-	// vecArrangementS is a size specifier of double word (32-bit)
-	vecArrangementS
-	// vecArrangementD is a size specifier of quad word (64-bit)
-	vecArrangementD
-	// vecArrangementQ is a size specifier of the entire vector (128-bit)
-	vecArrangementQ
-)
-
-// String implements fmt.Stringer
-func (v vecArrangement) String() (ret string) {
-	switch v {
-	case vecArrangement8B:
-		ret = "8B"
-	case vecArrangement16B:
-		ret = "16B"
-	case vecArrangement4H:
-		ret = "4H"
-	case vecArrangement8H:
-		ret = "8H"
-	case vecArrangement2S:
-		ret = "2S"
-	case vecArrangement4S:
-		ret = "4S"
-	case vecArrangement1D:
-		ret = "1D"
-	case vecArrangement2D:
-		ret = "2D"
-	case vecArrangementB:
-		ret = "B"
-	case vecArrangementH:
-		ret = "H"
-	case vecArrangementS:
-		ret = "S"
-	case vecArrangementD:
-		ret = "D"
-	case vecArrangementQ:
-		ret = "Q"
-	case vecArrangementNone:
-		ret = "none"
-	default:
-		panic(v)
-	}
-	return
-}
-
-// vecIndex is the index of an element of a vector register
-type vecIndex byte
-
-// vecIndexNone indicates no vector index specified.
-const vecIndexNone = ^vecIndex(0)
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 5015a8a0..20d67493 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -101,6 +101,7 @@ var defKinds = [numInstructionKinds]defKind{
 	vecLanes:        defKindRD,
 	vecRRR:          defKindRD,
 	intToFpu:        defKindRD,
+	cCmpImm:         defKindNone,
 }
 
 // defs returns the list of regalloc.VReg that are defined by the instruction.
@@ -198,6 +199,7 @@ var useKinds = [numInstructionKinds]useKind{
 	fpuCSel:         useKindRNRM,
 	movToVec:        useKindRN,
 	movFromVec:      useKindRN,
+	cCmpImm:         useKindRN,
 	vecMisc:         useKindRN,
 	vecLanes:        useKindRN,
 	vecRRR:          useKindRNRM,
@@ -583,6 +585,17 @@ func (i *instruction) asFpuCmp(rn, rm operand, is64bit bool) {
 	}
 }
 
+func (i *instruction) asCCmpImm(rn operand, imm uint64, c condFlag, flag byte, is64bit bool) {
+	i.kind = cCmpImm
+	i.rn = rn
+	i.rm.data = imm
+	i.u1 = uint64(c)
+	i.u2 = uint64(flag)
+	if is64bit {
+		i.u3 = 1
+	}
+}
+
 // asALU setups a basic ALU instruction.
 func (i *instruction) asALU(aluOp aluOp, rd, rn, rm operand, dst64bit bool) {
 	switch rm.kind {
@@ -875,7 +888,11 @@ func (i *instruction) String() (str string) {
 	case cSet:
 		str = fmt.Sprintf("cset %s, %s", formatVRegSized(i.rd.nr(), 64), condFlag(i.u1))
 	case cCmpImm:
-		panic("TODO")
+		size := is64SizeBitToSize(i.u3)
+		str = fmt.Sprintf("ccmp %s, #%#x, #%#x, %s",
+			formatVRegSized(i.rn.nr(), size), i.rm.data,
+			i.u2&0b1111,
+			condFlag(i.u1))
 	case fpuMov64:
 		str = fmt.Sprintf("mov %s, %s",
 			formatVRegVec(i.rd.nr(), vecArrangement8B, vecIndexNone),
@@ -1345,10 +1362,10 @@ func (a aluOp) String() string {
 		return "sMulH"
 	case aluOpUMulH:
 		return "uMulH"
-	case aluOpSDiv64:
-		return "sDiv64"
-	case aluOpUDiv64:
-		return "uDiv64"
+	case aluOpSDiv:
+		return "sdiv"
+	case aluOpUDiv:
+		return "udiv"
 	case aluOpRotR:
 		return "ror"
 	case aluOpLsr:
@@ -1387,9 +1404,9 @@ const (
 	// Unsigned multiply, high-word result.
 	aluOpUMulH
 	// 64-bit Signed divide.
-	aluOpSDiv64
+	aluOpSDiv
 	// 64-bit Unsigned divide.
-	aluOpUDiv64
+	aluOpUDiv
 	// 32/64-bit Rotate right.
 	aluOpRotR
 	// 32/64-bit Logical shift right.
@@ -1704,3 +1721,86 @@ func (i *instruction) size() int64 {
 		return 4
 	}
 }
+
+// vecArrangement is the arrangement of data within a vector register.
+type vecArrangement byte
+
+const (
+	// vecArrangementNone is an arrangement indicating no data is stored.
+	vecArrangementNone vecArrangement = iota
+	// vecArrangement8B is an arrangement of 8 bytes (64-bit vector)
+	vecArrangement8B
+	// vecArrangement16B is an arrangement of 16 bytes (128-bit vector)
+	vecArrangement16B
+	// vecArrangement4H is an arrangement of 4 half precisions (64-bit vector)
+	vecArrangement4H
+	// vecArrangement8H is an arrangement of 8 half precisions (128-bit vector)
+	vecArrangement8H
+	// vecArrangement2S is an arrangement of 2 single precisions (64-bit vector)
+	vecArrangement2S
+	// vecArrangement4S is an arrangement of 4 single precisions (128-bit vector)
+	vecArrangement4S
+	// vecArrangement1D is an arrangement of 1 double precision (64-bit vector)
+	vecArrangement1D
+	// vecArrangement2D is an arrangement of 2 double precisions (128-bit vector)
+	vecArrangement2D
+
+	// Assign each vector size specifier to a vector arrangement ID.
+	// Instructions can only have an arrangement or a size specifier, but not both, so it
+	// simplifies the internal representation of vector instructions by being able to
+	// store either into the same field.
+
+	// vecArrangementB is a size specifier of byte
+	vecArrangementB
+	// vecArrangementH is a size specifier of word (16-bit)
+	vecArrangementH
+	// vecArrangementS is a size specifier of double word (32-bit)
+	vecArrangementS
+	// vecArrangementD is a size specifier of quad word (64-bit)
+	vecArrangementD
+	// vecArrangementQ is a size specifier of the entire vector (128-bit)
+	vecArrangementQ
+)
+
+// String implements fmt.Stringer
+func (v vecArrangement) String() (ret string) {
+	switch v {
+	case vecArrangement8B:
+		ret = "8B"
+	case vecArrangement16B:
+		ret = "16B"
+	case vecArrangement4H:
+		ret = "4H"
+	case vecArrangement8H:
+		ret = "8H"
+	case vecArrangement2S:
+		ret = "2S"
+	case vecArrangement4S:
+		ret = "4S"
+	case vecArrangement1D:
+		ret = "1D"
+	case vecArrangement2D:
+		ret = "2D"
+	case vecArrangementB:
+		ret = "B"
+	case vecArrangementH:
+		ret = "H"
+	case vecArrangementS:
+		ret = "S"
+	case vecArrangementD:
+		ret = "D"
+	case vecArrangementQ:
+		ret = "Q"
+	case vecArrangementNone:
+		ret = "none"
+	default:
+		panic(v)
+	}
+	return
+}
+
+// vecIndex is the index of an element of a vector register
+type vecIndex byte
+
+// vecIndexNone indicates no vector index specified.
+const vecIndexNone = ^vecIndex(0)
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index fd9fd313..a3625be6 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -274,6 +274,16 @@ func (i *instruction) encode(c backend.Compiler) {
 			regNumberInEncoding[i.rm.realReg()],
 			vecArrangement(i.u2),
 		))
+	case cCmpImm:
+		// Conditional compare (immediate) in https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en
+		sf := uint32(i.u3 & 0b1)
+		nzcv := uint32(i.u2 & 0b1111)
+		cond := uint32(condFlag(i.u1))
+		imm := uint32(i.rm.data & 0b11111)
+		rn := regNumberInEncoding[i.rn.realReg()]
+		c.Emit4Bytes(
+			sf<<31 | 0b111101001<<22 | imm<<16 | cond<<12 | 0b1<<11 | rn<<5 | nzcv,
+		)
 	default:
 		panic(i.String())
 	}
@@ -984,6 +994,14 @@ func encodeAluRRR(op aluOp, rd, rn, rm uint32, _64bit, isRnSp bool) uint32 {
 		case aluOpRotR:
 			_15to10 = 0b001011
 		}
+	case aluOpSDiv:
+		// "Data-processing (2 source)".
+		_31to21 = 0b11010110
+		_15to10 = 0b000011
+	case aluOpUDiv:
+		// "Data-processing (2 source)".
+		_31to21 = 0b11010110
+		_15to10 = 0b000010
 	default:
 		panic(op.String())
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index d20eef18..20dee76b 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -162,15 +162,15 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		m.lowerShifts(instr, extModeNone, aluOpLsl)
 	case ssa.OpcodeSshr:
 		if instr.Return().Type().Bits() == 64 {
-			m.lowerShifts(instr, extModeSignExtend64, aluOpLsr)
+			m.lowerShifts(instr, extModeSignExtend64, aluOpAsr)
 		} else {
-			m.lowerShifts(instr, extModeSignExtend32, aluOpLsr)
+			m.lowerShifts(instr, extModeSignExtend32, aluOpAsr)
 		}
 	case ssa.OpcodeUshr:
 		if instr.Return().Type().Bits() == 64 {
-			m.lowerShifts(instr, extModeZeroExtend64, aluOpAsr)
+			m.lowerShifts(instr, extModeZeroExtend64, aluOpLsr)
 		} else {
-			m.lowerShifts(instr, extModeZeroExtend32, aluOpAsr)
+			m.lowerShifts(instr, extModeZeroExtend32, aluOpLsr)
 		}
 	case ssa.OpcodeRotl:
 		m.lowerRotl(instr)
@@ -183,7 +183,7 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		x, y, c := instr.FcmpData()
 		m.lowerFcmp(x, y, instr.Return(), c)
 	case ssa.OpcodeImul:
-		x, y := instr.BinaryData()
+		x, y := instr.Arg2()
 		result := instr.Return()
 		m.lowerImul(x, y, result)
 	case ssa.OpcodeUndefined:
@@ -194,34 +194,34 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		c, x, y := instr.SelectData()
 		m.lowerSelect(c, x, y, instr.Return())
 	case ssa.OpcodeClz:
-		x := instr.UnaryData()
+		x := instr.Arg()
 		result := instr.Return()
 		m.lowerClz(x, result)
 	case ssa.OpcodeCtz:
-		x := instr.UnaryData()
+		x := instr.Arg()
 		result := instr.Return()
 		m.lowerCtz(x, result)
 	case ssa.OpcodePopcnt:
-		x := instr.UnaryData()
+		x := instr.Arg()
 		result := instr.Return()
 		m.lowerPopcnt(x, result)
 	case ssa.OpcodeFcvtFromSint:
-		x := instr.UnaryData()
+		x := instr.Arg()
 		result := instr.Return()
 		m.lowerIntToFpu(result, x, true, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
 	case ssa.OpcodeFcvtFromUint:
-		x := instr.UnaryData()
+		x := instr.Arg()
 		result := instr.Return()
 		m.lowerIntToFpu(result, x, false, x.Type() == ssa.TypeI64, result.Type().Bits() == 64)
 	case ssa.OpcodeFpromote:
-		v := instr.UnaryData()
+		v := instr.Arg()
 		rn := m.getOperand_NR(m.compiler.ValueDefinition(v), extModeNone)
 		rd := operandNR(m.compiler.VRegOf(instr.Return()))
 		cnt := m.allocateInstr()
 		cnt.asFpuRR(fpuUniOpCvt32To64, rd, rn, true)
 		m.insert(cnt)
 	case ssa.OpcodeIreduce:
-		rn := m.getOperand_NR(m.compiler.ValueDefinition(instr.UnaryData()), extModeNone)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(instr.Arg()), extModeNone)
 		retVal := instr.Return()
 		rd := m.compiler.VRegOf(retVal)
 
@@ -248,14 +248,85 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 	case ssa.OpcodeBitcast:
 		m.lowerBitcast(instr)
 	case ssa.OpcodeFcopysign:
-		x, y := instr.BinaryData()
+		x, y := instr.Arg2()
 		m.lowerFcopysign(x, y, instr.Return())
+	case ssa.OpcodeSdiv, ssa.OpcodeUdiv:
+		x, y, ctx := instr.Arg3()
+		ctxVReg := m.compiler.VRegOf(ctx)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		m.lowerIDiv(ctxVReg, rd, rn, rm, x.Type() == ssa.TypeI64, op == ssa.OpcodeSdiv)
+	case ssa.OpcodeSrem, ssa.OpcodeUrem:
+		x, y, ctx := instr.Arg3()
+		ctxVReg := m.compiler.VRegOf(ctx)
+		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+		rd := operandNR(m.compiler.VRegOf(instr.Return()))
+		m.lowerIRem(ctxVReg, rd, rn, rm, x.Type() == ssa.TypeI64, op == ssa.OpcodeSrem)
 	default:
-		panic("TODO: lowering " + instr.Opcode().String())
+		panic("TODO: lowering " + op.String())
 	}
 	m.FlushPendingInstructions()
 }
 
+func (m *machine) lowerIRem(execCtxVReg regalloc.VReg, rd, rn, rm operand, _64bit, signed bool) {
+	div := m.allocateInstr()
+
+	if signed {
+		div.asALU(aluOpSDiv, rd, rn, rm, _64bit)
+	} else {
+		div.asALU(aluOpUDiv, rd, rn, rm, _64bit)
+	}
+	m.insert(div)
+
+	// Check if rm is zero:
+	m.exitIfNot(execCtxVReg, registerAsRegNotZeroCond(rm.nr()), wazevoapi.ExitCodeIntegerDivisionByZero)
+
+	// rd = rn-rd*rm by MSUB instruction.
+	msub := m.allocateInstr()
+	msub.asALURRRR(aluOpMSub, rd, rd, rm, rn, _64bit)
+	m.insert(msub)
+}
+
+func (m *machine) lowerIDiv(execCtxVReg regalloc.VReg, rd, rn, rm operand, _64bit, signed bool) {
+	div := m.allocateInstr()
+
+	if signed {
+		div.asALU(aluOpSDiv, rd, rn, rm, _64bit)
+	} else {
+		div.asALU(aluOpUDiv, rd, rn, rm, _64bit)
+	}
+	m.insert(div)
+
+	// Check if rm is zero:
+	m.exitIfNot(execCtxVReg, registerAsRegNotZeroCond(rm.nr()), wazevoapi.ExitCodeIntegerDivisionByZero)
+
+	if signed {
+		// We need to check the signed overflow which happens iff "math.MinInt{32,64} / -1"
+		minusOneCheck := m.allocateInstr()
+		// Sets eq condition if rm == -1.
+		minusOneCheck.asALU(aluOpAddS, operandNR(xzrVReg), rm, operandImm12(1, 0), _64bit)
+		m.insert(minusOneCheck)
+
+		ccmp := m.allocateInstr()
+		// If eq condition is set, sets the flag by the result based on "rn - 1", otherwise clears the flag.
+		ccmp.asCCmpImm(rn, 1, eq, 0, _64bit)
+		m.insert(ccmp)
+
+		// Check the overflow flag.
+		m.exitIfNot(execCtxVReg, vs.invert().asCond(), wazevoapi.ExitCodeIntegerOverflow)
+	}
+}
+
+func (m *machine) exitIfNot(execCtxVReg regalloc.VReg, c cond, code wazevoapi.ExitCode) {
+	cbr := m.allocateInstr()
+	cbr.asCondBr(c, invalidLabel, false /* ignored */)
+	cbr.condBrOffsetResolve(exitWithCodeEncodingSize + 4 /* br offset is from the beginning of this instruction */)
+	m.insert(cbr)
+	m.lowerExitWithCode(execCtxVReg, code)
+}
+
 func (m *machine) lowerFcopysign(x, y, ret ssa.Value) {
 	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
 	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
@@ -354,7 +425,7 @@ func (m *machine) lowerFpuBinOp(si *ssa.Instruction) {
 	case ssa.OpcodeFmin:
 		op = fpuBinOpMin
 	}
-	x, y := si.BinaryData()
+	x, y := si.Arg2()
 	xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y)
 	rn := m.getOperand_NR(xDef, extModeNone)
 	rm := m.getOperand_NR(yDef, extModeNone)
@@ -364,7 +435,7 @@ func (m *machine) lowerFpuBinOp(si *ssa.Instruction) {
 }
 
 func (m *machine) lowerSubOrAdd(si *ssa.Instruction, add bool) {
-	x, y := si.BinaryData()
+	x, y := si.Arg2()
 	if !x.Type().IsInt() {
 		panic("BUG?")
 	}
@@ -436,7 +507,7 @@ func (m *machine) lowerIcmp(si *ssa.Instruction) {
 }
 
 func (m *machine) lowerShifts(si *ssa.Instruction, ext extMode, aluOp aluOp) {
-	x, amount := si.BinaryData()
+	x, amount := si.Arg2()
 	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), ext)
 	rm := m.getOperand_ShiftImm_NR(m.compiler.ValueDefinition(amount), ext, x.Type().Bits())
 	rd := operandNR(m.compiler.VRegOf(si.Return()))
@@ -447,7 +518,7 @@ func (m *machine) lowerShifts(si *ssa.Instruction, ext extMode, aluOp aluOp) {
 }
 
 func (m *machine) lowerBitwiseAluOp(si *ssa.Instruction, op aluOp) {
-	x, y := si.BinaryData()
+	x, y := si.Arg2()
 	if !x.Type().IsInt() {
 		panic("BUG?")
 	}
@@ -463,33 +534,25 @@ func (m *machine) lowerBitwiseAluOp(si *ssa.Instruction, op aluOp) {
 }
 
 func (m *machine) lowerRotl(si *ssa.Instruction) {
-	x, y := si.BinaryData()
-	if !x.Type().IsInt() {
-		panic("BUG?")
-	}
+	x, y := si.Arg2()
+	r := si.Return()
+	_64 := r.Type().Bits() == 64
 
-	// Encode rotl as neg + rotr: neg is really a sub against the zero-reg.
-
-	xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y)
-	rn := m.getOperand_NR(xDef, extModeNone)
-	rm := m.getOperand_NR(yDef, extModeNone)
-	rd := operandNR(m.compiler.VRegOf(si.Return()))
+	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+	rd := operandNR(m.compiler.VRegOf(r))
 
-	// Encode neg as sub $reg, xzr, $reg.
+	// Encode rotl as neg + rotr: neg is a sub against the zero-reg.
 	neg := m.allocateInstr()
-	neg.asALU(aluOpSub, rn, operandNR(xzrVReg), rn, si.Return().Type().Bits() == 64)
+	neg.asALU(aluOpSub, rm, operandNR(xzrVReg), rm, _64)
 	m.insert(neg)
-
 	alu := m.allocateInstr()
-	alu.asALU(aluOpRotR, rd, rn, rm, si.Return().Type().Bits() == 64)
+	alu.asALU(aluOpRotR, rd, rn, rm, _64)
 	m.insert(alu)
 }
 
 func (m *machine) lowerRotr(si *ssa.Instruction) {
-	x, y := si.BinaryData()
-	if !x.Type().IsInt() {
-		panic("BUG?")
-	}
+	x, y := si.Arg2()
 
 	xDef, yDef := m.compiler.ValueDefinition(x), m.compiler.ValueDefinition(y)
 	rn := m.getOperand_NR(xDef, extModeNone)
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
index e570caa4..00c1b2c3 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
@@ -338,7 +338,7 @@ func (m *machine) collectAddends(ptr ssa.Value) (addends32 []addend32, addends64
 		switch op := m.compiler.MatchInstrOneOf(def, addendsMatchOpcodes[:]); op {
 		case ssa.OpcodeIadd:
 			// If the addend is an add, we recursively collect its operands.
-			x, y := def.Instr.BinaryData()
+			x, y := def.Instr.Arg2()
 			m.addendsWorkQueue = append(m.addendsWorkQueue, x, y)
 			m.compiler.MarkLowered(def.Instr)
 		case ssa.OpcodeIconst:
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 9ab7594a..d4baf236 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -172,6 +172,10 @@ func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint6
 			return wasmruntime.ErrRuntimeInvalidTableAccess
 		case wazevoapi.ExitCodeIndirectCallTypeMismatch:
 			return wasmruntime.ErrRuntimeIndirectCallTypeMismatch
+		case wazevoapi.ExitCodeIntegerOverflow:
+			return wasmruntime.ErrRuntimeIntegerOverflow
+		case wazevoapi.ExitCodeIntegerDivisionByZero:
+			return wasmruntime.ErrRuntimeIntegerDivideByZero
 		default:
 			panic("BUG")
 		}
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index a54d9ff6..da47fe4e 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -516,7 +516,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		y, x := state.pop(), state.pop()
-		result := builder.AllocateInstruction().AsSDiv(x, y).Insert(builder).Return()
+		result := builder.AllocateInstruction().AsSDiv(x, y, c.execCtxPtrValue).Insert(builder).Return()
 		state.push(result)
 
 	case wasm.OpcodeI32DivU, wasm.OpcodeI64DivU:
@@ -524,7 +524,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		y, x := state.pop(), state.pop()
-		result := builder.AllocateInstruction().AsUDiv(x, y).Insert(builder).Return()
+		result := builder.AllocateInstruction().AsUDiv(x, y, c.execCtxPtrValue).Insert(builder).Return()
 		state.push(result)
 
 	case wasm.OpcodeI32RemS, wasm.OpcodeI64RemS:
@@ -532,7 +532,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		y, x := state.pop(), state.pop()
-		result := builder.AllocateInstruction().AsSRem(x, y).Insert(builder).Return()
+		result := builder.AllocateInstruction().AsSRem(x, y, c.execCtxPtrValue).Insert(builder).Return()
 		state.push(result)
 
 	case wasm.OpcodeI32RemU, wasm.OpcodeI64RemU:
@@ -540,7 +540,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		y, x := state.pop(), state.pop()
-		result := builder.AllocateInstruction().AsURem(x, y).Insert(builder).Return()
+		result := builder.AllocateInstruction().AsURem(x, y, c.execCtxPtrValue).Insert(builder).Return()
 		state.push(result)
 
 	case wasm.OpcodeI32And, wasm.OpcodeI64And:
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 39e31850..ecfcbeed 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -25,6 +25,9 @@ type Builder interface {
 	// CurrentBlock returns the currently handled BasicBlock which is set by the latest call to SetCurrentBlock.
 	CurrentBlock() BasicBlock
 
+	// EntryBlock returns the entry BasicBlock of the currently-compiled function.
+	EntryBlock() BasicBlock
+
 	// SetCurrentBlock sets the instruction insertion target to the BasicBlock `b`.
 	SetCurrentBlock(b BasicBlock)
 
@@ -337,6 +340,11 @@ func (b *builder) CurrentBlock() BasicBlock {
 	return b.currentBB
 }
 
+// EntryBlock implements Builder.EntryBlock.
+func (b *builder) EntryBlock() BasicBlock {
+	return b.entryBlk()
+}
+
 // DeclareVariable implements Builder.DeclareVariable.
 func (b *builder) DeclareVariable(typ Type) Variable {
 	v := b.allocateVariable()
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index b98053b1..c89d4795 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -90,11 +90,16 @@ func (i *Instruction) Arg() Value {
 	return i.v
 }
 
-// Arg2 returns the first two argument2 to this instruction.
+// Arg2 returns the first two arguments to this instruction.
 func (i *Instruction) Arg2() (Value, Value) {
 	return i.v, i.v2
 }
 
+// Arg3 returns the first three arguments to this instruction.
+func (i *Instruction) Arg3() (Value, Value, Value) {
+	return i.v, i.v2, i.v3
+}
+
 // Next returns the next instruction laid out next to itself.
 func (i *Instruction) Next() *Instruction {
 	return i.next
@@ -389,20 +394,16 @@ const (
 	// `v = sqmul_round_sat x, y`.
 	OpcodeSqmulRoundSat
 
-	// OpcodeUdiv ...
-	// `v = udiv x, y`.
+	// OpcodeUdiv performs the unsigned integer division `v = Udiv x, y`.
 	OpcodeUdiv
 
-	// OpcodeSdiv ...
-	// `v = sdiv x, y`.
+	// OpcodeSdiv performs the signed integer division `v = Sdiv x, y`.
 	OpcodeSdiv
 
-	// OpcodeUrem ...
-	// `v = urem x, y`.
+	// OpcodeUrem computes the remainder of the unsigned integer division `v = Urem x, y`.
 	OpcodeUrem
 
-	// OpcodeSrem ...
-	// `v = srem x, y`.
+	// OpcodeSrem computes the remainder of the signed integer division `v = Srem x, y`.
 	OpcodeSrem
 
 	// OpcodeIaddImm ...
@@ -525,12 +526,10 @@ const (
 	// `v = bxor_imm x, Y`. (BinaryImm64)
 	OpcodeBxorImm
 
-	// OpcodeRotl ...
-	// `v = rotl x, y`.
+	// OpcodeRotl rotates the given integer value to the left: `v = Rotl x, y`.
 	OpcodeRotl
 
-	// OpcodeRotr ...
-	// `v = rotr x, y`.
+	// OpcodeRotr rotates the given integer value to the right: `v = Rotr x, y`.
 	OpcodeRotr
 
 	// OpcodeRotlImm ...
@@ -878,7 +877,9 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeTrunc:              sideEffectFalse,
 	OpcodeNearest:            sideEffectFalse,
 	OpcodeSdiv:               sideEffectFalse,
+	OpcodeSrem:               sideEffectFalse,
 	OpcodeUdiv:               sideEffectFalse,
+	OpcodeUrem:               sideEffectFalse,
 	OpcodeFabs:               sideEffectFalse,
 	OpcodeFcopysign:          sideEffectFalse,
 }
@@ -904,7 +905,9 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeIshl:      returnTypesFnSingle,
 	OpcodeSshr:      returnTypesFnSingle,
 	OpcodeSdiv:      returnTypesFnSingle,
+	OpcodeSrem:      returnTypesFnSingle,
 	OpcodeUdiv:      returnTypesFnSingle,
+	OpcodeUrem:      returnTypesFnSingle,
 	OpcodeUshr:      returnTypesFnSingle,
 	OpcodeJump:      returnTypesFnNoReturns,
 	OpcodeUndefined: returnTypesFnNoReturns,
@@ -1060,11 +1063,6 @@ func (i *Instruction) AsIconst32(v uint32) *Instruction {
 	return i
 }
 
-// BinaryData return the operands for a binary instruction.
-func (i *Instruction) BinaryData() (x, y Value) {
-	return i.v, i.v2
-}
-
 // AsIadd initializes this instruction as an integer addition instruction with OpcodeIadd.
 func (i *Instruction) AsIadd(x, y Value) *Instruction {
 	i.opcode = OpcodeIadd
@@ -1116,37 +1114,41 @@ func (i *Instruction) AsFcmp(x, y Value, c FloatCmpCond) {
 }
 
 // AsSDiv initializes this instruction as an integer bitwise and instruction with OpcodeSdiv.
-func (i *Instruction) AsSDiv(x, y Value) *Instruction {
+func (i *Instruction) AsSDiv(x, y, ctx Value) *Instruction {
 	i.opcode = OpcodeSdiv
 	i.v = x
 	i.v2 = y
+	i.v3 = ctx
 	i.typ = x.Type()
 	return i
 }
 
 // AsUDiv initializes this instruction as an integer bitwise and instruction with OpcodeUdiv.
-func (i *Instruction) AsUDiv(x, y Value) *Instruction {
+func (i *Instruction) AsUDiv(x, y, ctx Value) *Instruction {
 	i.opcode = OpcodeUdiv
 	i.v = x
 	i.v2 = y
+	i.v3 = ctx
 	i.typ = x.Type()
 	return i
 }
 
 // AsSRem initializes this instruction as an integer bitwise and instruction with OpcodeSrem.
-func (i *Instruction) AsSRem(x, y Value) *Instruction {
+func (i *Instruction) AsSRem(x, y, ctx Value) *Instruction {
 	i.opcode = OpcodeSrem
 	i.v = x
 	i.v2 = y
+	i.v3 = ctx
 	i.typ = x.Type()
 	return i
 }
 
 // AsURem initializes this instruction as an integer bitwise and instruction with OpcodeUrem.
-func (i *Instruction) AsURem(x, y Value) *Instruction {
+func (i *Instruction) AsURem(x, y, ctx Value) *Instruction {
 	i.opcode = OpcodeUrem
 	i.v = x
 	i.v2 = y
+	i.v3 = ctx
 	i.typ = x.Type()
 	return i
 }
@@ -1482,11 +1484,6 @@ func (i *Instruction) AsPopcnt(x Value) {
 	i.typ = x.Type()
 }
 
-// UnaryData return the operand for a unary instruction.
-func (i *Instruction) UnaryData() Value {
-	return i.v
-}
-
 // AsFneg initializes this instruction as an instruction with OpcodeFneg.
 func (i *Instruction) AsFneg(x Value) *Instruction {
 	i.opcode = OpcodeFneg
@@ -1735,7 +1732,7 @@ func (i *Instruction) Format(b Builder) string {
 		}
 		instSuffix += "]"
 	case OpcodeBand, OpcodeBor, OpcodeBxor, OpcodeRotr, OpcodeRotl, OpcodeIshl, OpcodeSshr, OpcodeUshr,
-		OpcodeSdiv, OpcodeUdiv, OpcodeFcopysign:
+		OpcodeSdiv, OpcodeUdiv, OpcodeFcopysign, OpcodeSrem, OpcodeUrem:
 		instSuffix = fmt.Sprintf(" %s, %s", i.v.Format(b), i.v2.Format(b))
 	case OpcodeUndefined:
 	case OpcodeClz, OpcodeCtz, OpcodePopcnt, OpcodeFneg, OpcodeFcvtFromSint, OpcodeFcvtFromUint, OpcodeFpromote,
diff --git a/internal/engine/wazevo/wazevoapi/exitcode.go b/internal/engine/wazevo/wazevoapi/exitcode.go
index 0afdd8c0..08ca7661 100644
--- a/internal/engine/wazevo/wazevoapi/exitcode.go
+++ b/internal/engine/wazevo/wazevoapi/exitcode.go
@@ -16,6 +16,8 @@ const (
 	ExitCodeTableOutOfBounds
 	ExitCodeIndirectCallNullPointer
 	ExitCodeIndirectCallTypeMismatch
+	ExitCodeIntegerDivisionByZero
+	ExitCodeIntegerOverflow
 	exitCodeMax
 )
 
@@ -42,6 +44,10 @@ func (e ExitCode) String() string {
 		return "indirect_call_null_pointer"
 	case ExitCodeIndirectCallTypeMismatch:
 		return "indirect_call_type_mismatch"
+	case ExitCodeIntegerDivisionByZero:
+		return "integer_division_by_zero"
+	case ExitCodeIntegerOverflow:
+		return "integer_overflow"
 	}
 	panic("TODO")
 }


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index e9272aac..8a1d31f9 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -1327,7 +1327,7 @@ L1 (SSA Block: blk0):
 	orr x11?, x4?, x5?
 	eor x12?, x4?, x5?
 	eor x15?, x4?, x5?, lsl #8
-	sub x4?, xzr, x4?
+	sub x5?, xzr, x5?
 	ror x16?, x4?, x5?
 	ror x17?, x4?, x5?
 	str x17?, [#ret_space, #0x8]
@@ -1356,7 +1356,7 @@ L1 (SSA Block: blk0):
 	orr x5, x8, x9
 	eor x6, x8, x9
 	eor x7, x8, x9, lsl #8
-	sub x8, xzr, x8
+	sub x9, xzr, x9
 	ror x10, x8, x9
 	ror x8, x8, x9
 	str x8, [sp, #0x18]
@@ -1378,14 +1378,14 @@ L1 (SSA Block: blk0):
 	lsl w8?, w2?, 0x1f
 	lsl x9?, x4?, x5?
 	lsl x11?, x4?, 0x20
-	asr w12?, w2?, w3?
-	asr w14?, w2?, 0x1f
-	asr x15?, x4?, x5?
-	asr x17?, x4?, 0x20
-	lsr w18?, w2?, w3?
-	lsr w20?, w2?, 0x1f
-	lsr x21?, x4?, x5?
-	lsr x23?, x4?, 0x20
+	lsr w12?, w2?, w3?
+	lsr w14?, w2?, 0x1f
+	lsr x15?, x4?, x5?
+	lsr x17?, x4?, 0x20
+	asr w18?, w2?, w3?
+	asr w20?, w2?, 0x1f
+	asr x21?, x4?, x5?
+	asr x23?, x4?, 0x20
 	str x23?, [#ret_space, #0x18]
 	str x21?, [#ret_space, #0x10]
 	str w20?, [#ret_space, #0x8]
@@ -1411,14 +1411,14 @@ L1 (SSA Block: blk0):
 	lsl w1, w9, 0x1f
 	lsl x2, x8, x11
 	lsl x3, x8, 0x20
-	asr w4, w9, w10
-	asr w5, w9, 0x1f
-	asr x6, x8, x11
-	asr x7, x8, 0x20
-	lsr w10, w9, w10
-	lsr w9, w9, 0x1f
-	lsr x11, x8, x11
-	lsr x8, x8, 0x20
+	lsr w4, w9, w10
+	lsr w5, w9, 0x1f
+	lsr x6, x8, x11
+	lsr x7, x8, 0x20
+	asr w10, w9, w10
+	asr w9, w9, 0x1f
+	asr x11, x8, x11
+	asr x8, x8, 0x20
 	str x8, [sp, #0x28]
 	str x11, [sp, #0x20]
 	str w9, [sp, #0x18]
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
index 2c47a27b..d2fdd97d 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -19,6 +19,8 @@ func TestInstruction_encode(t *testing.T) {
 		setup func(*instruction)
 		want  string
 	}{
+		{want: "2f08417a", setup: func(i *instruction) { i.asCCmpImm(operandNR(x1VReg), 1, eq, 0b1111, false) }},
+		{want: "201841fa", setup: func(i *instruction) { i.asCCmpImm(operandNR(x1VReg), 1, ne, 0, true) }},
 		{want: "411ca32e", setup: func(i *instruction) {
 			i.asVecRRR(vecOpBit, operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), vecArrangement8B)
 		}},
@@ -445,6 +447,18 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "4028d49a", setup: func(i *instruction) {
 			i.asALU(aluOpAsr, operandNR(x0VReg), operandNR(x2VReg), operandNR(x20VReg), true)
 		}},
+		{want: "400cd49a", setup: func(i *instruction) {
+			i.asALU(aluOpSDiv, operandNR(x0VReg), operandNR(x2VReg), operandNR(x20VReg), true)
+		}},
+		{want: "400cd41a", setup: func(i *instruction) {
+			i.asALU(aluOpSDiv, operandNR(x0VReg), operandNR(x2VReg), operandNR(x20VReg), false)
+		}},
+		{want: "4008d49a", setup: func(i *instruction) {
+			i.asALU(aluOpUDiv, operandNR(x0VReg), operandNR(x2VReg), operandNR(x20VReg), true)
+		}},
+		{want: "4008d41a", setup: func(i *instruction) {
+			i.asALU(aluOpUDiv, operandNR(x0VReg), operandNR(x2VReg), operandNR(x20VReg), false)
+		}},
 		{want: "407c0113", setup: func(i *instruction) {
 			i.asALUShift(aluOpAsr, operandNR(x0VReg), operandNR(x2VReg), operandShiftImm(1), false)
 		}},
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
index ef0553b6..3250f9b9 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -1,6 +1,7 @@
 package arm64
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -275,3 +276,67 @@ func TestMachine_InsertMove(t *testing.T) {
 		})
 	}
 }
+
+func TestMachine_lowerIDiv(t *testing.T) {
+	for _, tc := range []struct {
+		name   string
+		_64bit bool
+		signed bool
+		exp    string
+	}{
+		{
+			name: "32bit unsigned", _64bit: false, signed: false,
+			exp: `
+udiv w1?, w2?, w3?
+cbnz w3?, #0x20 (L0)
+movz x27, #0xa, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+`,
+		},
+		{name: "32bit signed", _64bit: false, signed: true, exp: `
+sdiv w1?, w2?, w3?
+cbnz w3?, #0x20 (L0)
+movz x27, #0xa, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+adds wzr, w3?, #0x1
+ccmp w2?, #0x1, #0x0, eq
+b.vc #0x20
+movz x27, #0xb, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+`},
+		{name: "64bit unsigned", _64bit: true, signed: false, exp: `
+udiv x1?, x2?, x3?
+cbnz w3?, #0x20 (L0)
+movz x27, #0xa, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+`},
+		{name: "64bit signed", _64bit: true, signed: true, exp: `
+sdiv x1?, x2?, x3?
+cbnz w3?, #0x20 (L0)
+movz x27, #0xa, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+adds xzr, x3?, #0x1
+ccmp x2?, #0x1, #0x0, eq
+b.vc #0x20
+movz x27, #0xb, lsl 0
+str w27, [x65535?]
+exit_sequence x65535?
+`},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			execCtx := regalloc.VReg(0xffff).SetRegType(regalloc.RegTypeInt)
+			rd, rn, rm := regalloc.VReg(1).SetRegType(regalloc.RegTypeInt),
+				regalloc.VReg(2).SetRegType(regalloc.RegTypeInt),
+				regalloc.VReg(3).SetRegType(regalloc.RegTypeInt)
+			_, _, m := newSetupWithMockContext()
+			m.lowerIDiv(execCtx, operandNR(rd), operandNR(rn), operandNR(rm), tc._64bit, tc.signed)
+			fmt.Println(formatEmittedInstructionsInCurrentBlock(m))
+			require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
+		})
+	}
+}
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 544dd502..e8bb0dff 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -73,9 +73,10 @@ func TestSpectestV1(t *testing.T) {
 		{name: "if"},
 		{name: "imports"},
 		{name: "inline-module"},
-		//{name: "i32"},
-		//{name: "int_exprs"},
-		//{name: "int_literals"},
+		{name: "i32"},
+		{name: "i64"},
+		{name: "int_exprs"},
+		{name: "int_literals"},
 		{name: "labels"},
 		//{name: "left-to-right"},
 		{name: "load"},
@@ -409,7 +410,6 @@ func TestE2E_host_functions(t *testing.T) {
 	b := r.NewHostModuleBuilder("env")
 	b.NewFunctionBuilder().WithFunc(func(ctx2 context.Context, d float64) float64 {
 		require.Equal(t, ctx, ctx2)
-		fmt.Printf("%#x\n", math.Float64bits(d))
 		require.Equal(t, 35.0, d)
 		return math.Sqrt(d)
 	}).Export("root")