Skip to content

Commit

Permalink
Added trim action for bounded_int. (#6788)
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi authored Dec 3, 2024
1 parent 4af513e commit c503fd2
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 2 deletions.
55 changes: 54 additions & 1 deletion corelib/src/internal/bounded_int.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,59 @@ extern fn bounded_int_constrain<T, const BOUNDARY: felt252, impl H: ConstrainHel
value: T,
) -> Result<H::LowT, H::HighT> implicits(RangeCheck) nopanic;

/// A helper trait for trimming a `BoundedInt` instance.
pub trait TrimHelper<T, const TRIMMED_VALUE: felt252> {
type Target;
}
mod trim_impl {
pub impl Impl<
T, const TRIMMED_VALUE: felt252, const MIN: felt252, const MAX: felt252,
> of super::TrimHelper<T, TRIMMED_VALUE> {
type Target = super::BoundedInt<MIN, MAX>;
}
}
impl U8TrimBelow = trim_impl::Impl<u8, 0, 1, 0xff>;
impl U8TrimAbove = trim_impl::Impl<u8, 0xff, 0, 0xfe>;
impl I8TrimBelow = trim_impl::Impl<i8, -0x80, -0x7f, 0x7f>;
impl I8TrimAbove = trim_impl::Impl<i8, 0x7f, -0x80, 0x7e>;
impl U16TrimBelow = trim_impl::Impl<u16, 0, 1, 0xffff>;
impl U16TrimAbove = trim_impl::Impl<u16, 0xffff, 0, 0xfffe>;
impl I16TrimBelow = trim_impl::Impl<i16, -0x8000, -0x7fff, 0x7fff>;
impl I16TrimAbove = trim_impl::Impl<i16, 0x7fff, -0x8000, 0x7ffe>;
impl U32TrimBelow = trim_impl::Impl<u32, 0, 1, 0xffffffff>;
impl U32TrimAbove = trim_impl::Impl<u32, 0xffffffff, 0, 0xfffffffe>;
impl I32TrimBelow = trim_impl::Impl<i32, -0x80000000, -0x7fffffff, 0x7fffffff>;
impl I32TrimAbove = trim_impl::Impl<i32, 0x7fffffff, -0x80000000, 0x7ffffffe>;
impl U64TrimBelow = trim_impl::Impl<u64, 0, 1, 0xffffffffffffffff>;
impl U64TrimAbove = trim_impl::Impl<u64, 0xffffffffffffffff, 0, 0xfffffffffffffffe>;
impl I64TrimBelow =
trim_impl::Impl<i64, -0x8000000000000000, -0x7fffffffffffffff, 0x7fffffffffffffff>;
impl I64TrimAbove =
trim_impl::Impl<i64, 0x7fffffffffffffff, -0x8000000000000000, 0x7ffffffffffffffe>;
impl U128TrimBelow = trim_impl::Impl<u128, 0, 1, 0xffffffffffffffffffffffffffffffff>;
impl U128TrimAbove =
trim_impl::Impl<
u128, 0xffffffffffffffffffffffffffffffff, 0, 0xfffffffffffffffffffffffffffffffe,
>;
impl I128TrimBelow =
trim_impl::Impl<
i128,
-0x80000000000000000000000000000000,
-0x7fffffffffffffffffffffffffffffff,
0x7fffffffffffffffffffffffffffffff,
>;
impl I128TrimAbove =
trim_impl::Impl<
i128,
0x7fffffffffffffffffffffffffffffff,
-0x80000000000000000000000000000000,
0x7ffffffffffffffffffffffffffffffe,
>;

extern fn bounded_int_trim<T, const TRIMMED_VALUE: felt252, impl H: TrimHelper<T, TRIMMED_VALUE>>(
value: T,
) -> core::internal::OptionRev<H::Target> nopanic;

extern fn bounded_int_is_zero<T>(value: T) -> crate::zeroable::IsZeroResult<T> implicits() nopanic;

/// Returns the negation of the given `felt252` value.
Expand Down Expand Up @@ -219,5 +272,5 @@ impl MulMinusOneNegateHelper<T, impl H: MulHelper<T, MinusOne>> of NegateHelper<
pub use {
bounded_int_add as add, bounded_int_sub as sub, bounded_int_mul as mul,
bounded_int_div_rem as div_rem, bounded_int_constrain as constrain,
bounded_int_is_zero as is_zero,
bounded_int_is_zero as is_zero, bounded_int_trim as trim,
};
81 changes: 81 additions & 0 deletions corelib/src/test/integer_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2155,4 +2155,85 @@ mod bounded_int {
assert!(test_constrain_helper::<u129, U128_UPPER>(upcast(bi_const::<U128_UPPER>())));
assert!(test_constrain_helper::<u129, U128_UPPER>(upcast(bi_const::<U129_MAX>())));
}

#[test]
fn test_trim() {
use core::internal::OptionRev;
assert!(bounded_int::trim::<u8, 0>(0) == OptionRev::None);
assert!(bounded_int::trim::<u8, 0>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<u8, 0xff>(0xff) == OptionRev::None);
assert!(bounded_int::trim::<u8, 0xff>(0xfe) == OptionRev::Some(0xfe));
assert!(bounded_int::trim::<i8, -0x80>(-0x80) == OptionRev::None);
assert!(bounded_int::trim::<i8, -0x80>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<i8, 0x7f>(0x7f) == OptionRev::None);
assert!(bounded_int::trim::<i8, 0x7f>(1) == OptionRev::Some(1));

assert!(bounded_int::trim::<u16, 0>(0) == OptionRev::None);
assert!(bounded_int::trim::<u16, 0>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<u16, 0xffff>(0xffff) == OptionRev::None);
assert!(bounded_int::trim::<u16, 0xffff>(0xfffe) == OptionRev::Some(0xfffe));
assert!(bounded_int::trim::<i16, -0x8000>(-0x8000) == OptionRev::None);
assert!(bounded_int::trim::<i16, -0x8000>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<i16, 0x7fff>(0x7fff) == OptionRev::None);
assert!(bounded_int::trim::<i16, 0x7fff>(1) == OptionRev::Some(1));

assert!(bounded_int::trim::<u32, 0>(0) == OptionRev::None);
assert!(bounded_int::trim::<u32, 0>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<u32, 0xffffffff>(0xffffffff) == OptionRev::None);
assert!(bounded_int::trim::<u32, 0xffffffff>(0xfffffffe) == OptionRev::Some(0xfffffffe));
assert!(bounded_int::trim::<i32, -0x80000000>(-0x80000000) == OptionRev::None);
assert!(bounded_int::trim::<i32, -0x80000000>(1) == OptionRev::Some(1));
assert!(bounded_int::trim::<i32, 0x7fffffff>(0x7fffffff) == OptionRev::None);
assert!(bounded_int::trim::<i32, 0x7fffffff>(1) == OptionRev::Some(1));

assert!(bounded_int::trim::<u64, 0>(0) == OptionRev::None);
assert!(bounded_int::trim::<u64, 0>(1) == OptionRev::Some(1));
assert!(
bounded_int::trim::<u64, 0xffffffffffffffff>(0xffffffffffffffff) == OptionRev::None,
);
assert!(
bounded_int::trim::<
u64, 0xffffffffffffffff,
>(0xfffffffffffffffe) == OptionRev::Some(0xfffffffffffffffe),
);
assert!(
bounded_int::trim::<i64, -0x8000000000000000>(-0x8000000000000000) == OptionRev::None,
);
assert!(bounded_int::trim::<i64, -0x8000000000000000>(1) == OptionRev::Some(1));
assert!(
bounded_int::trim::<i64, 0x7fffffffffffffff>(0x7fffffffffffffff) == OptionRev::None,
);
assert!(bounded_int::trim::<i64, 0x7fffffffffffffff>(1) == OptionRev::Some(1));

assert!(bounded_int::trim::<u128, 0>(0) == OptionRev::None);
assert!(bounded_int::trim::<u128, 0>(1) == OptionRev::Some(1));
assert!(
bounded_int::trim::<
u128, 0xffffffffffffffffffffffffffffffff,
>(0xffffffffffffffffffffffffffffffff) == OptionRev::None,
);
assert!(
bounded_int::trim::<
u128, 0xffffffffffffffffffffffffffffffff,
>(
0xfffffffffffffffffffffffffffffffe,
) == OptionRev::Some(0xfffffffffffffffffffffffffffffffe),
);
assert!(
bounded_int::trim::<
i128, -0x80000000000000000000000000000000,
>(-0x80000000000000000000000000000000) == OptionRev::None,
);
assert!(
bounded_int::trim::<i128, -0x80000000000000000000000000000000>(1) == OptionRev::Some(1),
);
assert!(
bounded_int::trim::<
i128, 0x7fffffffffffffffffffffffffffffff,
>(0x7fffffffffffffffffffffffffffffff) == OptionRev::None,
);
assert!(
bounded_int::trim::<i128, 0x7fffffffffffffffffffffffffffffff>(1) == OptionRev::Some(1),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
ApChange::Known(1 + if libfunc.boundary.is_zero() { 0 } else { 1 }),
]
}
BoundedIntConcreteLibfunc::Trim(libfunc) => {
let ap_change = if libfunc.trimmed_value.is_zero() { 0 } else { 1 };
vec![ApChange::Known(ap_change), ApChange::Known(ap_change)]
}
BoundedIntConcreteLibfunc::IsZero(_) => vec![ApChange::Known(0), ApChange::Known(0)],
BoundedIntConcreteLibfunc::WrapNonZero(_) => {
vec![ApChange::Known(0)]
Expand Down
5 changes: 5 additions & 0 deletions crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ pub fn core_libfunc_cost(
.into(),
]
}
BoundedIntConcreteLibfunc::Trim(libfunc) => {
let steps: BranchCost =
ConstCost::steps(if libfunc.trimmed_value.is_zero() { 1 } else { 2 }).into();
vec![steps.clone(), steps]
}
BoundedIntConcreteLibfunc::IsZero(_) => {
vec![ConstCost::steps(1).into(), ConstCost::steps(1).into()]
}
Expand Down
22 changes: 22 additions & 0 deletions crates/cairo-lang-sierra-to-casm/src/invocations/int/bounded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn build(
BoundedIntConcreteLibfunc::Constrain(libfunc) => {
build_constrain(builder, &libfunc.boundary)
}
BoundedIntConcreteLibfunc::Trim(libfunc) => build_trim(builder, &libfunc.trimmed_value),
BoundedIntConcreteLibfunc::IsZero(_) => build_is_zero(builder),
BoundedIntConcreteLibfunc::WrapNonZero(_) => build_identity(builder),
}
Expand Down Expand Up @@ -221,3 +222,24 @@ fn build_constrain(
},
))
}

/// Build trim on bounded ints.
fn build_trim(
builder: CompiledInvocationBuilder<'_>,
trimmed_value: &BigInt,
) -> Result<CompiledInvocation, InvocationError> {
let [value] = builder.try_get_single_cells()?;
let mut casm_builder = CasmBuilder::default();
add_input_variables!(casm_builder, deref value; );
casm_build_extend! {casm_builder,
const trimmed_value = trimmed_value.clone();
maybe_tempvar diff = value - trimmed_value;
jump Target if diff != 0;
};
let target_statement_id = get_non_fallthrough_statement_id(&builder);
Ok(builder.build_from_casm_builder(
casm_builder,
[("Fallthrough", &[], None), ("Target", &[&[value]], Some(target_statement_id))],
Default::default(),
))
}
73 changes: 72 additions & 1 deletion crates/cairo-lang-sierra/src/extensions/modules/bounded_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::ops::Shl;
use cairo_lang_utils::require;
use itertools::Itertools;
use num_bigint::{BigInt, ToBigInt};
use num_traits::{One, Signed};
use num_traits::{One, Signed, Zero};
use starknet_types_core::felt::Felt as Felt252;

use super::non_zero::{NonZeroType, nonzero_ty};
Expand Down Expand Up @@ -79,6 +79,7 @@ define_libfunc_hierarchy! {
Mul(BoundedIntMulLibfunc),
DivRem(BoundedIntDivRemLibfunc),
Constrain(BoundedIntConstrainLibfunc),
Trim(BoundedIntTrimLibfunc),
IsZero(BoundedIntIsZeroLibfunc),
WrapNonZero(BoundedIntWrapNonZeroLibfunc),
}, BoundedIntConcreteLibfunc
Expand Down Expand Up @@ -384,6 +385,76 @@ impl SignatureBasedConcreteLibfunc for BoundedIntConstrainConcreteLibfunc {
}
}

/// Libfunc for trimming a BoundedInt<Min, Max> by removing `Min` or `Max` from the range.
/// The libfunc is also applicable for standard types such as u* and i*.
#[derive(Default)]
pub struct BoundedIntTrimLibfunc {}
impl NamedLibfunc for BoundedIntTrimLibfunc {
type Concrete = BoundedIntTrimConcreteLibfunc;

const STR_ID: &'static str = "bounded_int_trim";

fn specialize_signature(
&self,
context: &dyn SignatureSpecializationContext,
args: &[GenericArg],
) -> Result<LibfuncSignature, SpecializationError> {
let (ty, trimmed_value) = match args {
[GenericArg::Type(ty), GenericArg::Value(trimmed_value)] => Ok((ty, trimmed_value)),
[_, _] => Err(SpecializationError::UnsupportedGenericArg),
_ => Err(SpecializationError::WrongNumberOfGenericArgs),
}?;
let ty_info = context.get_type_info(ty.clone())?;
let mut range = Range::from_type_info(&ty_info)?;
if trimmed_value == &range.lower {
range.lower += 1;
} else {
range.upper -= 1;
require(&range.upper == trimmed_value)
.ok_or(SpecializationError::UnsupportedGenericArg)?;
}
let ap_change = SierraApChange::Known { new_vars_only: trimmed_value.is_zero() };
Ok(LibfuncSignature {
param_signatures: vec![ParamSignature::new(ty.clone())],
branch_signatures: vec![
BranchSignature { vars: vec![], ap_change: ap_change.clone() },
BranchSignature {
vars: vec![OutputVarInfo {
ty: bounded_int_ty(context, range.lower, range.upper - 1)?,
ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 0 },
}],
ap_change,
},
],
fallthrough: Some(0),
})
}

fn specialize(
&self,
context: &dyn SpecializationContext,
args: &[GenericArg],
) -> Result<Self::Concrete, SpecializationError> {
let trimmed_value = match args {
[GenericArg::Type(_), GenericArg::Value(trimmed_value)] => Ok(trimmed_value.clone()),
[_, _] => Err(SpecializationError::UnsupportedGenericArg),
_ => Err(SpecializationError::WrongNumberOfGenericArgs),
}?;
let context = context.upcast();
Ok(Self::Concrete { trimmed_value, signature: self.specialize_signature(context, args)? })
}
}

pub struct BoundedIntTrimConcreteLibfunc {
pub trimmed_value: BigInt,
signature: LibfuncSignature,
}
impl SignatureBasedConcreteLibfunc for BoundedIntTrimConcreteLibfunc {
fn signature(&self) -> &LibfuncSignature {
&self.signature
}
}

/// Helper function for specializing the signature of a simple bounded int operation libfunc.
fn specialize_helper(
context: &dyn SignatureSpecializationContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"bounded_int_is_zero",
"bounded_int_mul",
"bounded_int_sub",
"bounded_int_trim",
"bounded_int_wrap_non_zero",
"box_forward_snapshot",
"branch_align",
Expand Down
Loading

0 comments on commit c503fd2

Please sign in to comment.