From 57f5726d6499498defb8506ec6dbd75851dcb4b7 Mon Sep 17 00:00:00 2001 From: Wojciech Zmuda Date: Fri, 24 Jan 2025 01:22:56 +0100 Subject: [PATCH] compiler-rt: alu: add absolute for i8 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 --- compiler-rt/src/alu.cairo | 1 + compiler-rt/src/alu/abs.cairo | 41 ++++++ compiler-rt/src/alu/abs/abs_i8.cairo | 188 +++++++++++++++++++++++++++ compiler-rt/src/alu/sdiv.cairo | 24 +--- compiler-rt/src/utils.cairo | 8 +- 5 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 compiler-rt/src/alu/abs.cairo create mode 100644 compiler-rt/src/alu/abs/abs_i8.cairo diff --git a/compiler-rt/src/alu.cairo b/compiler-rt/src/alu.cairo index d59143a8..8b9a7771 100644 --- a/compiler-rt/src/alu.cairo +++ b/compiler-rt/src/alu.cairo @@ -26,5 +26,6 @@ pub mod udiv; pub mod sdiv; pub mod urem; pub mod srem; +pub mod abs; mod test_case; diff --git a/compiler-rt/src/alu/abs.cairo b/compiler-rt/src/alu/abs.cairo new file mode 100644 index 00000000..f02a5af7 --- /dev/null +++ b/compiler-rt/src/alu/abs.cairo @@ -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_.cairo files. +pub fn abs< + T, + // The trait bounds are chosen so that: + // + // - BitSize: we can determine the length of the data type in bits, + // - Bounded: we can determine min and max value of the type, + // - TryInto, Into - we can convert the type from/to u128, + // - Destruct: 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, + impl TBounded: Bounded, + impl TTryInto: TryInto, + impl TInto: Into, + impl TDestruct: Destruct, +>( + arg: u128, +) -> u128 { + // Make sure the value passed in the u128 arguments can fit in the concrete type. + assert_fits_in_type::(arg); + + // Check if operands and result are negative + let sign_mask = shl::(1, BitSize::::bits().into() - 1); + let is_value_negative = (arg & sign_mask) != 0; + + if is_value_negative { + negate_twos_complement(arg) & Bounded::::MAX.into() + } else { + arg + } +} diff --git a/compiler-rt/src/alu/abs/abs_i8.cairo b/compiler-rt/src/alu/abs/abs_i8.cairo new file mode 100644 index 00000000..251eac0c --- /dev/null +++ b/compiler-rt/src/alu/abs/abs_i8.cairo @@ -0,0 +1,188 @@ +use crate::alu::abs::abs; + +pub fn __llvm_abs_i8_i8(arg: u128, _is_int_min_poison: u128) -> u128 { + abs::(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); + } + } +} diff --git a/compiler-rt/src/alu/sdiv.cairo b/compiler-rt/src/alu/sdiv.cairo index e69bba56..3cf28c9f 100644 --- a/compiler-rt/src/alu/sdiv.cairo +++ b/compiler-rt/src/alu/sdiv.cairo @@ -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. @@ -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::::MAX.into() - } else { - lhs - }; - let abs_divisor = if is_divisor_negative { - twos_complement(rhs) & Bounded::::MAX.into() - } else { - rhs - }; + let abs_dividend = abs::(lhs); + let abs_divisor = abs::(rhs); // Perform unsigned division and get quotient and remainder. // Adjust quotient for floor division if result is negative and there's a remainder. @@ -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 }; diff --git a/compiler-rt/src/utils.cairo b/compiler-rt/src/utils.cairo index 74f1e990..27906749 100644 --- a/compiler-rt/src/utils.cairo +++ b/compiler-rt/src/utils.cairo @@ -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. @@ -43,3 +43,9 @@ pub fn assert_fits_in_type< ) { expect_into::(v); } + +// Negates the given value using two's complement representation. +#[inline] +pub fn negate_twos_complement(value: u128) -> u128 { + (~value).wrapping_add(1) +}