-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
compiler-rt: alu: add arithmetic shift right for i8
Implement the following polyfill: - `__llvm_ashr_i8_i8` Signed-off-by: Wojciech Zmuda <zmuda.w@gmail.com>
- Loading branch information
Showing
3 changed files
with
141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,5 +27,6 @@ pub mod sdiv; | |
pub mod urem; | ||
pub mod srem; | ||
pub mod abs; | ||
pub mod ashr; | ||
|
||
mod test_case; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |