diff --git a/codegen/masm/intrinsics/i64.masm b/codegen/masm/intrinsics/i64.masm index 0913adbb7..156aacc70 100644 --- a/codegen/masm/intrinsics/i64.masm +++ b/codegen/masm/intrinsics/i64.masm @@ -253,6 +253,40 @@ pub proc checked_div # [b_lo, b_hi, a_lo, a_hi] end end +# Computes `a % b`, asserting that both inputs are valid i64 values (u32 limbs). +# +# Execution traps on division by zero, and on `i64::MIN % -1` (the underlying division overflows). +pub proc checked_mod # [b_lo, b_hi, a_lo, a_hi] + u32assertw + + # Trap on i64::MIN % -1, since the underlying division overflows. + dup.1 dup.1 # [b_lo, b_hi, b_lo, b_hi, a_lo, a_hi] + push.NEG1_HI push.NEG1_LO exec.::miden::core::math::u64::eq # [b == -1, b_lo, b_hi, a_lo, a_hi] + dup.4 dup.4 # [a_lo, a_hi, b == -1, b_lo, b_hi, a_lo, a_hi] + push.MIN_HI push.MIN_LO exec.::miden::core::math::u64::eq # [a == MIN, b == -1, b_lo, b_hi, a_lo, a_hi] + and assertz # [b_lo, b_hi, a_lo, a_hi] + + # The remainder takes the sign of the dividend `a`, so `is_signed_a` will be needed later. + dup.3 push.SIGN_BIT u32and push.SIGN_BIT eq # [is_a_signed, b_lo, b_hi, a_lo, a_hi] + movdn.4 # [b_lo, b_hi, a_lo, a_hi, is_a_signed] + + # abs(a) + movup.3 movup.3 # [a_lo, a_hi, b_lo, b_hi, is_a_signed] + dup.4 movdn.2 # [a_lo, a_hi, is_a_signed, b_lo, b_hi, is_a_signed] + exec.cneg_u64 # [abs_a_lo, abs_a_hi, b_lo, b_hi, is_a_signed] + + # abs(b) + movup.3 movup.3 # [b_lo, b_hi, abs_a_lo, abs_a_hi, is_a_signed] + dup.1 push.SIGN_BIT u32and push.SIGN_BIT eq # [is_b_signed, b_lo, b_hi, abs_a_lo, abs_a_hi, is_a_signed] + movdn.2 exec.cneg_u64 # [abs_b_lo, abs_b_hi, abs_a_lo, abs_a_hi, is_a_signed] + + # r = abs(a) % abs(b); traps on division by zero. + exec.::miden::core::math::u64::mod # [r_lo, r_hi, is_a_signed] + + # Apply the dividend's sign to the remainder. + exec.cneg_u64 # [result_lo, result_hi] +end + # Given two i64 values in two's complement representation, compare them, # returning -1 if `a` < `b`, 0 if equal, and 1 if `a` > `b`. pub proc icmp # [b_lo, b_hi, a_lo, a_hi] diff --git a/codegen/masm/src/emit/binary.rs b/codegen/masm/src/emit/binary.rs index bdb27aa92..281915205 100644 --- a/codegen/masm/src/emit/binary.rs +++ b/codegen/masm/src/emit/binary.rs @@ -654,8 +654,9 @@ impl OpEmitter<'_> { assert_eq!(ty, rhs.ty(), "expected mod operands to be the same type"); match &ty { Type::U64 => self.checked_mod_u64(span), - Type::I32 => self.checked_mod_i32(span), + Type::I64 => self.checked_mod_i64(span), Type::U32 => self.checked_mod_u32(span), + Type::I32 => self.checked_mod_i32(span), ty @ (Type::U16 | Type::U8) => { self.checked_mod_uint(ty.size_in_bits() as u32, span); } @@ -678,8 +679,9 @@ impl OpEmitter<'_> { self.push_immediate(imm, span); self.checked_mod_u64(span); } - Type::I32 => self.checked_mod_imm_i32(imm.as_i32().unwrap(), span), + Type::I64 => self.checked_mod_imm_i64(imm.as_i64().unwrap(), span), Type::U32 => self.checked_mod_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 => self.checked_mod_imm_i32(imm.as_i32().unwrap(), span), ty @ (Type::U16 | Type::U8) => { self.checked_mod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); } diff --git a/codegen/masm/src/emit/int64.rs b/codegen/masm/src/emit/int64.rs index 108c1027b..65c4fad5d 100644 --- a/codegen/masm/src/emit/int64.rs +++ b/codegen/masm/src/emit/int64.rs @@ -713,6 +713,24 @@ impl OpEmitter<'_> { self.raw_exec("::intrinsics::i64::checked_div", span); } + /// Pops two i64 values off the stack, `b` and `a`, and performs `a % b`. + /// + /// This operation is checked, so if the operands or result are not valid i64, execution traps. + pub fn checked_mod_i64(&mut self, span: SourceSpan) { + self.raw_exec("::intrinsics::i64::checked_mod", span); + } + + /// Pops a i64 value off the stack, `a`, and performs `a % `. + /// + /// This function will panic if the divisor is zero. + /// + /// This operation is checked, so if the operand or result are not valid i64, execution traps. + pub fn checked_mod_imm_i64(&mut self, imm: i64, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.push_i64(imm, span); + self.raw_exec("::intrinsics::i64::checked_mod", span); + } + /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the /// stack. /// diff --git a/tests/integration/src/end_to_end/arithmetic.rs b/tests/integration/src/end_to_end/arithmetic.rs index 81e71d598..734ac793f 100644 --- a/tests/integration/src/end_to_end/arithmetic.rs +++ b/tests/integration/src/end_to_end/arithmetic.rs @@ -647,7 +647,6 @@ fn overflowing_rem_i32() { } #[test] -#[ignore = "https://github.com/0xMiden/compiler/issues/1000"] fn overflowing_rem_i64() { test_overflowing_arith( i64::overflowing_rem, @@ -868,7 +867,6 @@ fn checked_rem_i32() { } #[test] -#[ignore = "https://github.com/0xMiden/compiler/issues/1000"] fn checked_rem_i64() { test_checked_arith(i64::checked_rem, "checked_rem", NumericStrategy::rem_signed_checked()); } diff --git a/tests/integration/src/end_to_end/intrinsics/i64_arithmetic.rs b/tests/integration/src/end_to_end/intrinsics/i64_arithmetic.rs index 9608d6ceb..84ab73281 100644 --- a/tests/integration/src/end_to_end/intrinsics/i64_arithmetic.rs +++ b/tests/integration/src/end_to_end/intrinsics/i64_arithmetic.rs @@ -363,6 +363,30 @@ fn i64_checked_div() { ); } +#[test] +fn i64_checked_mod() { + let proc_body = r#" + # Stack: [b_lo, b_hi, a_lo, a_hi] + exec.::intrinsics::i64::checked_mod + # Stack: [r_lo, r_hi] +"#; + test_i64_intrinsic( + proc_body, + NumericStrategy::::rem_signed_checked(), + binary_i64op_inputs_to_stack, + |(a, b): &(i64, i64)| { + if *b == 0 { + Err(TrapExpectation::DivideByZero) + } else if *a == i64::MIN && *b == -1 { + Err(TrapExpectation::FailedAssertionOverflow) + } else { + Ok(vec![a.checked_rem(*b).unwrap()]) + } + }, + |stack: &[Felt]| decode_outputs_i64_only(stack, 1), + ); +} + #[test] fn i64_checked_shr() { let proc_body = r#"