Skip to content

Commit

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

Since the absolute value was already calculated in a few places, use the
new polyfill there. Also refactor two's complement calculation to be an
utility function since it's now used in abs and sdiv.

Signed-off-by: Wojciech Zmuda <[email protected]>
  • Loading branch information
wzmuda committed Jan 24, 2025
1 parent f80196d commit 57f5726
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 19 deletions.
1 change: 1 addition & 0 deletions compiler-rt/src/alu.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ pub mod udiv;
pub mod sdiv;
pub mod urem;
pub mod srem;
pub mod abs;

mod test_case;
41 changes: 41 additions & 0 deletions compiler-rt/src/alu/abs.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
pub mod abs_i8;

use crate::utils::{assert_fits_in_type, negate_twos_complement};
use crate::alu::shl::shl;
use core::num::traits::{BitSize, Bounded};

// Perform the `abs` operation.
//
// This is a generic implementation for every data type. Its specialized versions
// are defined and tested in the abs/abs_<type>.cairo files.
pub fn abs<
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>,
>(
arg: u128,
) -> u128 {
// Make sure the value passed in the u128 arguments can fit in the concrete type.
assert_fits_in_type::<T>(arg);

// Check if operands and result are negative
let sign_mask = shl::<u128>(1, BitSize::<T>::bits().into() - 1);
let is_value_negative = (arg & sign_mask) != 0;

if is_value_negative {
negate_twos_complement(arg) & Bounded::<T>::MAX.into()
} else {
arg
}
}
188 changes: 188 additions & 0 deletions compiler-rt/src/alu/abs/abs_i8.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use crate::alu::abs::abs;

pub fn __llvm_abs_i8_i8(arg: u128, _is_int_min_poison: u128) -> u128 {
abs::<u8>(arg)
}

#[cfg(test)]
mod tests {
use super::__llvm_abs_i8_i8;
use crate::alu::test_case::TestCaseOneArg;
#[cairofmt::skip]
pub const test_cases: [TestCaseOneArg; 160] = [
// Due to Cairo's casting limitation, negative numbers are represented as bit patterns.

// Random test cases
TestCaseOneArg{arg: 101, expected: 101},
TestCaseOneArg{arg: 103, expected: 103},
TestCaseOneArg{arg: 105, expected: 105},
TestCaseOneArg{arg: 109, expected: 109},
TestCaseOneArg{arg: 11, expected: 11},
TestCaseOneArg{arg: 111, expected: 111},
TestCaseOneArg{arg: 113, expected: 113},
TestCaseOneArg{arg: 115, expected: 115},
TestCaseOneArg{arg: 117, expected: 117},
TestCaseOneArg{arg: 118, expected: 118},
TestCaseOneArg{arg: 120, expected: 120},
TestCaseOneArg{arg: 121, expected: 121},
TestCaseOneArg{arg: 123, expected: 123},
TestCaseOneArg{arg: 124, expected: 124},
TestCaseOneArg{arg: 13, expected: 13},
TestCaseOneArg{arg: 14, expected: 14},
TestCaseOneArg{arg: 16, expected: 16},
TestCaseOneArg{arg: 19, expected: 19},
TestCaseOneArg{arg: 2, expected: 2},
TestCaseOneArg{arg: 22, expected: 22},
TestCaseOneArg{arg: 26, expected: 26},
TestCaseOneArg{arg: 27, expected: 27},
TestCaseOneArg{arg: 28, expected: 28},
TestCaseOneArg{arg: 29, expected: 29},
TestCaseOneArg{arg: 3, expected: 3},
TestCaseOneArg{arg: 30, expected: 30},
TestCaseOneArg{arg: 31, expected: 31},
TestCaseOneArg{arg: 32, expected: 32},
TestCaseOneArg{arg: 34, expected: 34},
TestCaseOneArg{arg: 35, expected: 35},
TestCaseOneArg{arg: 37, expected: 37},
TestCaseOneArg{arg: 38, expected: 38},
TestCaseOneArg{arg: 39, expected: 39},
TestCaseOneArg{arg: 4, expected: 4},
TestCaseOneArg{arg: 41, expected: 41},
TestCaseOneArg{arg: 44, expected: 44},
TestCaseOneArg{arg: 45, expected: 45},
TestCaseOneArg{arg: 46, expected: 46},
TestCaseOneArg{arg: 47, expected: 47},
TestCaseOneArg{arg: 48, expected: 48},
TestCaseOneArg{arg: 49, expected: 49},
TestCaseOneArg{arg: 51, expected: 51},
TestCaseOneArg{arg: 55, expected: 55},
TestCaseOneArg{arg: 56, expected: 56},
TestCaseOneArg{arg: 57, expected: 57},
TestCaseOneArg{arg: 6, expected: 6},
TestCaseOneArg{arg: 61, expected: 61},
TestCaseOneArg{arg: 63, expected: 63},
TestCaseOneArg{arg: 64, expected: 64},
TestCaseOneArg{arg: 66, expected: 66},
TestCaseOneArg{arg: 68, expected: 68},
TestCaseOneArg{arg: 69, expected: 69},
TestCaseOneArg{arg: 70, expected: 70},
TestCaseOneArg{arg: 71, expected: 71},
TestCaseOneArg{arg: 73, expected: 73},
TestCaseOneArg{arg: 75, expected: 75},
TestCaseOneArg{arg: 76, expected: 76},
TestCaseOneArg{arg: 77, expected: 77},
TestCaseOneArg{arg: 78, expected: 78},
TestCaseOneArg{arg: 8, expected: 8},
TestCaseOneArg{arg: 80, expected: 80},
TestCaseOneArg{arg: 81, expected: 81},
TestCaseOneArg{arg: 82, expected: 82},
TestCaseOneArg{arg: 85, expected: 85},
TestCaseOneArg{arg: 87, expected: 87},
TestCaseOneArg{arg: 89, expected: 89},
TestCaseOneArg{arg: 9, expected: 9},
TestCaseOneArg{arg: 90, expected: 90},
TestCaseOneArg{arg: 91, expected: 91},
TestCaseOneArg{arg: 92, expected: 92},
TestCaseOneArg{arg: 94, expected: 94},
TestCaseOneArg{arg: 95, expected: 95},
TestCaseOneArg{arg: 97, expected: 97},
TestCaseOneArg{arg: 98, expected: 98},
TestCaseOneArg{arg: 0b10000000, expected: 128}, // arg = -128
TestCaseOneArg{arg: 0b10000001, expected: 127}, // arg = -127
TestCaseOneArg{arg: 0b10000010, expected: 126}, // arg = -126
TestCaseOneArg{arg: 0b10000011, expected: 125}, // arg = -125
TestCaseOneArg{arg: 0b10000100, expected: 124}, // arg = -124
TestCaseOneArg{arg: 0b10000110, expected: 122}, // arg = -122
TestCaseOneArg{arg: 0b10001000, expected: 120}, // arg = -120
TestCaseOneArg{arg: 0b10001100, expected: 116}, // arg = -116
TestCaseOneArg{arg: 0b10001101, expected: 115}, // arg = -115
TestCaseOneArg{arg: 0b10001110, expected: 114}, // arg = -114
TestCaseOneArg{arg: 0b10010000, expected: 112}, // arg = -112
TestCaseOneArg{arg: 0b10010010, expected: 110}, // arg = -110
TestCaseOneArg{arg: 0b10010100, expected: 108}, // arg = -108
TestCaseOneArg{arg: 0b10010111, expected: 105}, // arg = -105
TestCaseOneArg{arg: 0b10011000, expected: 104}, // arg = -104
TestCaseOneArg{arg: 0b10011010, expected: 102}, // arg = -102
TestCaseOneArg{arg: 0b10011011, expected: 101}, // arg = -101
TestCaseOneArg{arg: 0b10011100, expected: 100}, // arg = -100
TestCaseOneArg{arg: 0b10011101, expected: 99}, // arg = -99
TestCaseOneArg{arg: 0b10011110, expected: 98}, // arg = -98
TestCaseOneArg{arg: 0b10100100, expected: 92}, // arg = -92
TestCaseOneArg{arg: 0b10100101, expected: 91}, // arg = -91
TestCaseOneArg{arg: 0b10100110, expected: 90}, // arg = -90
TestCaseOneArg{arg: 0b10100111, expected: 89}, // arg = -89
TestCaseOneArg{arg: 0b10101000, expected: 88}, // arg = -88
TestCaseOneArg{arg: 0b10101001, expected: 87}, // arg = -87
TestCaseOneArg{arg: 0b10101010, expected: 86}, // arg = -86
TestCaseOneArg{arg: 0b10101011, expected: 85}, // arg = -85
TestCaseOneArg{arg: 0b10101100, expected: 84}, // arg = -84
TestCaseOneArg{arg: 0b10101101, expected: 83}, // arg = -83
TestCaseOneArg{arg: 0b10101110, expected: 82}, // arg = -82
TestCaseOneArg{arg: 0b10101111, expected: 81}, // arg = -81
TestCaseOneArg{arg: 0b10110000, expected: 80}, // arg = -80
TestCaseOneArg{arg: 0b10110001, expected: 79}, // arg = -79
TestCaseOneArg{arg: 0b10110011, expected: 77}, // arg = -77
TestCaseOneArg{arg: 0b10110100, expected: 76}, // arg = -76
TestCaseOneArg{arg: 0b10110101, expected: 75}, // arg = -75
TestCaseOneArg{arg: 0b10110110, expected: 74}, // arg = -74
TestCaseOneArg{arg: 0b10110111, expected: 73}, // arg = -73
TestCaseOneArg{arg: 0b10111000, expected: 72}, // arg = -72
TestCaseOneArg{arg: 0b10111001, expected: 71}, // arg = -71
TestCaseOneArg{arg: 0b10111100, expected: 68}, // arg = -68
TestCaseOneArg{arg: 0b10111101, expected: 67}, // arg = -67
TestCaseOneArg{arg: 0b10111110, expected: 66}, // arg = -66
TestCaseOneArg{arg: 0b10111111, expected: 65}, // arg = -65
TestCaseOneArg{arg: 0b11000000, expected: 64}, // arg = -64
TestCaseOneArg{arg: 0b11000010, expected: 62}, // arg = -62
TestCaseOneArg{arg: 0b11000011, expected: 61}, // arg = -61
TestCaseOneArg{arg: 0b11000100, expected: 60}, // arg = -60
TestCaseOneArg{arg: 0b11000101, expected: 59}, // arg = -59
TestCaseOneArg{arg: 0b11000111, expected: 57}, // arg = -57
TestCaseOneArg{arg: 0b11001000, expected: 56}, // arg = -56
TestCaseOneArg{arg: 0b11001011, expected: 53}, // arg = -53
TestCaseOneArg{arg: 0b11001101, expected: 51}, // arg = -51
TestCaseOneArg{arg: 0b11001110, expected: 50}, // arg = -50
TestCaseOneArg{arg: 0b11010000, expected: 48}, // arg = -48
TestCaseOneArg{arg: 0b11010110, expected: 42}, // arg = -42
TestCaseOneArg{arg: 0b11010111, expected: 41}, // arg = -41
TestCaseOneArg{arg: 0b11011000, expected: 40}, // arg = -40
TestCaseOneArg{arg: 0b11011001, expected: 39}, // arg = -39
TestCaseOneArg{arg: 0b11011010, expected: 38}, // arg = -38
TestCaseOneArg{arg: 0b11011100, expected: 36}, // arg = -36
TestCaseOneArg{arg: 0b11011101, expected: 35}, // arg = -35
TestCaseOneArg{arg: 0b11011110, expected: 34}, // arg = -34
TestCaseOneArg{arg: 0b11011111, expected: 33}, // arg = -33
TestCaseOneArg{arg: 0b11100000, expected: 32}, // arg = -32
TestCaseOneArg{arg: 0b11100011, expected: 29}, // arg = -29
TestCaseOneArg{arg: 0b11100100, expected: 28}, // arg = -28
TestCaseOneArg{arg: 0b11100101, expected: 27}, // arg = -27
TestCaseOneArg{arg: 0b11100111, expected: 25}, // arg = -25
TestCaseOneArg{arg: 0b11101000, expected: 24}, // arg = -24
TestCaseOneArg{arg: 0b11101011, expected: 21}, // arg = -21
TestCaseOneArg{arg: 0b11101100, expected: 20}, // arg = -20
TestCaseOneArg{arg: 0b11101101, expected: 19}, // arg = -19
TestCaseOneArg{arg: 0b11110000, expected: 16}, // arg = -16
TestCaseOneArg{arg: 0b11110001, expected: 15}, // arg = -15
TestCaseOneArg{arg: 0b11110010, expected: 14}, // arg = -14
TestCaseOneArg{arg: 0b11110101, expected: 11}, // arg = -11
TestCaseOneArg{arg: 0b11110110, expected: 10}, // arg = -10
TestCaseOneArg{arg: 0b11111001, expected: 7}, // arg = -7
TestCaseOneArg{arg: 0b11111010, expected: 6}, // arg = -6
TestCaseOneArg{arg: 0b11111011, expected: 5}, // arg = -5
TestCaseOneArg{arg: 0b11111110, expected: 2}, // arg = -2

// Edge cases
TestCaseOneArg{arg: 0, expected: 0},
TestCaseOneArg{arg: 127, expected: 127},
TestCaseOneArg{arg: 0b10000000, expected: 128}, // arg = -128
];

#[test]
fn test_i8() {
// As per `docs/ALU Design.md`, poison values are not supported.
let unused = 0;
for case in test_cases.span() {
assert_eq!(__llvm_abs_i8_i8(*case.arg, unused), *case.expected);
}
}
}
24 changes: 6 additions & 18 deletions compiler-rt/src/alu/sdiv.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod sdiv_i8;

use crate::utils::assert_fits_in_type;
use crate::utils::{assert_fits_in_type, negate_twos_complement};
use crate::alu::shl::shl;
use crate::alu::abs::abs;
use core::num::traits::{BitSize, Bounded, WrappingAdd};

// Perform signed division of integers. Return quotient and remainder.
Expand Down Expand Up @@ -36,22 +37,9 @@ pub fn divide_with_remainder_signed<
let is_divisor_negative = (rhs & sign_mask) != 0;
let is_quotient_negative = is_dividend_negative ^ is_divisor_negative;

// A helper function to compute two's complement
let twos_complement = |x: u128| -> u128 {
(~x).wrapping_add(1)
};

// Get absolute value of operands
let abs_dividend = if is_dividend_negative {
twos_complement(lhs) & Bounded::<T>::MAX.into()
} else {
lhs
};
let abs_divisor = if is_divisor_negative {
twos_complement(rhs) & Bounded::<T>::MAX.into()
} else {
rhs
};
let abs_dividend = abs::<T>(lhs);
let abs_divisor = abs::<T>(rhs);

// Perform unsigned division and get quotient and remainder.
// Adjust quotient for floor division if result is negative and there's a remainder.
Expand All @@ -65,12 +53,12 @@ pub fn divide_with_remainder_signed<

// Apply sign to the quotient and the remainder
let quotient = if is_quotient_negative {
twos_complement(quotient_unsigned)
negate_twos_complement(quotient_unsigned)
} else {
quotient_unsigned
};
let remainder = if is_divisor_negative && remainder != 0 {
twos_complement(remainder)
negate_twos_complement(remainder)
} else {
remainder
};
Expand Down
8 changes: 7 additions & 1 deletion compiler-rt/src/utils.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::num::traits::BitSize;
use core::num::traits::{BitSize, WrappingAdd};

// Indicated the direction of overflow in polyfills implementing arithmetic operations that can
// overflow.
Expand Down Expand Up @@ -43,3 +43,9 @@ pub fn assert_fits_in_type<
) {
expect_into::<T>(v);
}

// Negates the given value using two's complement representation.
#[inline]
pub fn negate_twos_complement(value: u128) -> u128 {
(~value).wrapping_add(1)
}

0 comments on commit 57f5726

Please sign in to comment.