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 5c7e210a07d..066a1dcdcb3 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 @@ -34,6 +34,7 @@ use cairo_lang_sierra::extensions::mem::MemConcreteLibfunc; use cairo_lang_sierra::extensions::nullable::NullableConcreteLibfunc; use cairo_lang_sierra::extensions::pedersen::PedersenConcreteLibfunc; use cairo_lang_sierra::extensions::poseidon::PoseidonConcreteLibfunc; +use cairo_lang_sierra::extensions::range::RangeConcreteLibfunc; use cairo_lang_sierra::extensions::starknet::testing::TestingConcreteLibfunc; use cairo_lang_sierra::extensions::starknet::StarkNetConcreteLibfunc; use cairo_lang_sierra::extensions::structure::StructConcreteLibfunc; @@ -411,6 +412,9 @@ pub fn core_libfunc_ap_change( Circuit(CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify(_)) => { vec![ApChange::Known(0)] } + Range(libfunc) => match libfunc { + RangeConcreteLibfunc::PopFront(_) => vec![ApChange::Known(1), ApChange::Known(1)], + }, } } 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 9bedfc7ce31..7594955e1d5 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 @@ -42,6 +42,7 @@ use cairo_lang_sierra::extensions::mem::MemConcreteLibfunc::{ use cairo_lang_sierra::extensions::nullable::NullableConcreteLibfunc; use cairo_lang_sierra::extensions::pedersen::PedersenConcreteLibfunc; use cairo_lang_sierra::extensions::poseidon::PoseidonConcreteLibfunc; +use cairo_lang_sierra::extensions::range::RangeConcreteLibfunc; use cairo_lang_sierra::extensions::structure::StructConcreteLibfunc; use cairo_lang_sierra::ids::ConcreteTypeId; use cairo_lang_sierra::program::Function; @@ -581,6 +582,11 @@ pub fn core_libfunc_cost( vec![ConstCost { steps: 32, holes: 0, range_checks: 0, range_checks96: 6 }.into()] } }, + Range(libfunc) => match libfunc { + RangeConcreteLibfunc::PopFront(_) => { + vec![ConstCost::steps(2).into(), ConstCost::steps(3).into()] + } + }, } } diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/mod.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/mod.rs index cad7559c85c..b90becf0351 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/mod.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/mod.rs @@ -55,6 +55,7 @@ mod misc; mod nullable; mod pedersen; mod poseidon; +mod range; mod range_reduction; mod starknet; mod structure; @@ -721,6 +722,7 @@ pub fn compile_invocation( }, BoundedInt(libfunc) => int::bounded::build(libfunc, builder), Circuit(libfunc) => circuit::build(libfunc, builder), + Range(libfunc) => range::build(libfunc, builder), } } diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/range.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/range.rs new file mode 100644 index 00000000000..6e3896ad8fb --- /dev/null +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/range.rs @@ -0,0 +1,46 @@ +use cairo_lang_casm::builder::CasmBuilder; +use cairo_lang_casm::casm_build_extend; +use cairo_lang_sierra::extensions::range::RangeConcreteLibfunc; + +use super::{CompiledInvocation, CompiledInvocationBuilder, InvocationError}; +use crate::invocations::{add_input_variables, get_non_fallthrough_statement_id}; + +/// Builds instructions for `Range` operations. +pub fn build( + libfunc: &RangeConcreteLibfunc, + builder: CompiledInvocationBuilder<'_>, +) -> Result { + match libfunc { + RangeConcreteLibfunc::PopFront(_) => build_pop_front(builder), + } +} + +/// Libfunc for reducing `[a, b)` to `[a + 1, b)`. +fn build_pop_front( + builder: CompiledInvocationBuilder<'_>, +) -> Result { + let [start, end] = builder.try_get_refs::<1>()?[0].try_unpack()?; + + let mut casm_builder = CasmBuilder::default(); + add_input_variables! {casm_builder, + deref start; + deref end; + }; + casm_build_extend! {casm_builder, + tempvar is_non_empty = end - start; + jump NonEmpty if is_non_empty != 0; + jump Failure; + NonEmpty: + const one = 1; + let new_start = start + one; + }; + let failure_handle = get_non_fallthrough_statement_id(&builder); + Ok(builder.build_from_casm_builder( + casm_builder, + [ + ("Fallthrough", &[&[new_start, end], &[start]], None), + ("Failure", &[], Some(failure_handle)), + ], + Default::default(), + )) +} diff --git a/crates/cairo-lang-sierra/src/extensions/core.rs b/crates/cairo-lang-sierra/src/extensions/core.rs index 4f47e81cfa7..9c4170fff7c 100644 --- a/crates/cairo-lang-sierra/src/extensions/core.rs +++ b/crates/cairo-lang-sierra/src/extensions/core.rs @@ -41,7 +41,7 @@ use super::modules::unconditional_jump::UnconditionalJumpLibfunc; use super::nullable::{NullableLibfunc, NullableType}; use super::pedersen::{PedersenLibfunc, PedersenType}; use super::poseidon::{PoseidonLibfunc, PoseidonType}; -use super::range::RangeType; +use super::range::{RangeLibfunc, RangeType}; use super::range_check::{RangeCheck96Type, RangeCheckType}; use super::segment_arena::SegmentArenaType; use super::snapshot::{SnapshotTakeLibfunc, SnapshotType}; @@ -139,6 +139,7 @@ define_libfunc_hierarchy! { Felt252DictEntry(Felt252DictEntryLibfunc), Pedersen(PedersenLibfunc), Poseidon(PoseidonLibfunc), + Range(RangeLibfunc), StarkNet(StarkNetLibfunc), Debug(DebugLibfunc), SnapshotTake(SnapshotTakeLibfunc), diff --git a/crates/cairo-lang-sierra/src/extensions/modules/range.rs b/crates/cairo-lang-sierra/src/extensions/modules/range.rs index 084a960369e..c375f904160 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/range.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/range.rs @@ -3,11 +3,18 @@ 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 crate::define_libfunc_hierarchy; +use crate::extensions::lib_func::{ + BranchSignature, DeferredOutputKind, LibfuncSignature, OutputVarInfo, ParamSignature, + SierraApChange, SignatureOnlyGenericLibfunc, SignatureSpecializationContext, +}; use crate::extensions::type_specialization_context::TypeSpecializationContext; use crate::extensions::types::{ GenericTypeArgGenericType, GenericTypeArgGenericTypeWrapper, TypeInfo, }; -use crate::extensions::{NamedType, SpecializationError}; +use crate::extensions::{ + args_as_single_type, NamedType, OutputVarReferenceInfo, SpecializationError, +}; use crate::ids::GenericTypeId; use crate::program::GenericArg; @@ -62,3 +69,54 @@ impl GenericTypeArgGenericType for RangeTypeWrapped { } } pub type RangeType = GenericTypeArgGenericTypeWrapper; + +define_libfunc_hierarchy! { + pub enum RangeLibfunc { + PopFront(RangePopFrontLibfunc), + }, RangeConcreteLibfunc +} + +/// Libfunc that takes the range `[x, y)` and if `x < y`, returns the range `[x + 1, y)` and the +/// value `x`. +#[derive(Default)] +pub struct RangePopFrontLibfunc {} +impl SignatureOnlyGenericLibfunc for RangePopFrontLibfunc { + const STR_ID: &'static str = "range_pop_front"; + + fn specialize_signature( + &self, + context: &dyn SignatureSpecializationContext, + args: &[GenericArg], + ) -> Result { + let ty = args_as_single_type(args)?; + let range_ty = context.get_wrapped_concrete_type(RangeType::id(), ty.clone())?; + + Ok(LibfuncSignature { + param_signatures: vec![ParamSignature::new(range_ty.clone())], + branch_signatures: vec![ + // Success. + BranchSignature { + vars: vec![ + OutputVarInfo { + ty: range_ty, + ref_info: OutputVarReferenceInfo::Deferred( + DeferredOutputKind::AddConst { param_idx: 0 }, + ), + }, + OutputVarInfo { + ty, + ref_info: OutputVarReferenceInfo::PartialParam { param_idx: 0 }, + }, + ], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + // Failure. + BranchSignature { + vars: vec![], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + ], + fallthrough: Some(0), + }) + } +} diff --git a/crates/cairo-lang-sierra/src/simulation/core.rs b/crates/cairo-lang-sierra/src/simulation/core.rs index 08a025956b9..12350ef9468 100644 --- a/crates/cairo-lang-sierra/src/simulation/core.rs +++ b/crates/cairo-lang-sierra/src/simulation/core.rs @@ -292,6 +292,7 @@ pub fn simulate< CoreConcreteLibfunc::Coupon(_) => unimplemented!(), CoreConcreteLibfunc::BoundedInt(_) => unimplemented!(), CoreConcreteLibfunc::Circuit(_) => unimplemented!(), + CoreConcreteLibfunc::Range(_) => unimplemented!(), }) } diff --git a/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists/all.json b/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists/all.json index 2478ac3280a..4a4349a1e8a 100644 --- a/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists/all.json +++ b/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists/all.json @@ -147,6 +147,7 @@ "nullable_forward_snapshot", "nullable_from_box", "pedersen", + "range_pop_front", "redeposit_gas", "rename", "replace_class_syscall", diff --git a/tests/e2e_test.rs b/tests/e2e_test.rs index 39f2ed4976f..8dd8a1d624b 100644 --- a/tests/e2e_test.rs +++ b/tests/e2e_test.rs @@ -74,6 +74,7 @@ cairo_lang_test_utils::test_file_test_with_runner!( i8: "i8", nullable: "nullable", poseidon: "poseidon", + range: "range", snapshot: "snapshot", u128: "u128", u16: "u16", diff --git a/tests/e2e_test_data/libfuncs/range b/tests/e2e_test_data/libfuncs/range new file mode 100644 index 00000000000..78a787988dd --- /dev/null +++ b/tests/e2e_test_data/libfuncs/range @@ -0,0 +1,60 @@ +//! > range_pop_front libfunc + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +// TODO(lior): Move to `range.cairo`. +extern type Range; +extern fn range_pop_front(range: Range) -> Option<(Range, T)> nopanic; + +fn foo(v: Range) -> Option<(Range, i16)> { + range_pop_front(v) +} + +//! > casm +[fp + -3] = [ap + 0] + [fp + -4], ap++; +jmp rel 4 if [ap + -1] != 0; +jmp rel 9; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = [fp + -3], ap++; +[ap + 0] = [fp + -4], ap++; +ret; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 700}) + +//! > sierra_code +type Range = Range [storable: true, drop: true, dup: true, 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 Tuple, i16> = Struct, i16> [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option::<(test::Range::, core::integer::i16)> = Enum, core::integer::i16)>, Tuple, i16>, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc range_pop_front = range_pop_front; +libfunc branch_align = branch_align; +libfunc struct_construct, i16>> = struct_construct, i16>>; +libfunc enum_init, core::integer::i16)>, 0> = enum_init, core::integer::i16)>, 0>; +libfunc store_temp, core::integer::i16)>> = store_temp, core::integer::i16)>>; +libfunc struct_construct = struct_construct; +libfunc enum_init, core::integer::i16)>, 1> = enum_init, core::integer::i16)>, 1>; + +range_pop_front([0]) { fallthrough([1], [2]) 6() }; // 0 +branch_align() -> (); // 1 +struct_construct, i16>>([1], [2]) -> ([3]); // 2 +enum_init, core::integer::i16)>, 0>([3]) -> ([4]); // 3 +store_temp, core::integer::i16)>>([4]) -> ([4]); // 4 +return([4]); // 5 +branch_align() -> (); // 6 +struct_construct() -> ([5]); // 7 +enum_init, core::integer::i16)>, 1>([5]) -> ([6]); // 8 +store_temp, core::integer::i16)>>([6]) -> ([6]); // 9 +return([6]); // 10 + +test::foo@0([0]: Range) -> (core::option::Option::<(test::Range::, core::integer::i16)>);