Skip to content

Commit

Permalink
compiler-rt: alu: add bitwise lshr polyfills
Browse files Browse the repository at this point in the history
Signed-off-by: Wojciech Zmuda <[email protected]>
  • Loading branch information
wzmuda committed Nov 20, 2024
1 parent 578c2e5 commit 654b792
Show file tree
Hide file tree
Showing 8 changed files with 874 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 @@ -2,5 +2,6 @@ pub mod and;
pub mod or;
pub mod xor;
pub mod shl;
pub mod lshr;

mod test_case;
44 changes: 44 additions & 0 deletions compiler-rt/src/alu/lshr.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
pub mod lshr_i1;
pub mod lshr_i8;
pub mod lshr_i16;
pub mod lshr_i32;
pub mod lshr_i64;
pub mod lshr_i128;

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

// Perform logical shift right operation.
//
// This is a generic implementation for every data type. Its specialized versions
// are defined and tested in lshr/lshr_<type>.cairo files.
fn lshr<
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 as u128 arguments can fit in the concrete type.
assert_fits_in_type::<T>(n);
assert_fits_in_type::<T>(shift);

// Cairo does not have << or >> operators so we must implement the shift manually.
let mut result = n;
// Right logical shift by 1 bit is effectively division by 2, so just divide `shift` times
for _ in 0..shift {
result /= 2;
};

// Make sure the result is limited only to the bit width of the concrete type.
result & Bounded::<T>::MAX.into()
}
38 changes: 38 additions & 0 deletions compiler-rt/src/alu/lshr/lshr_i1.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::alu::lshr::lshr;

pub fn __llvm_lshr_i1_i1(n: u128, shift: u128) -> u128 {
if n > 1 {
panic!("n = {:?} does not fit in i1", n)
}

if shift > 1 {
panic!("shift = {:?} does not fit in i1", shift)
}

// There is no dedicated 1-bit type so after making sure that lhs and rhs
// fit in 1 bit, promote them to u8, do the job and return the LSB.
lshr::<u8>(n, shift) & 0x1
}

#[cfg(test)]
mod tests {
use super::__llvm_lshr_i1_i1;
use crate::alu::test_case::TestCase;
#[cairofmt::skip]
pub const test_cases: [TestCase; 4] = [
// All possible 1-bit cases
TestCase { lhs: 0, rhs: 0, expected: 0 },
TestCase { lhs: 0, rhs: 1, expected: 0 },
TestCase { lhs: 1, rhs: 0, expected: 1 },
TestCase { lhs: 1, rhs: 1, expected: 0 },
];

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

396 changes: 396 additions & 0 deletions compiler-rt/src/alu/lshr/lshr_i128.cairo

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions compiler-rt/src/alu/lshr/lshr_i16.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::alu::lshr::lshr;

pub fn __llvm_lshr_i16_i16(n: u128, shift: u128) -> u128 {
lshr::<u16>(n, shift)
}

#[cfg(test)]
mod tests {
use super::__llvm_lshr_i16_i16;
use crate::alu::test_case::TestCase;
#[cairofmt::skip]
pub const test_cases: [TestCase; 41] = [
// All possible shifts on all 1s from 0 throughout the whole input value length
TestCase { lhs: 0b1111111111111111, rhs: 0, expected: 0b1111111111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 1, expected: 0b0111111111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 2, expected: 0b0011111111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 3, expected: 0b0001111111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 4, expected: 0b0000111111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 5, expected: 0b0000011111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 6, expected: 0b0000001111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 7, expected: 0b0000000111111111 },
TestCase { lhs: 0b1111111111111111, rhs: 8, expected: 0b0000000011111111 },
TestCase { lhs: 0b1111111111111111, rhs: 9, expected: 0b0000000001111111 },
TestCase { lhs: 0b1111111111111111, rhs: 10, expected: 0b0000000000111111 },
TestCase { lhs: 0b1111111111111111, rhs: 11, expected: 0b0000000000011111 },
TestCase { lhs: 0b1111111111111111, rhs: 12, expected: 0b0000000000001111 },
TestCase { lhs: 0b1111111111111111, rhs: 13, expected: 0b0000000000000111 },
TestCase { lhs: 0b1111111111111111, rhs: 14, expected: 0b0000000000000011 },
TestCase { lhs: 0b1111111111111111, rhs: 15, expected: 0b0000000000000001 },
TestCase { lhs: 0b1111111111111111, rhs: 16, expected: 0b0000000000000000 },

// Shifts by more bits than the input value contains - overflow should cause loss of the excessive bits
TestCase { lhs: 0b1111111111111111, rhs: 17, expected: 0b0000000000000000 },
TestCase { lhs: 0b1111111111111111, rhs: 90, expected: 0b0000000000000000 },
TestCase { lhs: 0b1111111111111111, rhs: 123, expected: 0b0000000000000000 },

// The same set of operations but on a zero bit pattern
TestCase { lhs: 0b0000000000000000, rhs: 0, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 1, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 2, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 3, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 4, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 5, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 6, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 7, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 8, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 9, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 10, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 11, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 12, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 13, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 14, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 15, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 16, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 17, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 90, expected: 0b0000000000000000 },
TestCase { lhs: 0b0000000000000000, rhs: 123, expected: 0b0000000000000000 },

// Shift of a mixed 0/1 bit pattern
TestCase { lhs: 0b1010101010101010, rhs: 8, expected: 0b10101010 },
];


#[test]
fn test_i16() {
for case in test_cases
.span() {
assert_eq!(__llvm_lshr_i16_i16(*case.lhs, *case.rhs), *case.expected);
}
}
}
103 changes: 103 additions & 0 deletions compiler-rt/src/alu/lshr/lshr_i32.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::alu::lshr::lshr;

pub fn __llvm_lshr_i32_i32(n: u128, shift: u128) -> u128 {
lshr::<u32>(n, shift)
}

#[cfg(test)]
mod tests {
use super::__llvm_lshr_i32_i32;
use crate::alu::test_case::TestCase;
#[cairofmt::skip]
pub const test_cases: [TestCase; 73] = [
// All possible shifts on all 1s from 0 throughout the whole input value length
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 0, expected: 0b11111111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 1, expected: 0b01111111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 2, expected: 0b00111111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 3, expected: 0b00011111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 4, expected: 0b00001111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 5, expected: 0b00000111111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 6, expected: 0b00000011111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 7, expected: 0b00000001111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 8, expected: 0b00000000111111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 9, expected: 0b00000000011111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 10, expected: 0b00000000001111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 11, expected: 0b00000000000111111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 12, expected: 0b00000000000011111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 13, expected: 0b00000000000001111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 14, expected: 0b00000000000000111111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 15, expected: 0b00000000000000011111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 16, expected: 0b00000000000000001111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 17, expected: 0b00000000000000000111111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 18, expected: 0b00000000000000000011111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 19, expected: 0b00000000000000000001111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 20, expected: 0b00000000000000000000111111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 21, expected: 0b00000000000000000000011111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 22, expected: 0b00000000000000000000001111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 23, expected: 0b00000000000000000000000111111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 24, expected: 0b00000000000000000000000011111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 25, expected: 0b00000000000000000000000001111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 26, expected: 0b00000000000000000000000000111111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 27, expected: 0b00000000000000000000000000011111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 28, expected: 0b00000000000000000000000000001111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 29, expected: 0b00000000000000000000000000000111 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 30, expected: 0b00000000000000000000000000000011 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 31, expected: 0b00000000000000000000000000000001 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 32, expected: 0b00000000000000000000000000000000 },

// Shifts by more bits than the input value contains - overflow should cause loss of the excessive bits
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 33, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 90, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b11111111111111111111111111111111, rhs: 123, expected: 0b00000000000000000000000000000000 },

// The same set of operations but on a zero bit pattern
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 0, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 1, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 2, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 3, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 4, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 5, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 6, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 7, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 8, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 9, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 10, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 11, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 12, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 13, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 14, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 15, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 16, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 17, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 18, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 19, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 20, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 21, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 22, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 23, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 24, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 25, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 26, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 27, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 28, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 29, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 30, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 31, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 32, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 33, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 90, expected: 0b00000000000000000000000000000000 },
TestCase { lhs: 0b00000000000000000000000000000000, rhs: 123, expected: 0b00000000000000000000000000000000 },

// Shift of a mixed 0/1 bit pattern
TestCase { lhs: 0b10101010101010101010101010101010, rhs: 16, expected: 0b1010101010101010 },
];

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

Loading

0 comments on commit 654b792

Please sign in to comment.