From 7814babd8c453dff7340e2ad0750cd1e8611f0ab Mon Sep 17 00:00:00 2001 From: Ori Ziv Date: Sun, 19 May 2024 10:43:58 +0300 Subject: [PATCH] Added conversion from span to fixed sized array. commit-id:78f53cb7 --- .../src/core_libfunc_ap_change.rs | 1 + .../src/core_libfunc_cost_base.rs | 3 + .../src/invocations/array.rs | 34 +++++++- .../src/extensions/modules/array.rs | 77 +++++++++++++++---- .../cairo-lang-sierra/src/simulation/core.rs | 1 + .../src/allowed_libfuncs_lists/all.json | 1 + tests/e2e_test_data/libfuncs/array | 57 ++++++++++++++ 7 files changed, 156 insertions(+), 18 deletions(-) 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 1eca7203084..0f4783d581b 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 @@ -71,6 +71,7 @@ pub fn core_libfunc_ap_change( Array(libfunc) => match libfunc { ArrayConcreteLibfunc::New(_) => vec![ApChange::Known(1)], ArrayConcreteLibfunc::SpanFromTuple(_) => vec![ApChange::Known(0)], + ArrayConcreteLibfunc::TupleFromSpan(_) => vec![ApChange::Known(2), ApChange::Known(2)], ArrayConcreteLibfunc::Append(_) => vec![ApChange::Known(0)], ArrayConcreteLibfunc::PopFront(_) | ArrayConcreteLibfunc::PopFrontConsume(_) 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 3409c0873aa..badd51211e0 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 @@ -246,6 +246,9 @@ pub fn core_libfunc_cost( Array(libfunc) => match libfunc { ArrayConcreteLibfunc::New(_) => vec![ConstCost::steps(1).into()], ArrayConcreteLibfunc::SpanFromTuple(_) => vec![ConstCost::steps(0).into()], + ArrayConcreteLibfunc::TupleFromSpan(_) => { + vec![ConstCost::steps(3).into(), ConstCost::steps(3).into()] + } ArrayConcreteLibfunc::Append(libfunc) => { vec![ConstCost::steps(info_provider.type_size(&libfunc.ty) as i32).into()] } diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/array.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/array.rs index 9b9ac6ffb8e..492bbed79d5 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/array.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/array.rs @@ -16,6 +16,7 @@ pub fn build( match libfunc { ArrayConcreteLibfunc::New(_) => build_array_new(builder), ArrayConcreteLibfunc::SpanFromTuple(libfunc) => build_span_from_tuple(builder, &libfunc.ty), + ArrayConcreteLibfunc::TupleFromSpan(libfunc) => build_tuple_from_span(builder, &libfunc.ty), ArrayConcreteLibfunc::Append(_) => build_array_append(builder), ArrayConcreteLibfunc::PopFront(libfunc) | ArrayConcreteLibfunc::SnapshotPopFront(libfunc) => { @@ -51,8 +52,8 @@ fn build_array_new( )) } -// Builds instructions for converting a box of a struct containing only the same type as members -// into a span of that type. +/// Builds instructions for converting a box of a struct containing only the same type as members +/// into a span of that type. fn build_span_from_tuple( builder: CompiledInvocationBuilder<'_>, ty: &ConcreteTypeId, @@ -78,6 +79,35 @@ fn build_span_from_tuple( )) } +/// Builds instructions for converting a span of a type into a box of a struct containing only +/// members of that same type. +fn build_tuple_from_span( + builder: CompiledInvocationBuilder<'_>, + ty: &ConcreteTypeId, +) -> Result { + let [arr_start, arr_end] = builder.try_get_refs::<1>()?[0].try_unpack()?; + let full_struct_size = builder.program_info.type_sizes[ty]; + + let mut casm_builder = CasmBuilder::default(); + + add_input_variables! {casm_builder, + deref arr_start; + deref arr_end; + }; + casm_build_extend! {casm_builder, + const success_span_size = full_struct_size; + tempvar actual_length = arr_end - arr_start; + tempvar diff = actual_length - success_span_size; + jump Failure if diff != 0; + }; + let failure_handle = get_non_fallthrough_statement_id(&builder); + Ok(builder.build_from_casm_builder( + casm_builder, + [("Fallthrough", &[&[arr_start]], None), ("Failure", &[], Some(failure_handle))], + Default::default(), + )) +} + /// Handles a Sierra statement for appending an element to an array. fn build_array_append( builder: CompiledInvocationBuilder<'_>, diff --git a/crates/cairo-lang-sierra/src/extensions/modules/array.rs b/crates/cairo-lang-sierra/src/extensions/modules/array.rs index 912d4117501..2c2aa19bfe6 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/array.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/array.rs @@ -51,6 +51,7 @@ define_libfunc_hierarchy! { pub enum ArrayLibfunc { New(ArrayNewLibfunc), SpanFromTuple(SpanFromTupleLibfunc), + TupleFromSpan(TupleFromSpanLibfunc), Append(ArrayAppendLibfunc), PopFront(ArrayPopFrontLibfunc), PopFrontConsume(ArrayPopFrontConsumeLibfunc), @@ -96,24 +97,10 @@ impl SignatureAndTypeGenericLibfunc for SpanFromTupleLibfuncWrapped { context: &dyn SignatureSpecializationContext, ty: ConcreteTypeId, ) -> Result { - let struct_type = StructConcreteType::try_from_concrete_type(context, &ty)?; - if struct_type.info.zero_sized { - return Err(SpecializationError::UnsupportedGenericArg); - } - - let member_type = - struct_type.members.first().ok_or(SpecializationError::UnsupportedGenericArg)?; - // Validate all members are of the same type. - for member in &struct_type.members { - if member != member_type { - return Err(SpecializationError::UnsupportedGenericArg); - } - } - - let input_ty = box_ty(context, snapshot_ty(context, ty)?)?; + let member_type = validate_tuple_and_fetch_ty(context, &ty)?; Ok(LibfuncSignature::new_non_branch( - vec![input_ty], + vec![box_ty(context, snapshot_ty(context, ty)?)?], vec![OutputVarInfo { ty: snapshot_ty( context, @@ -130,6 +117,64 @@ impl SignatureAndTypeGenericLibfunc for SpanFromTupleLibfuncWrapped { pub type SpanFromTupleLibfunc = WrapSignatureAndTypeGenericLibfunc; +/// Libfunc for creating a box of struct of members of the same type from a span. +#[derive(Default)] +pub struct TupleFromSpanLibfuncWrapped; +impl SignatureAndTypeGenericLibfunc for TupleFromSpanLibfuncWrapped { + const STR_ID: &'static str = "tuple_from_span"; + + fn specialize_signature( + &self, + context: &dyn SignatureSpecializationContext, + ty: ConcreteTypeId, + ) -> Result { + let member_type = validate_tuple_and_fetch_ty(context, &ty)?; + + Ok(LibfuncSignature { + param_signatures: vec![ParamSignature::new(snapshot_ty( + context, + context.get_wrapped_concrete_type(ArrayType::id(), member_type)?, + )?)], + branch_signatures: vec![ + BranchSignature { + vars: vec![OutputVarInfo { + ty: snapshot_ty(context, box_ty(context, ty)?)?, + ref_info: OutputVarReferenceInfo::PartialParam { param_idx: 0 }, + }], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + BranchSignature { + vars: vec![], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + ], + fallthrough: Some(0), + }) + } +} + +/// Validates that the given type is a tuple with all members of the same type, and returns the type +/// of the members. +fn validate_tuple_and_fetch_ty( + context: &dyn SignatureSpecializationContext, + ty: &ConcreteTypeId, +) -> Result { + let struct_type = StructConcreteType::try_from_concrete_type(context, ty)?; + if struct_type.info.zero_sized { + return Err(SpecializationError::UnsupportedGenericArg); + } + let mut members = struct_type.members.into_iter(); + let member_type = members.next().ok_or(SpecializationError::UnsupportedGenericArg)?; + for member in members { + if member != member_type { + return Err(SpecializationError::UnsupportedGenericArg); + } + } + Ok(member_type) +} + +pub type TupleFromSpanLibfunc = WrapSignatureAndTypeGenericLibfunc; + /// Libfunc for getting the length of the array. #[derive(Default)] pub struct ArrayLenLibfuncWrapped {} diff --git a/crates/cairo-lang-sierra/src/simulation/core.rs b/crates/cairo-lang-sierra/src/simulation/core.rs index 8b3db15a869..4fecdd96b06 100644 --- a/crates/cairo-lang-sierra/src/simulation/core.rs +++ b/crates/cairo-lang-sierra/src/simulation/core.rs @@ -147,6 +147,7 @@ pub fn simulate< } } Array(ArrayConcreteLibfunc::SpanFromTuple(_)) => todo!(), + Array(ArrayConcreteLibfunc::TupleFromSpan(_)) => todo!(), Array(ArrayConcreteLibfunc::Append(_)) => match &inputs[..] { [CoreValue::Array(_), _] => { let mut iter = inputs.into_iter(); 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 2770aacfd10..159d35b5a7d 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 @@ -169,6 +169,7 @@ "struct_construct", "struct_deconstruct", "struct_snapshot_deconstruct", + "tuple_from_span", "u128_byte_reverse", "u128_const", "u128_eq", diff --git a/tests/e2e_test_data/libfuncs/array b/tests/e2e_test_data/libfuncs/array index 17cf5956d1d..480a3f6f663 100644 --- a/tests/e2e_test_data/libfuncs/array +++ b/tests/e2e_test_data/libfuncs/array @@ -890,3 +890,60 @@ store_temp>>([1]) -> ([1]); // 1 return([1]); // 2 test::foo@0([0]: Box>) -> (Snapshot>); + +//! > ========================================================================== + +//! > tuple_from_span libfunc + +//! > test_runner_name +SmallE2ETestRunner + +//! > cairo +extern fn tuple_from_span(span: @Array) -> Option<@Box> nopanic; + +fn foo(span: @Array) -> Option<@Box<(felt252, felt252, felt252)>> { + tuple_from_span(span) +} + +//! > casm +[fp + -3] = [ap + 0] + [fp + -4], ap++; +[ap + -1] = [ap + 0] + 3, ap++; +jmp rel 6 if [ap + -1] != 0; +[ap + 0] = 0, ap++; +[ap + 0] = [fp + -4], ap++; +ret; +[ap + 0] = 1, ap++; +[ap + 0] = 0, ap++; +ret; + +//! > function_costs +test::foo: OrderedHashMap({Const: 500}) + +//! > sierra_code +type Array = Array [storable: true, drop: true, dup: false, zero_sized: false]; +type Snapshot> = Snapshot> [storable: true, drop: true, dup: true, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; +type Box> = Box> [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option::<@core::box::Box::<(core::felt252, core::felt252, core::felt252)>> = Enum>, Box>, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type Tuple = Struct [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc tuple_from_span> = tuple_from_span>; +libfunc branch_align = branch_align; +libfunc enum_init>, 0> = enum_init>, 0>; +libfunc store_temp>> = store_temp>>; +libfunc struct_construct = struct_construct; +libfunc enum_init>, 1> = enum_init>, 1>; + +tuple_from_span>([0]) { fallthrough([1]) 5() }; // 0 +branch_align() -> (); // 1 +enum_init>, 0>([1]) -> ([2]); // 2 +store_temp>>([2]) -> ([2]); // 3 +return([2]); // 4 +branch_align() -> (); // 5 +struct_construct() -> ([3]); // 6 +enum_init>, 1>([3]) -> ([4]); // 7 +store_temp>>([4]) -> ([4]); // 8 +return([4]); // 9 + +test::foo@0([0]: Snapshot>) -> (core::option::Option::<@core::box::Box::<(core::felt252, core::felt252, core::felt252)>>);