Skip to content

Commit

Permalink
compiler-rt: alu: add arithmetic shift right for i8
Browse files Browse the repository at this point in the history
Implement the following polyfill:
- `__llvm_ashr_i8_i8`

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

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

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

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

// As per the LLVM Language Reference Manual:
//
// If op2 is (statically or dynamically) equal to or larger than the number of bits in op1,
// this instruction returns a poison value.
//
// As per `docs/ALU Design.md`, poison values are not supported.
let bit_size: u128 = BitSize::<T>::bits().into();
if shift >= bit_size {
panic!("Requested shift by more bits than input word size")
}

let n_shifted = lshr::<u128>(n, shift);

let sign_bit_mask = shl::<u128>(1, bit_size - 1);
let is_value_negative = (n & sign_bit_mask) != 0;
if is_value_negative {
// Negative values are sign extended.
// Calculate value mask after the initial right shift.
let value_mask = shl::<u128>(1, bit_size - 1 - shift) - 1;
(~value_mask | n_shifted) & Bounded::<T>::MAX.into()
} else {
// Positive values are just logically shifted right.
n_shifted & Bounded::<T>::MAX.into()
}
}
82 changes: 82 additions & 0 deletions compiler-rt/src/alu/ashr/ashr_i8.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::alu::ashr::ashr;

pub fn __llvm_ashr_i8_i8(n: u128, shift: u128) -> u128 {
ashr::<u8>(n, shift)
}

#[cfg(test)]
mod tests {
use super::__llvm_ashr_i8_i8;
use crate::alu::test_case::TestCaseTwoArgs;

#[cairofmt::skip]
pub const test_cases: [TestCaseTwoArgs; 26] = [
// All possible shifts on -1 from 0 throughout the whole input value length.
// Since -1 is negative, sign extension happens and the output is also -1.
TestCaseTwoArgs { lhs: 0b11111111, rhs: 0, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 1, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 2, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 3, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 4, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 5, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 6, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 7, expected: 0b11111111 },

// All possible shifts on 127 from 0 throughout the whole input value length.
// Since 127 is positive, the output is just right shift by rhs.
TestCaseTwoArgs { lhs: 0b01111111, rhs: 0, expected: 0b01111111 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 1, expected: 0b00111111 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 2, expected: 0b00011111 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 3, expected: 0b00001111 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 4, expected: 0b00000111 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 5, expected: 0b00000011 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 6, expected: 0b00000001 },
TestCaseTwoArgs { lhs: 0b01111111, rhs: 7, expected: 0b00000000 },

// The same set of operations but on a zero bit pattern - 0 is positive, so no sign extension happens
TestCaseTwoArgs { lhs: 0b00000000, rhs: 0, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 1, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 2, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 3, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 4, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 5, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 6, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 7, expected: 0b00000000 },

// Shift of a mixed 0/1 bit pattern
TestCaseTwoArgs { lhs: 0b10101010, rhs: 4, expected: 0b11111010 },
TestCaseTwoArgs { lhs: 0b01010101, rhs: 4, expected: 0b00000101 },
];

#[test]
fn test_i8() {
for case in test_cases.span() {
assert_eq!(__llvm_ashr_i8_i8(*case.lhs, *case.rhs), *case.expected);
}
}

// As per the LLVM Language Reference Manual:
//
// If op2 is (statically or dynamically) equal to or larger than the number of bits in op1,
// this instruction returns a poison value.
//
// As per `docs/ALU Design.md`, poison values are not supported.
pub const test_cases_panic: [TestCaseTwoArgs; 8] = [
TestCaseTwoArgs { lhs: 0b11111111, rhs: 8, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 9, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 90, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b11111111, rhs: 123, expected: 0b11111111 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 8, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 9, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 90, expected: 0b00000000 },
TestCaseTwoArgs { lhs: 0b00000000, rhs: 123, expected: 0b00000000 },
];

#[test]
#[should_panic(expected: "Requested shift by more bits than input word size")]
fn test_i8_panic() {
for case in test_cases_panic.span() {
assert_eq!(__llvm_ashr_i8_i8(*case.lhs, *case.rhs), *case.expected);
}
}
}

0 comments on commit abcdfc4

Please sign in to comment.