diff --git a/crates/cairo-lang-sierra-generator/src/block_generator.rs b/crates/cairo-lang-sierra-generator/src/block_generator.rs index 72f16e43052..0152bc0c9d5 100644 --- a/crates/cairo-lang-sierra-generator/src/block_generator.rs +++ b/crates/cairo-lang-sierra-generator/src/block_generator.rs @@ -3,7 +3,9 @@ mod test; use cairo_lang_diagnostics::Maybe; +use cairo_lang_filesystem::flag::flag_future_sierra; use cairo_lang_lowering::BlockId; +use cairo_lang_lowering::db::LoweringGroup; use cairo_lang_lowering::ids::LocationId; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use itertools::{chain, enumerate, zip_eq}; @@ -17,6 +19,7 @@ use crate::block_generator::sierra::ids::ConcreteLibfuncId; use crate::db::SierraGenGroup; use crate::expr_generator_context::{ExprGenerationResult, ExprGeneratorContext}; use crate::lifetime::{DropLocation, SierraGenVar, UseLocation}; +use crate::local_variables::MIN_SIZE_FOR_LOCAL_INTO_BOX; use crate::pre_sierra::{self, StatementWithLocation}; use crate::replace_ids::{DebugReplacer, SierraIdReplacer}; use crate::utils::{ @@ -24,9 +27,9 @@ use crate::utils::{ drop_libfunc_id, dup_libfunc_id, enable_ap_tracking_libfunc_id, enum_from_bounded_int_libfunc_id, enum_init_libfunc_id, get_concrete_libfunc_id, get_libfunc_signature, into_box_libfunc_id, jump_libfunc_id, jump_statement, - match_enum_libfunc_id, rename_libfunc_id, return_statement, simple_basic_statement, - snapshot_take_libfunc_id, struct_construct_libfunc_id, struct_deconstruct_libfunc_id, - unbox_libfunc_id, + local_into_box_libfunc_id, match_enum_libfunc_id, rename_libfunc_id, return_statement, + simple_basic_statement, snapshot_take_libfunc_id, struct_construct_libfunc_id, + struct_deconstruct_libfunc_id, unbox_libfunc_id, }; /// Generates Sierra code for the body of the given [lowering::Block]. @@ -564,11 +567,20 @@ fn generate_statement_into_box<'db>( statement_location: &StatementLocation, ) -> Maybe<()> { let input = maybe_add_dup_statement(context, statement_location, 0, &statement.input)?; + let var_id = statement.input.var_id; + let ty = context.get_variable_sierra_type(var_id)?; + let db = context.get_db(); + + let use_local_into_box = flag_future_sierra(db) + && context.is_fp_relative(var_id) + && db.type_size(context.get_lowered_variable(var_id).ty) >= MIN_SIZE_FOR_LOCAL_INTO_BOX; + let libfunc_id = if use_local_into_box { + local_into_box_libfunc_id(db, ty) + } else { + into_box_libfunc_id(db, ty) + }; let stmt = simple_basic_statement( - into_box_libfunc_id( - context.get_db(), - context.get_variable_sierra_type(statement.input.var_id)?, - ), + libfunc_id, &[input], &[context.get_sierra_variable(statement.output)], ); diff --git a/crates/cairo-lang-sierra-generator/src/block_generator_test.rs b/crates/cairo-lang-sierra-generator/src/block_generator_test.rs index ae246926dca..d92bfee354b 100644 --- a/crates/cairo-lang-sierra-generator/src/block_generator_test.rs +++ b/crates/cairo-lang-sierra-generator/src/block_generator_test.rs @@ -79,6 +79,7 @@ fn block_generator_test( function_id, &lifetime, crate::ap_tracking::ApTrackingConfiguration::default(), + Default::default(), ); let mut expected_sierra_code = String::default(); diff --git a/crates/cairo-lang-sierra-generator/src/expr_generator_context.rs b/crates/cairo-lang-sierra-generator/src/expr_generator_context.rs index 7a4919f082b..e4361bedc84 100644 --- a/crates/cairo-lang-sierra-generator/src/expr_generator_context.rs +++ b/crates/cairo-lang-sierra-generator/src/expr_generator_context.rs @@ -14,6 +14,7 @@ use crate::ap_tracking::ApTrackingConfiguration; use crate::db::SierraGenGroup; use crate::id_allocator::IdAllocator; use crate::lifetime::{DropLocation, SierraGenVar, UseLocation, VariableLifetimeResult}; +use crate::local_variables::VariablesInfo; use crate::pre_sierra; /// Context for the methods that generate Sierra instructions for an expression. @@ -26,6 +27,7 @@ pub struct ExprGeneratorContext<'db, 'a> { var_id_allocator: IdAllocator, label_id_allocator: IdAllocator, variables: OrderedHashMap, + variables_info: VariablesInfo, /// Allocated Sierra variables and their locations. variable_locations: Vec<(cairo_lang_sierra::ids::VarId, LocationId<'db>)>, block_labels: OrderedHashMap>, @@ -48,6 +50,7 @@ impl<'db, 'a> ExprGeneratorContext<'db, 'a> { function_id: ConcreteFunctionWithBodyId<'db>, lifetime: &'a VariableLifetimeResult, ap_tracking_configuration: ApTrackingConfiguration, + variables_info: VariablesInfo, ) -> Self { ExprGeneratorContext { db, @@ -61,6 +64,7 @@ impl<'db, 'a> ExprGeneratorContext<'db, 'a> { block_labels: OrderedHashMap::default(), ap_tracking_enabled: true, ap_tracking_configuration, + variables_info, statements: vec![], curr_cairo_location: None, } @@ -189,6 +193,17 @@ impl<'db, 'a> ExprGeneratorContext<'db, 'a> { && self.ap_tracking_configuration.disable_ap_tracking.contains(block_id) } + /// Returns true if the variable is at a known fp-relative location (local, parameter, or alias + /// of one). + pub fn is_fp_relative(&self, var: VariableId) -> bool { + self.variables_info.fp_relative_variables.contains(&var) + } + + /// Returns the lowered variable for the given variable id. + pub fn get_lowered_variable(&self, var_id: VariableId) -> &lowering::Variable<'db> { + &self.lowered.variables[var_id] + } + /// Adds a statement for the expression. pub fn push_statement(&mut self, statement: pre_sierra::Statement<'db>) { self.statements.push(pre_sierra::StatementWithLocation { diff --git a/crates/cairo-lang-sierra-generator/src/function_generator.rs b/crates/cairo-lang-sierra-generator/src/function_generator.rs index e14cb3a2373..e2758a07808 100644 --- a/crates/cairo-lang-sierra-generator/src/function_generator.rs +++ b/crates/cairo-lang-sierra-generator/src/function_generator.rs @@ -65,10 +65,11 @@ fn get_function_ap_change_and_code<'db>( analyze_ap_change_result: AnalyzeApChangesResult, ) -> Maybe> { let root_block = lowered_function.blocks.root_block()?; - let AnalyzeApChangesResult { known_ap_change, local_variables, ap_tracking_configuration } = + let AnalyzeApChangesResult { known_ap_change, variables_info, ap_tracking_configuration } = analyze_ap_change_result; // Get lifetime information. + let local_variables = variables_info.local_variables.clone(); let lifetime = find_variable_lifetime(lowered_function, &local_variables)?; let mut context = ExprGeneratorContext::new( @@ -77,6 +78,7 @@ fn get_function_ap_change_and_code<'db>( function_id, &lifetime, ap_tracking_configuration, + variables_info, ); // If the function starts with `revoke_ap_tracking` then we can avoid @@ -159,6 +161,7 @@ pub fn priv_get_dummy_function<'db>( function_id, &lifetime, ap_tracking_configuration, + Default::default(), ); // Generate a label for the function's body. diff --git a/crates/cairo-lang-sierra-generator/src/function_generator_test.rs b/crates/cairo-lang-sierra-generator/src/function_generator_test.rs index 3df2a24fa6b..8a7d126b1a0 100644 --- a/crates/cairo-lang-sierra-generator/src/function_generator_test.rs +++ b/crates/cairo-lang-sierra-generator/src/function_generator_test.rs @@ -4,6 +4,7 @@ cairo_lang_test_utils::test_file_test!( function_generator, "src/function_generator_test_data", { + boxing: "boxing", inline: "inline", struct_: "struct", match_: "match", diff --git a/crates/cairo-lang-sierra-generator/src/function_generator_test_data/boxing b/crates/cairo-lang-sierra-generator/src/function_generator_test_data/boxing new file mode 100644 index 00000000000..493f33719b4 --- /dev/null +++ b/crates/cairo-lang-sierra-generator/src/function_generator_test_data/boxing @@ -0,0 +1,297 @@ +//! > Test local_into_box for large struct parameter (size >= 3). + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo(a: MyStruct) -> Box { + BoxTrait::new(a) +} + +//! > function_name +foo + +//! > module_code +struct MyStruct { + x: felt252, + y: felt252, + z: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +local_into_box([0]) -> ([1]) +store_temp>([1]) -> ([1]) +return([1]) + +//! > ========================================================================== + +//! > Test into_box for small struct parameter (size < 3). + +//! > test_runner_name +test_function_generator + +//! > function_code +fn foo(a: SmallStruct) -> Box { + BoxTrait::new(a) +} + +//! > function_name +foo + +//! > module_code +struct SmallStruct { + x: felt252, + y: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +into_box([0]) -> ([1]) +return([1]) + +//! > ========================================================================== + +//! > Test chained boxing: inner uses local_into_box, outer uses into_box (Box has size 1). + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo(a: MyStruct) -> Box> { + BoxTrait::new(BoxTrait::new(a)) +} + +//! > function_name +foo + +//! > module_code +struct MyStruct { + x: felt252, + y: felt252, + z: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +local_into_box([0]) -> ([1]) +store_temp>([1]) -> ([1]) +into_box>([1]) -> ([2]) +return([2]) + +//! > ========================================================================== + +//! > Test local_into_box for ap-based variable that becomes local due to size. + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo() -> Box { + let x = create_struct(); + BoxTrait::new(x) +} + +//! > function_name +foo + +//! > module_code +#[derive(Drop)] +struct MyStruct { + x: felt252, + y: felt252, + z: felt252, +} + +#[inline(never)] +fn create_struct() -> MyStruct { + MyStruct { x: 1, y: 2, z: 3 } +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +alloc_local() -> ([1]) +finalize_locals() -> () +function_call() -> ([0]) +store_local([1], [0]) -> ([0]) +local_into_box([0]) -> ([2]) +store_temp>([2]) -> ([2]) +return([2]) + +//! > ========================================================================== + +//! > Test local_into_box for snapshot of parameter (alias is fp-relative). + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo(a: MyStruct) -> Box<@MyStruct> { + BoxTrait::new(@a) +} + +//! > function_name +foo + +//! > module_code +#[derive(Drop)] +struct MyStruct { + x: felt252, + y: felt252, + z: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +snapshot_take([0]) -> ([1], [2]) +drop([1]) -> () +local_into_box([2]) -> ([3]) +store_temp>([3]) -> ([3]) +return([3]) + +//! > ========================================================================== + +//! > Test const_as_box for inline struct construction with constants. + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo() -> Box { + BoxTrait::new(MyStruct { x: 1, y: 2, z: 3 }) +} + +//! > function_name +foo + +//! > module_code +#[derive(Drop)] +struct MyStruct { + x: felt252, + y: felt252, + z: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +const_as_box, Const, Const>, 0>() -> ([0]) +return([0]) + +//! > ========================================================================== + +//! > Test local_into_box for struct field extracted from parameter (PartialParam). + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo(s: Wrapper) -> Box { + BoxTrait::new(s.inner) +} + +//! > function_name +foo + +//! > module_code +struct Wrapper { + inner: Inner, +} + +struct Inner { + a: felt252, + b: felt252, + c: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +struct_deconstruct([0]) -> ([1]) +local_into_box([1]) -> ([2]) +store_temp>([2]) -> ([2]) +return([2]) + +//! > ========================================================================== + +//! > Test local_into_box for nested struct field (PartialParam chain). + +//! > test_runner_name +test_function_generator(future_sierra:true) + +//! > function_code +fn foo(s: Outer) -> Box { + BoxTrait::new(s.mid.inner) +} + +//! > function_name +foo + +//! > module_code +struct Outer { + mid: Mid, +} + +struct Mid { + inner: Inner, +} + +struct Inner { + a: felt252, + b: felt252, + c: felt252, +} + +//! > semantic_diagnostics + +//! > lowering_diagnostics + +//! > sierra_gen_diagnostics + +//! > sierra_code +label_test::foo::0: +struct_deconstruct([0]) -> ([1]) +struct_deconstruct([1]) -> ([2]) +local_into_box([2]) -> ([3]) +store_temp>([3]) -> ([3]) +return([3]) diff --git a/crates/cairo-lang-sierra-generator/src/lifetime_test.rs b/crates/cairo-lang-sierra-generator/src/lifetime_test.rs index 0d1b5013ef8..e45084c0057 100644 --- a/crates/cairo-lang-sierra-generator/src/lifetime_test.rs +++ b/crates/cairo-lang-sierra-generator/src/lifetime_test.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use lowering::ids::ConcreteFunctionWithBodyId; use super::find_variable_lifetime; -use crate::local_variables::{AnalyzeApChangesResult, analyze_ap_changes}; +use crate::local_variables::analyze_ap_changes; use crate::test_utils::SierraGenDatabaseForTesting; cairo_lang_test_utils::test_file_test!( @@ -49,8 +49,8 @@ fn check_variable_lifetime( let lowered_formatter = lowering::fmt::LoweredFormatter::new(db, &lowered_function.variables); let lowered_str = format!("{:?}", lowered_function.debug(&lowered_formatter)); - let AnalyzeApChangesResult { known_ap_change: _, local_variables, .. } = - analyze_ap_changes(db, lowered_function).unwrap(); + let local_variables = + analyze_ap_changes(db, lowered_function).unwrap().variables_info.local_variables; let find_variable_lifetime_res = find_variable_lifetime(lowered_function, &local_variables) .expect("find_variable_lifetime failed unexpectedly"); let last_use_str = find_variable_lifetime_res diff --git a/crates/cairo-lang-sierra-generator/src/local_variables.rs b/crates/cairo-lang-sierra-generator/src/local_variables.rs index 932dda947fd..c1540700195 100644 --- a/crates/cairo-lang-sierra-generator/src/local_variables.rs +++ b/crates/cairo-lang-sierra-generator/src/local_variables.rs @@ -3,7 +3,9 @@ mod test; use cairo_lang_diagnostics::Maybe; +use cairo_lang_filesystem::flag::flag_future_sierra; use cairo_lang_lowering as lowering; +use cairo_lang_lowering::db::LoweringGroup; use cairo_lang_lowering::{BlockId, VariableId}; use cairo_lang_semantic::items::constant::ConstValue; use cairo_lang_sierra::extensions::OutputVarReferenceInfo; @@ -29,12 +31,15 @@ use crate::utils::{ struct_construct_libfunc_id, struct_deconstruct_libfunc_id, }; +/// Minimum type size for which `local_into_box` is more efficient than `into_box`. +pub const MIN_SIZE_FOR_LOCAL_INTO_BOX: usize = 3; + /// Information returned by [analyze_ap_changes]. pub struct AnalyzeApChangesResult { - /// True if the function has a known_ap_change + /// True if the function has a known_ap_change. pub known_ap_change: bool, - /// The variables that should be stored in locals as they are revoked during the function. - pub local_variables: OrderedHashSet, + /// Information about variables. + pub variables_info: VariablesInfo, /// Information about where ap tracking should be enabled and disabled. pub ap_tracking_configuration: ApTrackingConfiguration, } @@ -49,7 +54,7 @@ pub fn analyze_ap_changes<'db>( let ctx = FindLocalsContext { db, lowered_function, - used_after_revoke: Default::default(), + local_candidates: Default::default(), block_callers: Default::default(), non_ap_based: UnorderedHashSet::from_iter(chain!( // Parameters are not ap based. @@ -69,16 +74,16 @@ pub fn analyze_ap_changes<'db>( root_info.demand.variables_introduced(&mut analysis.analyzer, &lowered_function.parameters, ()); let mut ctx = analysis.analyzer; - let peeled_used_after_revoke: OrderedHashSet<_> = - ctx.used_after_revoke.iter().map(|var| ctx.peel_aliases(var)).copied().collect(); - // Any used after revoke variable that might be revoked should be a local. + let peeled_local_candidates: OrderedHashSet<_> = + ctx.local_candidates.iter().map(|var| *ctx.peel_aliases(var)).collect(); + // Filter candidates to those that actually need local storage. let locals: OrderedHashSet = ctx - .used_after_revoke + .local_candidates .iter() - .filter(|var| ctx.might_be_revoked(&peeled_used_after_revoke, var)) - .map(|var| ctx.peel_aliases(var)) - .cloned() + .filter(|var| ctx.needs_local(&peeled_local_candidates, var)) + .map(|var| *ctx.peel_aliases(var)) .collect(); + let mut need_ap_alignment = OrderedHashSet::default(); if !root_info.known_ap_change { // Add 'locals' to the set a variable that is not ap based. @@ -91,16 +96,23 @@ pub fn analyze_ap_changes<'db>( } info.demand.variables_introduced(&mut ctx, &info.introduced_vars, ()); for var in info.demand.vars.keys() { - if ctx.might_be_revoked(&peeled_used_after_revoke, var) { + if ctx.needs_local(&peeled_local_candidates, var) { need_ap_alignment.insert(*var); } } } } + let fp_relative_variables = lowered_function + .variables + .iter() + .map(|(id, _)| id) + .filter(|&var| ctx.is_fp_relative(var, &locals)) + .collect(); + Ok(AnalyzeApChangesResult { known_ap_change: root_info.known_ap_change, - local_variables: locals, + variables_info: VariablesInfo { local_variables: locals, fp_relative_variables }, ap_tracking_configuration: get_ap_tracking_configuration( lowered_function, root_info.known_ap_change, @@ -109,6 +121,16 @@ pub fn analyze_ap_changes<'db>( }) } +/// Information about variable storage and fp-relative status. +#[derive(Default)] +pub struct VariablesInfo { + /// The variables that should be stored as locals, if they were either revoked during the + /// function or large enough to benefit from `local_into_box`. + pub local_variables: OrderedHashSet, + /// Variables at known fp-relative locations (locals, parameters, or derived from them). + pub fp_relative_variables: UnorderedHashSet, +} + struct CalledBlockInfo { caller_count: usize, demand: LoweredDemand, @@ -119,14 +141,15 @@ struct CalledBlockInfo { struct FindLocalsContext<'db, 'a> { db: &'db dyn Database, lowered_function: &'a Lowered<'db>, - used_after_revoke: OrderedHashSet, + /// Candidates for local storage (used after revoke or large IntoBox inputs). + local_candidates: OrderedHashSet, block_callers: OrderedHashMap, /// Variables that are known not to be ap based, excluding constants. non_ap_based: UnorderedHashSet, /// Variables that are constants, i.e. created from Statement::Literal. constants: UnorderedHashSet, /// A mapping of variables which are the same in the context of finding locals. - /// I.e. if `aliases[var_id]` is local than var_id is also local. + /// I.e. if `aliases[var_id]` is local then var_id is also local. aliases: UnorderedHashMap, /// A mapping from partial param variables to the containing variable. partial_param_parents: UnorderedHashMap, @@ -248,14 +271,14 @@ impl<'db, 'a> FindLocalsContext<'db, 'a> { var } - /// Return true if the variable might be revoked by ap changes. + /// Return true if the variable needs to be local. /// Checks if the variable is a constant or is contained in a local variable. /// - /// Note that vars in `peeled_used_after_revoke` are going to be non-ap based once we make the + /// Note that vars in `peeled_local_candidates` are going to be non-ap based once we make the /// relevant variables local. - pub fn might_be_revoked( - &self, - peeled_used_after_revoke: &OrderedHashSet, + pub fn needs_local( + &'a self, + peeled_local_candidates: &OrderedHashSet, var: &VariableId, ) -> bool { if self.constants.contains(var) { @@ -266,17 +289,33 @@ impl<'db, 'a> FindLocalsContext<'db, 'a> { return false; } // In the case of partial params, we check if one of its ancestors is a local variable, or - // will be used after the revoke, and thus will be used as a local variable. If that - // is the case, then 'var' cannot be revoked. + // will become a local variable. If that is the case, then 'var' doesn't need to be local. while let Some(parent) = self.partial_param_parents.get(peeled) { peeled = self.peel_aliases(parent); - if self.non_ap_based.contains(peeled) || peeled_used_after_revoke.contains(peeled) { + if self.non_ap_based.contains(peeled) || peeled_local_candidates.contains(peeled) { return false; } } true } + /// Returns true if the variable is at a known fp-relative location. + /// A variable is fp-relative if it's a local, parameter, or reachable via aliases/partial_param + /// chains to one. + fn is_fp_relative(&self, var: VariableId, locals: &OrderedHashSet) -> bool { + let mut current = Some(var); + while let Some(v) = current { + // Peel aliases. + let peeled = *self.peel_aliases(&v); + if locals.contains(&peeled) || self.lowered_function.parameters.contains(&peeled) { + return true; + } + // Walk up the partial param parent chain. + current = self.partial_param_parents.get(&peeled).copied(); + } + false + } + fn analyze_call( &mut self, concrete_function_id: cairo_lang_sierra::ids::ConcreteLibfuncId, @@ -395,7 +434,16 @@ impl<'db, 'a> FindLocalsContext<'db, 'a> { self.aliases.insert(statement_desnap.output, statement_desnap.input.var_id); BranchInfo { known_ap_change: true } } - lowering::Statement::IntoBox(_) => BranchInfo { known_ap_change: true }, + lowering::Statement::IntoBox(statement_into_box) => { + if flag_future_sierra(self.db) { + let input_var = statement_into_box.input.var_id; + let ty = self.lowered_function.variables[input_var].ty; + if self.db.type_size(ty) >= MIN_SIZE_FOR_LOCAL_INTO_BOX { + self.local_candidates.insert(input_var); + } + } + BranchInfo { known_ap_change: true } + } lowering::Statement::Unbox(_) => BranchInfo { known_ap_change: true }, }; Ok(branch_info) @@ -405,9 +453,9 @@ impl<'db, 'a> FindLocalsContext<'db, 'a> { // Revoke if needed. if !branch_info.known_ap_change { info.known_ap_change = false; - // Revoke all demanded variables. + // Add all demanded variables as local candidates. for var in info.demand.vars.keys() { - self.used_after_revoke.insert(*var); + self.local_candidates.insert(*var); } } } diff --git a/crates/cairo-lang-sierra-generator/src/local_variables_test.rs b/crates/cairo-lang-sierra-generator/src/local_variables_test.rs index f74485dafd9..36c05ba6412 100644 --- a/crates/cairo-lang-sierra-generator/src/local_variables_test.rs +++ b/crates/cairo-lang-sierra-generator/src/local_variables_test.rs @@ -7,8 +7,8 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use itertools::Itertools; use lowering::ids::ConcreteFunctionWithBodyId; -use super::AnalyzeApChangesResult; use crate::function_generator_test_utils::test_function_generator; +use crate::local_variables::analyze_ap_changes; use crate::test_utils::SierraGenDatabaseForTesting; cairo_lang_test_utils::test_file_test!( @@ -49,11 +49,13 @@ fn check_find_local_variables( let lowered_formatter = lowering::fmt::LoweredFormatter::new(db, &lowered_function.variables); let lowered_str = format!("{:?}", lowered_function.debug(&lowered_formatter)); - let AnalyzeApChangesResult { known_ap_change: _, local_variables, .. } = - super::analyze_ap_changes(db, lowered_function).unwrap(); - - let local_variables_str = - local_variables.iter().map(|var_id| format!("v{:?}", var_id.index())).join(", "); + let local_variables_str = analyze_ap_changes(db, lowered_function) + .unwrap() + .variables_info + .local_variables + .iter() + .map(|var_id| format!("v{:?}", var_id.index())) + .join(", "); TestRunnerResult::success(OrderedHashMap::from([ ("lowering_format".into(), lowered_str), diff --git a/crates/cairo-lang-sierra-generator/src/utils.rs b/crates/cairo-lang-sierra-generator/src/utils.rs index 935ea2eb52c..517ff2eff42 100644 --- a/crates/cairo-lang-sierra-generator/src/utils.rs +++ b/crates/cairo-lang-sierra-generator/src/utils.rs @@ -138,6 +138,14 @@ pub fn unbox_libfunc_id( get_libfunc_id_with_generic_arg(db, "unbox", ty) } +/// Returns the [ConcreteLibfuncId] associated with `local_into_box`. +pub fn local_into_box_libfunc_id( + db: &dyn Database, + ty: cairo_lang_sierra::ids::ConcreteTypeId, +) -> cairo_lang_sierra::ids::ConcreteLibfuncId { + get_libfunc_id_with_generic_arg(db, "local_into_box", ty) +} + pub fn enum_init_libfunc_id( db: &dyn Database, ty: cairo_lang_sierra::ids::ConcreteTypeId, diff --git a/tests/e2e_test.rs b/tests/e2e_test.rs index 9b9be9e6a6b..3143741c824 100644 --- a/tests/e2e_test.rs +++ b/tests/e2e_test.rs @@ -3,6 +3,9 @@ use std::sync::{LazyLock, Mutex}; use cairo_lang_compiler::db::RootDatabase; use cairo_lang_compiler::diagnostics::DiagnosticsReporter; +use cairo_lang_filesystem::db::FilesGroup; +use cairo_lang_filesystem::flag::Flag; +use cairo_lang_filesystem::ids::FlagLongId; use cairo_lang_lowering::db::lowering_group_input; use cairo_lang_lowering::optimizations::config::{OptimizationConfig, Optimizations}; use cairo_lang_semantic::test_utils::setup_test_module; @@ -46,6 +49,14 @@ static SHARED_DB_WITH_OPTS: LazyLock> = LazyLock::new(|| { .to(Some(Optimizations::Enabled(Default::default()))); Mutex::new(db) }); +static SHARED_DB_FUTURE_SIERRA: LazyLock> = LazyLock::new(|| { + let mut db = RootDatabase::builder().detect_corelib().build().unwrap(); + lowering_group_input(&db).set_optimizations(&mut db).to(Some(Optimizations::Enabled( + OptimizationConfig::default().with_skip_const_folding(true), + ))); + db.set_flag(FlagLongId("future_sierra".into()), Some(Flag::FutureSierra(true).into())); + Mutex::new(db) +}); cairo_lang_test_utils::test_file_test_with_runner!( general_e2e, @@ -147,9 +158,10 @@ impl TestFileRunner for SmallE2ETestRunner { fn run( &mut self, inputs: &OrderedHashMap, - _args: &OrderedHashMap, + args: &OrderedHashMap, ) -> TestRunnerResult { - run_e2e_test(inputs, E2eTestParams::default()) + let future_sierra = args.get("future_sierra").is_some_and(|v| v == "true"); + run_e2e_test(inputs, E2eTestParams { future_sierra, ..E2eTestParams::default() }) } } @@ -194,6 +206,7 @@ impl TestFileRunner for SmallE2ETestRunnerMetadataComputation { add_withdraw_gas: false, metadata_computation: true, skip_optimization_passes: true, + ..Default::default() }, ) } @@ -211,12 +224,20 @@ struct E2eTestParams { /// Argument for `run_e2e_test` that controls whether to skip optimization passes. skip_optimization_passes: bool, + + /// Argument for `run_e2e_test` that controls whether to enable the `future_sierra` flag. + future_sierra: bool, } /// Implements default for `E2eTestParams`. impl Default for E2eTestParams { fn default() -> Self { - Self { add_withdraw_gas: true, metadata_computation: false, skip_optimization_passes: true } + Self { + add_withdraw_gas: true, + metadata_computation: false, + skip_optimization_passes: true, + future_sierra: false, + } } } @@ -225,7 +246,9 @@ fn run_e2e_test( inputs: &OrderedHashMap, params: E2eTestParams, ) -> TestRunnerResult { - let mut locked_db = test_lock(if !params.skip_optimization_passes { + let mut locked_db = test_lock(if params.future_sierra { + &SHARED_DB_FUTURE_SIERRA + } else if !params.skip_optimization_passes { &SHARED_DB_WITH_OPTS } else if params.add_withdraw_gas { &SHARED_DB_WITH_GAS_NO_OPTS diff --git a/tests/e2e_test_data/libfuncs/box b/tests/e2e_test_data/libfuncs/box index da23d49bdb3..95409cf2425 100644 --- a/tests/e2e_test_data/libfuncs/box +++ b/tests/e2e_test_data/libfuncs/box @@ -940,3 +940,49 @@ return([2]); test::bar@F0() -> (felt252); test::foo@F1() -> (Box); + +//! > ========================================================================== + +//! > ReprPointer (&T) with large struct triggers local_into_box optimization. + +//! > test_runner_name +SmallE2ETestRunner(future_sierra:true) + +//! > cairo_code +#[derive(Drop, Copy)] +struct LargeStruct { + a: felt252, + b: felt252, + c: felt252, +} + +fn foo(s: LargeStruct) -> &LargeStruct { + &s +} + +//! > casm +call rel 5; +[ap + 0] = [ap + -2] + -5, ap++; +ret; + +//! > function_costs +test::foo: SmallOrderedMap({Const: 400}) + +//! > sierra_code +type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; +type test::LargeStruct = Struct [storable: true, drop: true, dup: true, zero_sized: false]; +type Box = Box [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc snapshot_take = snapshot_take; +libfunc drop = drop; +libfunc local_into_box = local_into_box; +libfunc store_temp> = store_temp>; + +F0: +snapshot_take([0]) -> ([1], [2]); +drop([1]) -> (); +local_into_box([2]) -> ([3]); +store_temp>([3]) -> ([3]); +return([3]); + +test::foo@F0([0]: test::LargeStruct) -> (Box);