From bcfa44ce01b9ed2f96faa3649c871061596ba698 Mon Sep 17 00:00:00 2001 From: Ori Ziv Date: Sun, 25 Jun 2023 15:15:04 +0300 Subject: [PATCH] Added cast for signed integers. commit-id:c56d4b74 --- corelib/src/integer.cairo | 155 ++++- corelib/src/test/integer_test.cairo | 212 +++++- .../src/core_libfunc_ap_change.rs | 23 +- .../src/core_libfunc_cost_base.rs | 36 +- .../src/invocations/casts.rs | 212 +++++- .../src/extensions/modules/casts.rs | 113 +++- tests/e2e_test_data/libfuncs/casts | 632 ++++++++++++++++++ 7 files changed, 1275 insertions(+), 108 deletions(-) diff --git a/corelib/src/integer.cairo b/corelib/src/integer.cairo index 60dab9d727b..7187345bd7a 100644 --- a/corelib/src/integer.cairo +++ b/corelib/src/integer.cairo @@ -1586,32 +1586,133 @@ extern fn downcast(x: FromType) -> Option implicits(Ra // Marks `FromType` as upcastable to `ToType`. // Do not add user code implementing this trait. trait Upcastable; -impl UpcastableU8U16 of Upcastable {} -impl UpcastableU8U32 of Upcastable {} -impl UpcastableU8U64 of Upcastable {} -impl UpcastableU8U128 of Upcastable {} -impl UpcastableU16U32 of Upcastable {} -impl UpcastableU16U64 of Upcastable {} -impl UpcastableU16U128 of Upcastable {} -impl UpcastableU32U64 of Upcastable {} -impl UpcastableU32U128 of Upcastable {} -impl UpcastableU64U128 of Upcastable {} +impl UpcastableU8U16 of Upcastable; +impl UpcastableU8I16 of Upcastable; +impl UpcastableU8U32 of Upcastable; +impl UpcastableU8I32 of Upcastable; +impl UpcastableU8U64 of Upcastable; +impl UpcastableU8I64 of Upcastable; +impl UpcastableU8U128 of Upcastable; +impl UpcastableU8I128 of Upcastable; +impl UpcastableI8I16 of Upcastable; +impl UpcastableI8I32 of Upcastable; +impl UpcastableI8I64 of Upcastable; +impl UpcastableI8I128 of Upcastable; +impl UpcastableU16U32 of Upcastable; +impl UpcastableU16I32 of Upcastable; +impl UpcastableU16U64 of Upcastable; +impl UpcastableU16I64 of Upcastable; +impl UpcastableU16U128 of Upcastable; +impl UpcastableU16I128 of Upcastable; +impl UpcastableI16I32 of Upcastable; +impl UpcastableI16I64 of Upcastable; +impl UpcastableI16I128 of Upcastable; +impl UpcastableU32U64 of Upcastable; +impl UpcastableU32I64 of Upcastable; +impl UpcastableU32U128 of Upcastable; +impl UpcastableU32I128 of Upcastable; +impl UpcastableI32I64 of Upcastable; +impl UpcastableI32I128 of Upcastable; +impl UpcastableU64U128 of Upcastable; +impl UpcastableU64I128 of Upcastable; +impl UpcastableI64I128 of Upcastable; // Marks `FromType` as downcastable to `ToType`. // Do not add user code implementing this trait. trait Downcastable; -impl DowncastableU128U64 of Downcastable {} -impl DowncastableU128U32 of Downcastable {} -impl DowncastableU128U16 of Downcastable {} -impl DowncastableU128U8 of Downcastable {} - -impl DowncastableU64U32 of Downcastable {} -impl DowncastableU64U16 of Downcastable {} -impl DowncastableU64U8 of Downcastable {} - -impl DowncastableU32U16 of Downcastable {} -impl DowncastableU32U8 of Downcastable {} - -impl DowncastableU16U8 of Downcastable {} +impl DowncastableU8I8 of Downcastable; +impl DowncastableU8I16 of Downcastable; +impl DowncastableU8U16 of Downcastable; +impl DowncastableU8I32 of Downcastable; +impl DowncastableU8U32 of Downcastable; +impl DowncastableU8I64 of Downcastable; +impl DowncastableU8U64 of Downcastable; +impl DowncastableU8I128 of Downcastable; +impl DowncastableU8U128 of Downcastable; +impl DowncastableI8U8 of Downcastable; +impl DowncastableI8I16 of Downcastable; +impl DowncastableI8U16 of Downcastable; +impl DowncastableI8I32 of Downcastable; +impl DowncastableI8U32 of Downcastable; +impl DowncastableI8I64 of Downcastable; +impl DowncastableI8U64 of Downcastable; +impl DowncastableI8I128 of Downcastable; +impl DowncastableI8U128 of Downcastable; + +impl DowncastableU16I8 of Downcastable; +impl DowncastableU16U8 of Downcastable; +impl DowncastableU16I16 of Downcastable; +impl DowncastableU16I32 of Downcastable; +impl DowncastableU16U32 of Downcastable; +impl DowncastableU16I64 of Downcastable; +impl DowncastableU16U64 of Downcastable; +impl DowncastableU16I128 of Downcastable; +impl DowncastableU16U128 of Downcastable; +impl DowncastableI16I8 of Downcastable; +impl DowncastableI16U8 of Downcastable; +impl DowncastableI16U16 of Downcastable; +impl DowncastableI16I32 of Downcastable; +impl DowncastableI16U32 of Downcastable; +impl DowncastableI16I64 of Downcastable; +impl DowncastableI16U64 of Downcastable; +impl DowncastableI16I128 of Downcastable; +impl DowncastableI16U128 of Downcastable; + +impl DowncastableU32I8 of Downcastable; +impl DowncastableU32U8 of Downcastable; +impl DowncastableU32I16 of Downcastable; +impl DowncastableU32U16 of Downcastable; +impl DowncastableU32I32 of Downcastable; +impl DowncastableU32I64 of Downcastable; +impl DowncastableU32U64 of Downcastable; +impl DowncastableU32I128 of Downcastable; +impl DowncastableU32U128 of Downcastable; +impl DowncastableI32I8 of Downcastable; +impl DowncastableI32U8 of Downcastable; +impl DowncastableI32I16 of Downcastable; +impl DowncastableI32U16 of Downcastable; +impl DowncastableI32U32 of Downcastable; +impl DowncastableI32I64 of Downcastable; +impl DowncastableI32U64 of Downcastable; +impl DowncastableI32I128 of Downcastable; +impl DowncastableI32U128 of Downcastable; + +impl DowncastableU64I8 of Downcastable; +impl DowncastableU64U8 of Downcastable; +impl DowncastableU64I16 of Downcastable; +impl DowncastableU64U16 of Downcastable; +impl DowncastableU64I32 of Downcastable; +impl DowncastableU64U32 of Downcastable; +impl DowncastableU64I64 of Downcastable; +impl DowncastableU64I128 of Downcastable; +impl DowncastableU64U128 of Downcastable; +impl DowncastableI64I8 of Downcastable; +impl DowncastableI64U8 of Downcastable; +impl DowncastableI64I16 of Downcastable; +impl DowncastableI64U16 of Downcastable; +impl DowncastableI64I32 of Downcastable; +impl DowncastableI64U32 of Downcastable; +impl DowncastableI64U64 of Downcastable; +impl DowncastableI64I128 of Downcastable; +impl DowncastableI64U128 of Downcastable; + +impl DowncastableU128I8 of Downcastable; +impl DowncastableU128U8 of Downcastable; +impl DowncastableU128I16 of Downcastable; +impl DowncastableU128U16 of Downcastable; +impl DowncastableU128I32 of Downcastable; +impl DowncastableU128U32 of Downcastable; +impl DowncastableU128I64 of Downcastable; +impl DowncastableU128U64 of Downcastable; +impl DowncastableU128I128 of Downcastable; +impl DowncastableI128I8 of Downcastable; +impl DowncastableI128U8 of Downcastable; +impl DowncastableI128I16 of Downcastable; +impl DowncastableI128U16 of Downcastable; +impl DowncastableI128I32 of Downcastable; +impl DowncastableI128U32 of Downcastable; +impl DowncastableI128I64 of Downcastable; +impl DowncastableI128U64 of Downcastable; +impl DowncastableI128U128 of Downcastable; /// Default values impl U8Default of Default { @@ -1958,7 +2059,7 @@ impl I8Neg of Neg { extern fn i8_wide_mul(lhs: i8, rhs: i8) -> i16 implicits() nopanic; impl I8Mul of Mul { fn mul(lhs: i8, rhs: i8) -> i8 { - i8_try_from_felt252(i16_to_felt252(i8_wide_mul(lhs, rhs))).expect('i8_mul Overflow') + i8_wide_mul(lhs, rhs).try_into().expect('i8_mul Overflow') } } impl I8MulEq of MulEq { @@ -2059,7 +2160,7 @@ impl I16Neg of Neg { extern fn i16_wide_mul(lhs: i16, rhs: i16) -> i32 implicits() nopanic; impl I16Mul of Mul { fn mul(lhs: i16, rhs: i16) -> i16 { - i16_try_from_felt252(i32_to_felt252(i16_wide_mul(lhs, rhs))).expect('i16_mul Overflow') + i16_wide_mul(lhs, rhs).try_into().expect('i16_mul Overflow') } } impl I16MulEq of MulEq { @@ -2160,7 +2261,7 @@ impl I32Neg of Neg { extern fn i32_wide_mul(lhs: i32, rhs: i32) -> i64 implicits() nopanic; impl I32Mul of Mul { fn mul(lhs: i32, rhs: i32) -> i32 { - i32_try_from_felt252(i64_to_felt252(i32_wide_mul(lhs, rhs))).expect('i32_mul Overflow') + i32_wide_mul(lhs, rhs).try_into().expect('i32_mul Overflow') } } impl I32MulEq of MulEq { @@ -2261,7 +2362,7 @@ impl I64Neg of Neg { extern fn i64_wide_mul(lhs: i64, rhs: i64) -> i128 implicits() nopanic; impl I64Mul of Mul { fn mul(lhs: i64, rhs: i64) -> i64 { - i64_try_from_felt252(i128_to_felt252(i64_wide_mul(lhs, rhs))).expect('i64_mul Overflow') + i64_wide_mul(lhs, rhs).try_into().expect('i64_mul Overflow') } } impl I64MulEq of MulEq { diff --git a/corelib/src/test/integer_test.cairo b/corelib/src/test/integer_test.cairo index bba6c5273f5..e430c22ea0a 100644 --- a/corelib/src/test/integer_test.cairo +++ b/corelib/src/test/integer_test.cairo @@ -931,39 +931,213 @@ fn test_u256_try_into_felt252() { assert(f.is_none(), 'prime+2**128 is not felt252'); } -fn cast_must_pass< +/// Checks if `b` is out of range of `A`. +fn is_out_of_range, +TryInto>(b: B) -> bool { + let no_a: Option = b.try_into(); + no_a.is_none() +} + +/// Checks if `SubType` is trivially castable to `SuperType`. +fn cast_subtype_valid< + SubType, + SuperType, + +Drop, + +Drop, + +Copy, + +Copy, + +BoundedInt, + +PartialEq, + +PartialEq, + +Into, + +TryInto +>() -> bool { + let max_sub: SubType = BoundedInt::max(); + let max_sub_as_super: SuperType = max_sub.into(); + let min_sub: SubType = BoundedInt::min(); + let min_sub_as_super: SuperType = min_sub.into(); + min_sub_as_super.try_into().unwrap() == min_sub + && max_sub_as_super.try_into().unwrap() == max_sub +} + +/// Checks that `A::max()` is castable to `B`, and `A::max() + 1` is in `B`s range, and not +/// castable back to `A`. +fn validate_max_strictly_contained< A, B, +Drop, +Drop, + +Copy, +Copy, + +Add, + +BoundedInt, + +PartialEq, + +PartialEq, + +TryInto, + +TryInto, + +TryInto +>( + err: felt252 +) { + let max_a: A = BoundedInt::max(); + let max_a_as_b: B = max_a.try_into().expect(err); + assert(Option::Some(max_a) == max_a_as_b.try_into(), err); + assert(is_out_of_range::(max_a_as_b + 1.try_into().unwrap()), err); +} + +/// Checks that `A::min()` is castable to `B`, and `A::min() - 1` is in `B`s range, and not +/// castable back to `A`. +fn validate_min_strictly_contained< + A, + B, + +Drop, + +Drop, +Copy, + +Copy, + +Sub, + +BoundedInt, +PartialEq, +PartialEq, + +TryInto, + +TryInto, + +TryInto +>( + err: felt252 +) { + let min_sub: A = BoundedInt::min(); + let min_sub_as_super: B = min_sub.try_into().expect(err); + assert(Option::Some(min_sub) == min_sub_as_super.try_into(), err); + assert(is_out_of_range::(min_sub_as_super - 1.try_into().unwrap()), err); +} + +/// Checks that castings from `SubType` to `SuperType` are correct around the bounds, where +/// `SubType` is strictly contained (in both bounds) in `SuperType`. +fn validate_cast_bounds_strictly_contained< + SubType, + SuperType, + +Drop, + +Drop, + +Copy, + +Copy, + +Add, + +Sub, + +BoundedInt, + +PartialEq, + +PartialEq, + +Into, + +TryInto, + +TryInto, + +TryInto +>( + err: felt252 +) { + assert(cast_subtype_valid::(), err); + validate_min_strictly_contained::(err); + validate_max_strictly_contained::(err); +} + +/// Checks that castings from `SubType` to `SuperType` are correct around the bounds, where +/// `SubType` has the same min as `SuperType`, but has a lower max. +fn validate_cast_bounds_contained_same_min< + SubType, + SuperType, + +Drop, + +Drop, + +Copy, + +Copy, + +Add, + +Sub, + +BoundedInt, + +BoundedInt, + +PartialEq, + +PartialEq, + +Into, + +TryInto, + +TryInto, + +TryInto +>( + err: felt252 +) { + assert(cast_subtype_valid::(), err); + assert(BoundedInt::::min().into() == BoundedInt::::min(), err); + validate_max_strictly_contained::(err); +} + +/// Checks that castings from `A` to `B` are correct around the bounds. +/// Assumes that the ordering of the bounds is: `a_min < b_min < a_max < b_max`. +fn validate_cast_bounds_overlapping< + A, + B, + +Drop, + +Drop, + +Copy, + +Copy, + +Sub, + +Add, +BoundedInt, +BoundedInt, - +Into, - +TryInto + +PartialEq, + +PartialEq, + +TryInto, + +TryInto, + +TryInto, + +TryInto >( - ui: A, uj: B -) -> bool { - uj == ui.into() - && ui == uj.try_into().unwrap() - && BoundedInt::::min() == BoundedInt::::min().into() - && BoundedInt::::min() == BoundedInt::::min().try_into().unwrap() + err: felt252 +) { + validate_min_strictly_contained::(err); + validate_max_strictly_contained::(err); } + #[test] fn proper_cast() { - assert(cast_must_pass(0xFF_u8, 0xFF_u16), 'u8 to_and_fro u16'); - assert(cast_must_pass(0xFF_u8, 0xFF_u32), 'u8 to_and_fro u32'); - assert(cast_must_pass(0xFF_u8, 0xFF_u64), 'u8 to_and_fro u64'); - assert(cast_must_pass(0xFF_u8, 0xFF_u128), 'u8 to_and_fro u128'); - assert(cast_must_pass(0xFFFF_u16, 0xFFFF_u32), 'u16 to_and_fro u32'); - assert(cast_must_pass(0xFFFF_u16, 0xFFFF_u64), 'u16 to_and_fro u64'); - assert(cast_must_pass(0xFFFF_u16, 0xFFFF_u128), 'u16 to_and_fro u128'); - assert(cast_must_pass(0xFFFFFFFF_u32, 0xFFFFFFFF_u64), 'u32 to_and_fro u64'); - assert(cast_must_pass(0xFFFFFFFF_u32, 0xFFFFFFFF_u128), 'u32 to_and_fro u128'); - assert(cast_must_pass(0xFFFFFFFFFFFFFFFF_u64, 0xFFFFFFFFFFFFFFFF_u128), 'u64 to_and_fro u128'); + validate_cast_bounds_contained_same_min::('u8 u16 casts'); + validate_cast_bounds_contained_same_min::('u8 u32 casts'); + validate_cast_bounds_contained_same_min::('u8 u64 casts'); + validate_cast_bounds_contained_same_min::('u8 u128 casts'); + validate_cast_bounds_contained_same_min::('u16 u32 casts'); + validate_cast_bounds_contained_same_min::('u16 u64 casts'); + validate_cast_bounds_contained_same_min::('u16 u128 casts'); + validate_cast_bounds_contained_same_min::('u32 u64 casts'); + validate_cast_bounds_contained_same_min::('u32 u128 casts'); + validate_cast_bounds_contained_same_min::('u64 u128 casts'); + + validate_cast_bounds_strictly_contained::('u8 i16 casts'); + validate_cast_bounds_strictly_contained::('u8 i32 casts'); + validate_cast_bounds_strictly_contained::('u8 i64 casts'); + validate_cast_bounds_strictly_contained::('u8 i128 casts'); + validate_cast_bounds_strictly_contained::('u16 i32 casts'); + validate_cast_bounds_strictly_contained::('u16 i64 casts'); + validate_cast_bounds_strictly_contained::('u16 i128 casts'); + validate_cast_bounds_strictly_contained::('u32 i64 casts'); + validate_cast_bounds_strictly_contained::('u32 i128 casts'); + validate_cast_bounds_strictly_contained::('u64 i128 casts'); + + validate_cast_bounds_strictly_contained::('i8 i16 casts'); + validate_cast_bounds_strictly_contained::('i8 i32 casts'); + validate_cast_bounds_strictly_contained::('i8 i64 casts'); + validate_cast_bounds_strictly_contained::('i8 i128 casts'); + validate_cast_bounds_strictly_contained::('i16 i32 casts'); + validate_cast_bounds_strictly_contained::('i16 i64 casts'); + validate_cast_bounds_strictly_contained::('i16 i128 casts'); + validate_cast_bounds_strictly_contained::('i32 i64 casts'); + validate_cast_bounds_strictly_contained::('i32 i128 casts'); + validate_cast_bounds_strictly_contained::('i64 i128 casts'); + + validate_cast_bounds_overlapping::('i8 u8 casts'); + validate_cast_bounds_overlapping::('i8 u16 casts'); + validate_cast_bounds_overlapping::('i8 u32 casts'); + validate_cast_bounds_overlapping::('i8 u64 casts'); + validate_cast_bounds_overlapping::('i8 u128 casts'); + validate_cast_bounds_overlapping::('i16 u16 casts'); + validate_cast_bounds_overlapping::('i16 u32 casts'); + validate_cast_bounds_overlapping::('i16 u64 casts'); + validate_cast_bounds_overlapping::('i16 u128 casts'); + validate_cast_bounds_overlapping::('i32 u32 casts'); + validate_cast_bounds_overlapping::('i32 u64 casts'); + validate_cast_bounds_overlapping::('i32 u128 casts'); + validate_cast_bounds_overlapping::('i64 u64 casts'); + validate_cast_bounds_overlapping::('i64 u128 casts'); + validate_cast_bounds_overlapping::('i128 u128 casts'); } #[test] diff --git a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs index d22ac030047..e00ffb26afc 100644 --- a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs +++ b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs @@ -3,7 +3,7 @@ use cairo_lang_sierra::extensions::array::ArrayConcreteLibfunc; use cairo_lang_sierra::extensions::boolean::BoolConcreteLibfunc; use cairo_lang_sierra::extensions::boxing::BoxConcreteLibfunc; use cairo_lang_sierra::extensions::bytes31::Bytes31ConcreteLibfunc; -use cairo_lang_sierra::extensions::casts::CastConcreteLibfunc; +use cairo_lang_sierra::extensions::casts::{CastConcreteLibfunc, CastType}; use cairo_lang_sierra::extensions::core::CoreConcreteLibfunc; use cairo_lang_sierra::extensions::ec::EcConcreteLibfunc; use cairo_lang_sierra::extensions::enm::EnumConcreteLibfunc; @@ -96,7 +96,26 @@ pub fn core_libfunc_ap_change( BoxConcreteLibfunc::Unbox(_) => vec![ApChange::Known(0)], }, CoreConcreteLibfunc::Cast(libfunc) => match libfunc { - CastConcreteLibfunc::Downcast(_) => vec![ApChange::Known(2), ApChange::Known(2)], + CastConcreteLibfunc::Downcast(libfunc) => { + match libfunc.from_info.cast_type(&libfunc.to_info) { + CastType { overflow_above: false, overflow_below: false } => { + vec![ApChange::Known(0), ApChange::Known(0)] + } + CastType { overflow_above: true, overflow_below: false } => { + vec![ApChange::Known(2), ApChange::Known(2)] + } + // Overflow below test is more expensive for casting into signed types. + CastType { overflow_above: false, overflow_below: true } => vec![ + ApChange::Known(1 + usize::from(libfunc.to_info.signed)), + ApChange::Known(2), + ], + // Overflow below test is more expensive for casting into signed types. + CastType { overflow_above: true, overflow_below: true } => vec![ + ApChange::Known(2 + usize::from(libfunc.to_info.signed)), + ApChange::Known(3), + ], + } + } CastConcreteLibfunc::Upcast(_) => vec![ApChange::Known(0)], }, CoreConcreteLibfunc::Ec(libfunc) => match libfunc { diff --git a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs index 884a7a6f860..01b3d1ad719 100644 --- a/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs +++ b/crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs @@ -4,7 +4,7 @@ use cairo_lang_sierra::extensions::array::ArrayConcreteLibfunc; use cairo_lang_sierra::extensions::boolean::BoolConcreteLibfunc; use cairo_lang_sierra::extensions::boxing::BoxConcreteLibfunc; use cairo_lang_sierra::extensions::bytes31::Bytes31ConcreteLibfunc; -use cairo_lang_sierra::extensions::casts::CastConcreteLibfunc; +use cairo_lang_sierra::extensions::casts::{CastConcreteLibfunc, CastType}; use cairo_lang_sierra::extensions::core::CoreConcreteLibfunc::{self, *}; use cairo_lang_sierra::extensions::ec::EcConcreteLibfunc; use cairo_lang_sierra::extensions::enm::EnumConcreteLibfunc; @@ -137,11 +137,35 @@ pub fn core_libfunc_cost( BoolConcreteLibfunc::ToFelt252(_) => vec![ConstCost::steps(0).into()], }, Cast(libfunc) => match libfunc { - CastConcreteLibfunc::Downcast(_) => { - vec![ - (ConstCost::steps(3) + ConstCost::range_checks(1)).into(), - (ConstCost::steps(4) + ConstCost::range_checks(1)).into(), - ] + CastConcreteLibfunc::Downcast(libfunc) => { + match libfunc.from_info.cast_type(&libfunc.to_info) { + CastType { overflow_above: false, overflow_below: false } => { + vec![ConstCost::steps(0).into(), ConstCost::steps(0).into()] + } + CastType { overflow_above: true, overflow_below: false } => vec![ + (ConstCost::steps(3) + ConstCost::range_checks(1)).into(), + (ConstCost::steps(4) + ConstCost::range_checks(1)).into(), + ], + CastType { overflow_above: false, overflow_below: true } => { + // If the target type is signed the cost below is wrong. + assert!( + !libfunc.to_info.signed, + "If an overflow below is possible, and above is not, the target type \ + must be unsigned." + ); + vec![ + (ConstCost::steps(2) + ConstCost::range_checks(1)).into(), + (ConstCost::steps(4) + ConstCost::range_checks(1)).into(), + ] + } + CastType { overflow_above: true, overflow_below: true } => vec![ + // Overflow below test is more expensive for casting into signed types. + (ConstCost::steps(4 + i32::from(libfunc.to_info.signed)) + + ConstCost::range_checks(2)) + .into(), + (ConstCost::steps(5) + ConstCost::range_checks(1)).into(), + ], + } } CastConcreteLibfunc::Upcast(_) => vec![ConstCost::default().into()], }, diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/casts.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/casts.rs index c483de14204..276673e8a44 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/casts.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/casts.rs @@ -1,14 +1,17 @@ -use cairo_lang_casm::builder::CasmBuilder; +use cairo_lang_casm::builder::{CasmBuilder, Var}; use cairo_lang_casm::casm_build_extend; -use cairo_lang_sierra::extensions::casts::{CastConcreteLibfunc, DowncastConcreteLibfunc}; -use num_bigint::BigUint; -use num_traits::Pow; +use cairo_lang_sierra::extensions::casts::{ + CastConcreteLibfunc, CastType, DowncastConcreteLibfunc, IntTypeInfo, +}; +use num_bigint::BigInt; +use num_traits::Zero; use super::misc::build_identity; use super::{CompiledInvocation, CompiledInvocationBuilder, InvocationError}; use crate::invocations::{ add_input_variables, get_non_fallthrough_statement_id, CostValidationInfo, }; +use crate::references::ReferenceExpression; /// Builds instructions for Sierra cast operations. pub fn build( @@ -29,44 +32,39 @@ pub fn build_downcast( let [range_check, value] = builder.try_get_single_cells()?; let mut casm_builder = CasmBuilder::default(); add_input_variables!(casm_builder, - buffer(0) range_check; + buffer(1) range_check; deref value; ); // The casm code below assumes both types are at most 128 bits. assert!( - libfunc.from_nbits <= 128 && libfunc.to_nbits <= 128, + libfunc.from_info.nbits <= 128 && libfunc.to_info.nbits <= 128, "Downcasting from types of size > 128 bit is not supported." ); - let two = BigUint::from(2u64); - let bound = two.clone().pow(libfunc.to_nbits); - let rc_bound = two.pow(128_usize); - - casm_build_extend! {casm_builder, - let orig_range_check = range_check; - - // Use a hint to guess whether the result is in range (is_valid=1) or overflows - // (is_valid=0). - tempvar is_valid; - const limit = bound.clone(); - hint TestLessThan {lhs: value, rhs: limit} into {dst: is_valid}; - jump Success if is_valid != 0; - // Failure. - // value >= bound <=> value - bound >= 0. - // Note that we know that 0 <= value < 2^128. - tempvar shifted_value = value - limit; - assert shifted_value = *(range_check++); - jump Failure; + casm_build_extend!(casm_builder, let orig_range_check = range_check;); - // Success. - Success: - // Verify that the value is in range: - // value < limit <=> value + (rc_bound - bound) < rc_bound. - const pos_shift = rc_bound - bound; - tempvar shifted_value = value + pos_shift; - assert shifted_value = *(range_check++); - }; + let to_values = TypeValues::new(&libfunc.to_info); + match libfunc.from_info.cast_type(&libfunc.to_info) { + CastType { overflow_above: false, overflow_below: false } => { + return handle_downcast_no_overflow(builder); + } + CastType { overflow_above: true, overflow_below: false } => { + // Signed to signed would go the `both` or `no_overflow` cases. + // Signed to unsigned would go the `both` or `overflow_below` case. + assert!( + !libfunc.from_info.signed, + "`from` type must be unsigned for the overflow above case." + ); + add_downcast_overflow_above(&mut casm_builder, value, range_check, &(to_values.max + 1)) + } + CastType { overflow_above: false, overflow_below: true } => { + add_downcast_overflow_below(&mut casm_builder, value, range_check, &to_values.min) + } + CastType { overflow_above: true, overflow_below: true } => { + add_downcast_overflow_both(&mut casm_builder, value, range_check, &to_values) + } + } let target_statement_id = get_non_fallthrough_statement_id(&builder); Ok(builder.build_from_casm_builder( @@ -81,3 +79,151 @@ pub fn build_downcast( }, )) } + +#[derive(Debug)] +struct TypeValues { + /// The minimum value of the type. + min: BigInt, + /// The maximum value of the type. + max: BigInt, + /// The size of the type. + size: BigInt, +} +impl TypeValues { + fn new(info: &IntTypeInfo) -> Self { + Self { + min: if info.signed { -(BigInt::from(1) << (info.nbits - 1)) } else { BigInt::from(0) }, + max: (BigInt::from(1) << (if info.signed { info.nbits - 1 } else { info.nbits })) - 1, + size: BigInt::from(1) << info.nbits, + } + } +} + +/// Builds Casm instructions for [CastConcreteLibfunc::Downcast] for a trivial case where no +/// overflow is possible. +fn handle_downcast_no_overflow( + builder: CompiledInvocationBuilder<'_>, +) -> Result { + let [range_check, value] = builder.try_get_single_cells()?; + let success = vec![ + ReferenceExpression::from_cell(range_check.clone()), + ReferenceExpression::from_cell(value.clone()), + ]; + let unreachable_failure = vec![ReferenceExpression::from_cell(range_check.clone())]; + Ok(builder.build( + vec![], + vec![], + [success.into_iter(), unreachable_failure.into_iter()].into_iter(), + )) +} + +/// Adds instructions for downcasting where the value may only overflow above. +/// Note: The type of `value` must be unsigned. +fn add_downcast_overflow_above( + casm_builder: &mut CasmBuilder, + value: Var, + range_check: Var, + upper_bound: &BigInt, +) { + casm_build_extend! {casm_builder, + // Use a hint to guess whether the result is in range (is_valid=1) or overflows + // (is_valid=0). + // `value` is non-negative as it must be unsigned. + tempvar is_valid; + const value_limit_imm = upper_bound.clone(); + hint TestLessThan {lhs: value, rhs: value_limit_imm} into {dst: is_valid}; + jump Success if is_valid != 0; + } + // Validating we overflowed above. + validate_ge(casm_builder, range_check, value, upper_bound); + casm_build_extend!(casm_builder, jump Failure;); + + casm_build_extend!(casm_builder, Success:); + validate_lt(casm_builder, range_check, value, upper_bound); +} + +/// Adds instructions for downcasting where the value may only overflow below. +fn add_downcast_overflow_below( + casm_builder: &mut CasmBuilder, + value: Var, + range_check: Var, + lower_bound: &BigInt, +) { + casm_build_extend! {casm_builder, + const minus_lower_bound = -lower_bound; + let canonical_value = value + minus_lower_bound; + // Use a hint to guess whether the result is in range (is_valid=1) or overflows + // (is_valid=0). + tempvar is_valid; + const rc_bound_imm = (BigInt::from(u128::MAX) + 1) as BigInt; + hint TestLessThan {lhs: canonical_value, rhs: rc_bound_imm} into {dst: is_valid}; + jump Success if is_valid != 0; + // Overflow below. + } + validate_lt(casm_builder, range_check, value, lower_bound); + casm_build_extend!(casm_builder, jump Failure;); + + casm_build_extend!(casm_builder, Success:); + validate_ge(casm_builder, range_check, value, lower_bound); +} + +/// Adds instructions for downcasting where the value may both overflow and underflow. +fn add_downcast_overflow_both( + casm_builder: &mut CasmBuilder, + value: Var, + range_check: Var, + to_values: &TypeValues, +) { + casm_build_extend! {casm_builder, + const minus_to_min_value = -to_values.min.clone(); + let canonical_value = value + minus_to_min_value; + // Use a hint to guess whether the result is in range (is_valid=1) or overflows + // (is_valid=0). + tempvar is_valid; + const to_range_size = to_values.size.clone(); + hint TestLessThan {lhs: canonical_value, rhs: to_range_size} into {dst: is_valid}; + jump Success if is_valid != 0; + // Failure. + const rc_bound_imm = (BigInt::from(u128::MAX) + 1) as BigInt; + tempvar is_overflow_above; + // If the `canonical_value` is negative (and therefore larger than 2**128) this is an overflow + // below. + hint TestLessThan {lhs: canonical_value, rhs: rc_bound_imm} into {dst: is_overflow_above}; + jump OverflowAbove if is_overflow_above != 0; + } + // Overflow below. + validate_lt(casm_builder, range_check, value, &to_values.min); + casm_build_extend!(casm_builder, jump Failure;); + + casm_build_extend!(casm_builder, OverflowAbove:); + validate_ge(casm_builder, range_check, value, &(&to_values.max + 1)); + casm_build_extend!(casm_builder, jump Failure;); + + casm_build_extend!(casm_builder, Success:); + validate_ge(casm_builder, range_check, value, &to_values.min); + validate_lt(casm_builder, range_check, value, &(&to_values.max + 1)); +} + +/// Validates that `value` is smaller than `bound`. +fn validate_lt(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) { + casm_build_extend! {casm_builder, + // value < bound <=> value + (2**128 - bound) < 2**128. + const pos_shift = (BigInt::from(u128::MAX) + 1 - bound) as BigInt; + tempvar shifted_value = value + pos_shift; + assert shifted_value = *(range_check++); + }; +} + +/// Validates that `value` is greater or equal to `bound`. +fn validate_ge(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) { + if bound.is_zero() { + casm_build_extend! {casm_builder, assert value = *(range_check++);}; + } else { + casm_build_extend! {casm_builder, + // value >= bound <=> value - bound >= 0. + const bound = bound.clone(); + tempvar shifted_value = value - bound; + assert shifted_value = *(range_check++); + }; + } +} diff --git a/crates/cairo-lang-sierra/src/extensions/modules/casts.rs b/crates/cairo-lang-sierra/src/extensions/modules/casts.rs index ecbee336f56..8dec66ad9ce 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/casts.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/casts.rs @@ -1,3 +1,5 @@ +use super::int::signed::{Sint16Type, Sint32Type, Sint64Type, Sint8Type}; +use super::int::signed128::Sint128Type; use super::int::unsigned::{Uint16Type, Uint32Type, Uint64Type, Uint8Type}; use super::int::unsigned128::Uint128Type; use super::range_check::RangeCheckType; @@ -21,19 +23,81 @@ define_libfunc_hierarchy! { }, CastConcreteLibfunc } +/// The type of casting between two integer types. +#[derive(PartialEq, Eq)] +pub struct CastType { + /// Does the source type have values above the destination type possible values. + pub overflow_above: bool, + /// Does the source type have values below the destination type possible values. + pub overflow_below: bool, +} + +pub struct IntTypeInfo { + pub nbits: usize, + pub signed: bool, +} +impl IntTypeInfo { + /// Returns the cast type. + pub fn cast_type(&self, to: &IntTypeInfo) -> CastType { + match (self.signed, to.signed) { + (false, false) => { + // Unsigned to unsigned. + // Overflow below is never possible, as the minimum value of both types is 0. + // Overflow above is possible if `self` has strictly more bits than the `to`. + // We use `>=` instead of `>` to provide backward compatibility with the case + // casting from a type to itself. + // TODO(orizi): Remove this backward compatibility at next major sierra version. + CastType { overflow_above: self.nbits >= to.nbits, overflow_below: false } + } + (true, true) => { + // Signed to signed. + // Both overflows are possible if `self` has strictly more bits than `to`. + let can_overflow = self.nbits > to.nbits; + CastType { overflow_above: can_overflow, overflow_below: can_overflow } + } + (true, false) => { + // Signed to unsigned. + // Overflow below is always possible, as the minimum value of `self` is lower than 0 + // and of `to` is 0. Overflow above is possible if the `self` type + // has 2 bits more than `to` (as i8 to u7 cannot overflow, but i8 to u6 can). + CastType { overflow_above: self.nbits >= to.nbits + 2, overflow_below: true } + } + (false, true) => { + // Unsigned to signed. + // Overflow below is never possible, as the minimum value of `self` is 0 and of `to` + // lower than 0. Overflow above is possible if `self` has more bits + // than `to` (as u8 to i9 cannot overflow, but u8 to i8 can). + CastType { overflow_above: self.nbits >= to.nbits, overflow_below: false } + } + } + } + + /// Returns true if this type can participate in downcasts. + fn downcastable(&self) -> bool { + // We don't support downcasting larger than 128-bit integers, as this would not reduce the + // need for range checks. + self.nbits <= 128 + } +} + /// Returns a number of bits in a concrete integer type. -fn get_nbits( +fn get_int_info( context: &dyn SignatureSpecializationContext, ty: ConcreteTypeId, -) -> Result { - match context.get_type_info(ty)?.long_id.generic_id { - id if id == Uint8Type::ID => Ok(8), - id if id == Uint16Type::ID => Ok(16), - id if id == Uint32Type::ID => Ok(32), - id if id == Uint64Type::ID => Ok(64), - id if id == Uint128Type::ID => Ok(128), - _ => Err(SpecializationError::UnsupportedGenericArg), - } +) -> Result { + Ok(match context.get_type_info(ty)?.long_id.generic_id { + id if id == Uint8Type::ID => IntTypeInfo { nbits: 8, signed: false }, + id if id == Sint8Type::ID => IntTypeInfo { nbits: 8, signed: true }, + id if id == Uint16Type::ID => IntTypeInfo { nbits: 16, signed: false }, + id if id == Sint16Type::ID => IntTypeInfo { nbits: 16, signed: true }, + id if id == Uint32Type::ID => IntTypeInfo { nbits: 32, signed: false }, + id if id == Sint32Type::ID => IntTypeInfo { nbits: 32, signed: true }, + id if id == Uint64Type::ID => IntTypeInfo { nbits: 64, signed: false }, + id if id == Sint64Type::ID => IntTypeInfo { nbits: 64, signed: true }, + id if id == Uint128Type::ID => IntTypeInfo { nbits: 128, signed: false }, + id if id == Sint128Type::ID => IntTypeInfo { nbits: 128, signed: true }, + _ => return Err(SpecializationError::UnsupportedGenericArg), + }) } /// Libfunc for casting from one type to another where any input value can fit into the destination @@ -49,10 +113,17 @@ impl SignatureOnlyGenericLibfunc for UpcastLibfunc { args: &[GenericArg], ) -> Result { let (from_ty, to_ty) = args_as_two_types(args)?; - - let is_valid = get_nbits(context, from_ty.clone())? <= get_nbits(context, to_ty.clone())?; - if !is_valid { - return Err(SpecializationError::UnsupportedGenericArg); + let from_info = get_int_info(context, from_ty.clone())?; + let to_info = get_int_info(context, to_ty.clone())?; + let cast_type = from_info.cast_type(&to_info); + if cast_type.overflow_above || cast_type.overflow_below { + // Finding if the detected possible overflow is not actually possible, but a backward + // compatibility based one (as same type can have no overflow). + // TODO(orizi): Remove this check after the backward compatibility is removed at next + // major sierra version. + if from_ty != to_ty { + return Err(SpecializationError::UnsupportedGenericArg); + } } Ok(reinterpret_cast_signature(from_ty, to_ty)) @@ -63,9 +134,9 @@ impl SignatureOnlyGenericLibfunc for UpcastLibfunc { pub struct DowncastConcreteLibfunc { pub signature: LibfuncSignature, pub from_ty: ConcreteTypeId, - pub from_nbits: usize, + pub from_info: IntTypeInfo, pub to_ty: ConcreteTypeId, - pub to_nbits: usize, + pub to_info: IntTypeInfo, } impl SignatureBasedConcreteLibfunc for DowncastConcreteLibfunc { fn signature(&self) -> &LibfuncSignature { @@ -87,9 +158,9 @@ impl NamedLibfunc for DowncastLibfunc { args: &[GenericArg], ) -> Result { let (from_ty, to_ty) = args_as_two_types(args)?; - - let is_valid = get_nbits(context, from_ty.clone())? >= get_nbits(context, to_ty.clone())?; - if !is_valid { + let from_info = get_int_info(context, from_ty.clone())?; + let to_info = get_int_info(context, to_ty.clone())?; + if !from_info.downcastable() || !to_info.downcastable() { return Err(SpecializationError::UnsupportedGenericArg); } @@ -130,9 +201,9 @@ impl NamedLibfunc for DowncastLibfunc { let (from_ty, to_ty) = args_as_two_types(args)?; Ok(DowncastConcreteLibfunc { signature: self.specialize_signature(context.upcast(), args)?, - from_nbits: get_nbits(context.upcast(), from_ty.clone())?, + from_info: get_int_info(context.upcast(), from_ty.clone())?, from_ty, - to_nbits: get_nbits(context.upcast(), to_ty.clone())?, + to_info: get_int_info(context.upcast(), to_ty.clone())?, to_ty, }) } diff --git a/tests/e2e_test_data/libfuncs/casts b/tests/e2e_test_data/libfuncs/casts index 051ce8dba33..5ba49d6402b 100644 --- a/tests/e2e_test_data/libfuncs/casts +++ b/tests/e2e_test_data/libfuncs/casts @@ -30,6 +30,37 @@ test::foo@0([0]: u16) -> (u64); //! > ========================================================================== +//! > u64 to u64 upcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: u64) -> u64 { + integer::upcast(a) +} + +//! > casm +[ap + 0] = [fp + -3], ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 100}) + +//! > sierra_code +type u64 = u64 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc upcast = upcast; +libfunc store_temp = store_temp; + +upcast([0]) -> ([1]); // 0 +store_temp([1]) -> ([2]); // 1 +return([2]); // 2 + +test::foo@0([0]: u64) -> (u64); + +//! > ========================================================================== + //! > u64 to u16 downcast //! > test_runner_name @@ -94,3 +125,604 @@ rename>([7]) -> ([11]); // 12 return([10], [11]); // 13 test::foo@0([0]: RangeCheck, [1]: u64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > u64 to u64 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: u64) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = memory[fp + -3] < 18446744073709551616 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[fp + -3] = [ap + 0] + 18446744073709551616, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 12; +[ap + 0] = [fp + -3] + 340282366920938463444927863358058659840, ap++; +[ap + -1] = [[fp + -4] + 0]; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 770}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type u64 = u64 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, u64, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: u64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > i64 to i64 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: i64) -> Option:: { + integer::downcast(a) +} + +//! > casm +[ap + 0] = [fp + -4], ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 7; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 400}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type i64 = i64 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, i64, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: i64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > u32 to u64 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: u32) -> Option:: { + integer::downcast(a) +} + +//! > casm +[ap + 0] = [fp + -4], ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 7; +[ap + 0] = [fp + -4], ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 400}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type u64 = u64 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, u64, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type u32 = u32 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: u32) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > i64 to u16 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: i64) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = (memory[fp + -3] + 0) % PRIME < 65536 %} +jmp rel 14 if [ap + 0] != 0, ap++; +%{ memory[ap + 0] = (memory[fp + -3] + 0) % PRIME < 340282366920938463463374607431768211456 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768211456, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 20; +[fp + -3] = [ap + 0] + 65536, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 15; +[fp + -3] = [[fp + -4] + 0]; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768145920, ap++; +[ap + -1] = [[fp + -4] + 1]; +ap += 1; +[ap + 0] = [fp + -4] + 2, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 1050}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type u16 = u16 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, u16, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type i64 = i64 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: i64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > u64 to i16 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: u64) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = memory[fp + -3] < 32768 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[fp + -3] = [ap + 0] + 32768, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 12; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768178688, ap++; +[ap + -1] = [[fp + -4] + 0]; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 770}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type i16 = i16 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, i16, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type u64 = u64 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: u64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > i64 to i16 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: i64) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = (memory[fp + -3] + 32768) % PRIME < 65536 %} +jmp rel 14 if [ap + 0] != 0, ap++; +%{ memory[ap + 0] = (memory[fp + -3] + 32768) % PRIME < 340282366920938463463374607431768211456 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768244224, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 20; +[fp + -3] = [ap + 0] + 32768, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 15; +[fp + -3] = [ap + 0] + -32768, ap++; +[ap + -1] = [[fp + -4] + 0]; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768178688, ap++; +[ap + -1] = [[fp + -4] + 1]; +[ap + 0] = [fp + -4] + 2, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 1040}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type i16 = i16 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, i16, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type i64 = i64 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: i64) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > i16 to u64 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: i16) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = (memory[fp + -3] + 0) % PRIME < 340282366920938463463374607431768211456 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768211456, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 12; +[fp + -3] = [[fp + -4] + 0]; +ap += 1; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 780}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type u64 = u64 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, u64, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type i16 = i16 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: i16) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > i16 to u16 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: i16) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = (memory[fp + -3] + 0) % PRIME < 340282366920938463463374607431768211456 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768211456, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 12; +[fp + -3] = [[fp + -4] + 0]; +ap += 1; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 780}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type u16 = u16 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, u16, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type i16 = i16 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: i16) -> (RangeCheck, core::option::Option::); + +//! > ========================================================================== + +//! > u16 to i16 downcast + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +fn foo(a: u16) -> Option:: { + integer::downcast(a) +} + +//! > casm +%{ memory[ap + 0] = memory[fp + -3] < 32768 %} +jmp rel 7 if [ap + 0] != 0, ap++; +[fp + -3] = [ap + 0] + 32768, ap++; +[ap + -1] = [[fp + -4] + 0]; +jmp rel 12; +[ap + 0] = [fp + -3] + 340282366920938463463374607431768178688, ap++; +[ap + -1] = [[fp + -4] + 0]; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -3], ap++; +jmp rel 8; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 770}) + +//! > sierra_code +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type i16 = i16 [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option:: = Enum, i16, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type u16 = u16 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc downcast = downcast; +libfunc branch_align = branch_align; +libfunc enum_init, 0> = enum_init, 0>; +libfunc store_temp = store_temp; +libfunc store_temp> = store_temp>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init, 1> = enum_init, 1>; +libfunc rename = rename; +libfunc rename> = rename>; + +downcast([0], [1]) { fallthrough([2], [3]) 6([4]) }; // 0 +branch_align() -> (); // 1 +enum_init, 0>([3]) -> ([5]); // 2 +store_temp([2]) -> ([6]); // 3 +store_temp>([5]) -> ([7]); // 4 +jump() { 11() }; // 5 +branch_align() -> (); // 6 +struct_construct() -> ([8]); // 7 +enum_init, 1>([8]) -> ([9]); // 8 +store_temp([4]) -> ([6]); // 9 +store_temp>([9]) -> ([7]); // 10 +rename([6]) -> ([10]); // 11 +rename>([7]) -> ([11]); // 12 +return([10], [11]); // 13 + +test::foo@0([0]: RangeCheck, [1]: u16) -> (RangeCheck, core::option::Option::);