From c392a11557607bf4532d19ba87f50af4a787f864 Mon Sep 17 00:00:00 2001 From: Lior Goldberg Date: Mon, 26 Aug 2024 17:36:25 +0300 Subject: [PATCH] Add int_range_pop_front libfunc. commit-id:12946952 --- corelib/src/internal.cairo | 9 +++ .../src/core_libfunc_ap_change.rs | 4 ++ .../src/core_libfunc_cost_base.rs | 6 ++ .../src/invocations/mod.rs | 2 + .../src/invocations/range.rs | 44 ++++++++++++++ .../cairo-lang-sierra/src/extensions/core.rs | 5 +- .../src/extensions/modules/range.rs | 60 ++++++++++++++++++- .../cairo-lang-sierra/src/simulation/core.rs | 1 + .../src/allowed_libfuncs_lists/all.json | 1 + tests/e2e_test.rs | 1 + tests/e2e_test_data/libfuncs/range | 60 +++++++++++++++++++ 11 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 crates/cairo-lang-sierra-to-casm/src/invocations/range.rs create mode 100644 tests/e2e_test_data/libfuncs/range diff --git a/corelib/src/internal.cairo b/corelib/src/internal.cairo index 1f043d4acef..4f215171d66 100644 --- a/corelib/src/internal.cairo +++ b/corelib/src/internal.cairo @@ -8,3 +8,12 @@ pub extern fn require_implicit() implicits(Implicit) nopanic; extern type index_enum_type; pub(crate) mod bounded_int; + +/// Same as `Option`, except that the order of the variants is reversed. +/// This is used as the return type of some libfuncs for efficiency reasons. +#[must_use] +#[derive(Copy, Drop, Debug, PartialEq)] +pub enum OptionRev { + None, + Some: T, +} 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..254a8436a28 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::IntRangeConcreteLibfunc; 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)] } + IntRange(libfunc) => match libfunc { + IntRangeConcreteLibfunc::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..8c91fcca9b6 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::IntRangeConcreteLibfunc; 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()] } }, + IntRange(libfunc) => match libfunc { + IntRangeConcreteLibfunc::PopFront(_) => { + vec![ConstCost::steps(2).into(), ConstCost::steps(2).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..471a3743591 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), + IntRange(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..c5a700cfbff --- /dev/null +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/range.rs @@ -0,0 +1,44 @@ +use cairo_lang_casm::builder::CasmBuilder; +use cairo_lang_casm::casm_build_extend; +use cairo_lang_sierra::extensions::range::IntRangeConcreteLibfunc; + +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: &IntRangeConcreteLibfunc, + builder: CompiledInvocationBuilder<'_>, +) -> Result { + match libfunc { + IntRangeConcreteLibfunc::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, + const one = 1; + let new_start = start + one; + tempvar is_non_empty = end - start; + jump NonEmpty if is_non_empty != 0; + }; + let non_empty_handle = get_non_fallthrough_statement_id(&builder); + Ok(builder.build_from_casm_builder( + casm_builder, + [ + ("Fallthrough", &[], None), + ("NonEmpty", &[&[new_start, end], &[start]], Some(non_empty_handle)), + ], + Default::default(), + )) +} diff --git a/crates/cairo-lang-sierra/src/extensions/core.rs b/crates/cairo-lang-sierra/src/extensions/core.rs index 6acae8dcaa9..2becc50947c 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::IntRangeType; +use super::range::{IntRangeLibfunc, IntRangeType}; use super::range_check::{RangeCheck96Type, RangeCheckType}; use super::segment_arena::SegmentArenaType; use super::snapshot::{SnapshotTakeLibfunc, SnapshotType}; @@ -65,6 +65,7 @@ define_type_hierarchy! { EcState(EcStateType), Felt252(Felt252Type), GasBuiltin(GasBuiltinType), + IntRange(IntRangeType), BuiltinCosts(BuiltinCostsType), Uint8(Uint8Type), Uint16(Uint16Type), @@ -95,7 +96,6 @@ define_type_hierarchy! { Snapshot(SnapshotType), Bytes31(Bytes31Type), BoundedInt(BoundedIntType), - IntRange(IntRangeType), }, CoreTypeConcrete } @@ -117,6 +117,7 @@ define_libfunc_hierarchy! { Const(ConstLibfunc), FunctionCall(FunctionCallLibfunc), Gas(GasLibfunc), + IntRange(IntRangeLibfunc), Uint8(Uint8Libfunc), Uint16(Uint16Libfunc), Uint32(Uint32Libfunc), diff --git a/crates/cairo-lang-sierra/src/extensions/modules/range.rs b/crates/cairo-lang-sierra/src/extensions/modules/range.rs index 5e8597ba419..e9b80541375 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 IntRangeTypeWrapped { } } pub type IntRangeType = GenericTypeArgGenericTypeWrapper; + +define_libfunc_hierarchy! { + pub enum IntRangeLibfunc { + PopFront(IntRangePopFrontLibfunc), + }, IntRangeConcreteLibfunc +} + +/// 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 IntRangePopFrontLibfunc {} +impl SignatureOnlyGenericLibfunc for IntRangePopFrontLibfunc { + const STR_ID: &'static str = "int_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(IntRangeType::id(), ty.clone())?; + + Ok(LibfuncSignature { + param_signatures: vec![ParamSignature::new(range_ty.clone())], + branch_signatures: vec![ + // Failure. + BranchSignature { + vars: vec![], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + // 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 }, + }, + ], + 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..bcf2e539149 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::IntRange(_) => 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..7c9057bb41a --- /dev/null +++ b/tests/e2e_test_data/libfuncs/range @@ -0,0 +1,60 @@ +//! > int_range_pop_front libfunc + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +use core::internal::OptionRev; +// TODO(lior): Move to `range.cairo`. +extern type IntRange; +extern fn int_range_pop_front(range: IntRange) -> OptionRev<(IntRange, T)> nopanic; + +fn foo(v: IntRange) -> OptionRev<(IntRange, i16)> { + int_range_pop_front(v) +} + +//! > casm +[fp + -3] = [ap + 0] + [fp + -4], ap++; +jmp rel 11 if [ap + -1] != 0; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +[ap + 0] = 0, ap++; +ret; +[ap + 0] = 1, ap++; +[ap + 0] = [fp + -4] + 1, ap++; +[ap + 0] = [fp + -3], ap++; +[ap + 0] = [fp + -4], ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 600}) + +//! > sierra_code +type IntRange = IntRange [storable: true, drop: true, dup: true, zero_sized: false]; +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 Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type core::internal::OptionRev::<(test::IntRange::, core::integer::i16)> = Enum, core::integer::i16)>, Unit, Tuple, i16>> [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc int_range_pop_front = int_range_pop_front; +libfunc branch_align = branch_align; +libfunc struct_construct = struct_construct; +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, i16>> = struct_construct, i16>>; +libfunc enum_init, core::integer::i16)>, 1> = enum_init, core::integer::i16)>, 1>; + +int_range_pop_front([0]) { fallthrough() 6([1], [2]) }; // 0 +branch_align() -> (); // 1 +struct_construct() -> ([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, i16>>([1], [2]) -> ([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]: IntRange) -> (core::internal::OptionRev::<(test::IntRange::, core::integer::i16)>);