Skip to content

Commit

Permalink
compiler-rt: alu: add signed remainder for i8
Browse files Browse the repository at this point in the history
Implement the following polyfill:
- `__llvm_srem_i8_i8`

The implementation is done by extending the existing sdiv implementation
by the calculation of a remainder and then using that
division-with-reminder calculation for both `__llvm_srem_i8_i8` and
`__llvm_sdiv_i8_i8` to avoid code duplication.

Signed-off-by: Wojciech Zmuda <[email protected]>
  • Loading branch information
wzmuda committed Jan 23, 2025
1 parent 9405b0d commit 3c2a57a
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 14 deletions.
1 change: 1 addition & 0 deletions compiler-rt/src/alu.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ pub mod smul_with_overflow;
pub mod udiv;
pub mod sdiv;
pub mod urem;
pub mod srem;

mod test_case;
58 changes: 45 additions & 13 deletions compiler-rt/src/alu/sdiv.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::utils::assert_fits_in_type;
use crate::alu::shl::shl;
use core::num::traits::{BitSize, Bounded, WrappingAdd};

// Perform the `sdiv` operation.
// Perform signed division of integers. Return quotient and remainder.
//
// This is a generic implementation for every data type. Its specialized versions
// are defined and tested in the sdiv/sdiv_<type>.cairo files.
fn sdiv<
// This function is used to implement `sdiv` and `srem` found in the `sdiv/sdiv.cario`
// and `srem/srem.cairo` files.
pub fn divide_with_remainder_signed<
T,
// The trait bounds are chosen so that:
//
Expand All @@ -25,7 +25,7 @@ fn sdiv<
impl TDestruct: Destruct<T>,
>(
lhs: u128, rhs: u128,
) -> u128 {
) -> (u128, u128) {
// Make sure the value passed in the u128 arguments can fit in the concrete type.
assert_fits_in_type::<T>(lhs);
assert_fits_in_type::<T>(rhs);
Expand All @@ -34,7 +34,7 @@ fn sdiv<
let sign_mask = shl::<u128>(1, BitSize::<T>::bits().into() - 1);
let is_dividend_negative = (lhs & sign_mask) != 0;
let is_divisor_negative = (rhs & sign_mask) != 0;
let is_result_negative = is_dividend_negative ^ is_divisor_negative;
let is_quotient_negative = is_dividend_negative ^ is_divisor_negative;

// A helper function to compute two's complement
let twos_complement = |x: u128| -> u128 {
Expand All @@ -57,17 +57,49 @@ fn sdiv<
// Adjust quotient for floor division if result is negative and there's a remainder.
let quotient_unsigned = abs_dividend / abs_divisor;
let remainder = abs_dividend % abs_divisor;
let quotient_unsigned = if is_result_negative && remainder != 0 {
quotient_unsigned.wrapping_add(1)
let (quotient_unsigned, remainder) = if is_quotient_negative && remainder != 0 {
(quotient_unsigned.wrapping_add(1), abs_divisor - remainder)
} else {
quotient_unsigned
(quotient_unsigned, remainder)
};

// Apply sign to the quotient
if is_result_negative {
// Apply sign to the quotient and the remainder
let quotient = if is_quotient_negative {
twos_complement(quotient_unsigned)
} else {
quotient_unsigned
}
& Bounded::<T>::MAX.into()
};
let remainder = if is_divisor_negative && remainder != 0 {
twos_complement(remainder)
} else {
remainder
};

(quotient & Bounded::<T>::MAX.into(), remainder & Bounded::<T>::MAX.into())
}

// Perform the `sdiv` operation.
//
// This is a generic implementation for every data type. Its specialized versions
// are defined and tested in the sdiv/sdiv_<type>.cairo files.
fn sdiv<
T,
// The trait bounds are chosen so that:
//
// - BitSize<T>: we can determine the length of the data type in bits,
// - Bounded<T>: we can determine min and max value of the type,
// - TryInto<u128, T>, Into<T, u128> - we can convert the type from/to u128,
// - Destruct<T>: the type can be dropped as the result of the downcasting check.
//
// Overall these trait bounds allow any unsigned integer to be used as the concrete type.
impl TBitSize: BitSize<T>,
impl TBounded: Bounded<T>,
impl TTryInto: TryInto<u128, T>,
impl TInto: Into<T, u128>,
impl TDestruct: Destruct<T>,
>(
lhs: u128, rhs: u128,
) -> u128 {
let (quotient, _) = divide_with_remainder_signed::<T>(lhs, rhs);
quotient
}
2 changes: 1 addition & 1 deletion compiler-rt/src/alu/sdiv/sdiv_i8.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ mod tests {
TestCaseTwoArgs{lhs: 0b11101000, rhs: 0b10011110, expected: 0}, // lhs = -24 rhs = -98
TestCaseTwoArgs{lhs: 0b11100111, rhs: 53, expected: 0b11111111}, // lhs = -25 expected = -1
TestCaseTwoArgs{lhs: 14, rhs: 0b10111001, expected: 0b11111111}, // rhs = -71 expected = -1

// Edge cases
TestCaseTwoArgs{lhs: 0, rhs: 127, expected: 0},
TestCaseTwoArgs{lhs: 0, rhs: 0b10000000, expected: 0}, // rhs = -128
Expand Down
30 changes: 30 additions & 0 deletions compiler-rt/src/alu/srem.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pub mod srem_i8;

use core::num::traits::{BitSize, Bounded};
use crate::alu::sdiv::divide_with_remainder_signed;

// Perform the `srem` operation.
//
// This is a generic implementation for every data type. Its specialized versions
// are defined and tested in the srem/srem_<type>.cairo files.
fn srem<
T,
// The trait bounds are chosen so that:
//
// - BitSize<T>: we can determine the length of the data type in bits,
// - Bounded<T>: we can determine min and max value of the type,
// - TryInto<u128, T>, Into<T, u128> - we can convert the type from/to u128,
// - Destruct<T>: the type can be dropped as the result of the downcasting check.
//
// Overall these trait bounds allow any unsigned integer to be used as the concrete type.
impl TBitSize: BitSize<T>,
impl TBounded: Bounded<T>,
impl TTryInto: TryInto<u128, T>,
impl TInto: Into<T, u128>,
impl TDestruct: Destruct<T>,
>(
lhs: u128, rhs: u128,
) -> u128 {
let (_, remainder) = divide_with_remainder_signed::<T>(lhs, rhs);
remainder
}
Loading

0 comments on commit 3c2a57a

Please sign in to comment.