diff --git a/crates/cairo-lang-lowering/src/analysis/equality_analysis.rs b/crates/cairo-lang-lowering/src/analysis/equality_analysis.rs index ea8d3d1d899..bf056c4d129 100644 --- a/crates/cairo-lang-lowering/src/analysis/equality_analysis.rs +++ b/crates/cairo-lang-lowering/src/analysis/equality_analysis.rs @@ -2,17 +2,22 @@ //! //! This module tracks semantic equivalence between variables as information flows through the //! program. Two variables are equivalent if they hold the same value. Additionally, the analysis -//! tracks `Box`/unbox and snapshot/desnap relationships between equivalence classes. +//! tracks `Box`/unbox, snapshot/desnap, and struct/array construct relationships between +//! equivalence classes. Arrays reuse the struct hashcons infrastructure since both map +//! `(TypeId, Vec)` — array pop operations act as destructures. use cairo_lang_debug::DebugWithDb; -use cairo_lang_defs::ids::NamedLanguageElementId; +use cairo_lang_defs::ids::{ExternFunctionId, NamedLanguageElementId}; +use cairo_lang_semantic::helper::ModuleHelper; use cairo_lang_semantic::{ConcreteVariant, MatchArmSelector, TypeId}; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use salsa::Database; use crate::analysis::core::Edge; use crate::analysis::{DataflowAnalyzer, Direction, ForwardDataflowAnalysis}; -use crate::{BlockEnd, BlockId, Lowered, MatchInfo, Statement, VariableId}; +use crate::{ + BlockEnd, BlockId, Lowered, MatchArm, MatchExternInfo, MatchInfo, Statement, VariableId, +}; /// Tracks relationships between equivalence classes. #[derive(Clone, Debug, Default)] @@ -90,13 +95,16 @@ pub struct EqualityState<'db> { /// This allows efficient lookup when matching on an enum to find the original input. enum_hashcons_rev: OrderedHashMap, VariableId)>, - /// Hashcons for struct constructs: maps (type, [field_reps...]) -> output_rep. - /// This allows us to detect when two struct constructs with the same type - /// and equivalent fields should produce equivalent outputs. + /// Hashcons for struct/array constructs: maps (type, [field_reps...]) -> output_rep. + /// This allows us to detect when two constructs with the same type + /// and equivalent fields/elements should produce equivalent outputs. + /// Arrays reuse this same infrastructure — `array_new`/`array_append` chains are recorded + /// as constructs keyed by the array type, and `array_pop_front` acts as a destructure. struct_hashcons: OrderedHashMap<(TypeId<'db>, Vec), VariableId>, - /// Reverse hashcons for struct constructs: maps output_rep -> (type, [field_reps...]). - /// This allows efficient lookup when destructuring a struct to find the original fields. + /// Reverse hashcons for struct/array constructs: maps output_rep -> (type, + /// [field_reps/element_reps...]). + /// This allows efficient lookup when destructuring a struct or popping from an array. struct_hashcons_rev: OrderedHashMap, Vec)>, } @@ -307,23 +315,165 @@ impl<'db> DebugWithDb<'db> for EqualityState<'db> { /// Variable equality analysis. /// -/// This analyzer tracks snapshot/desnap and box/unbox relationships as data flows -/// through the program. At merge points (after match arms converge), we conservatively +/// This analyzer tracks snapshot/desnap, box/unbox, and array construct relationships as data +/// flows through the program. At merge points (after match arms converge), we conservatively /// intersect the equivalence classes, keeping only equalities that hold on all paths. pub struct EqualityAnalysis<'a, 'db> { + db: &'db dyn Database, lowered: &'a Lowered<'db>, + /// The `array_new` extern function id. + array_new: ExternFunctionId<'db>, + /// The `array_append` extern function id. + array_append: ExternFunctionId<'db>, + /// The `array_pop_front` extern function id. + array_pop_front: ExternFunctionId<'db>, + /// The `array_pop_front_consume` extern function id. + array_pop_front_consume: ExternFunctionId<'db>, + /// The `array_snapshot_pop_front` extern function id. + array_snapshot_pop_front: ExternFunctionId<'db>, + /// The `array_snapshot_pop_back` extern function id. + array_snapshot_pop_back: ExternFunctionId<'db>, } impl<'a, 'db> EqualityAnalysis<'a, 'db> { /// Creates a new equality analysis instance. - pub fn new(lowered: &'a Lowered<'db>) -> Self { - Self { lowered } + pub fn new(db: &'db dyn Database, lowered: &'a Lowered<'db>) -> Self { + let array_module = ModuleHelper::core(db).submodule("array"); + Self { + db, + lowered, + array_new: array_module.extern_function_id("array_new"), + array_append: array_module.extern_function_id("array_append"), + array_pop_front: array_module.extern_function_id("array_pop_front"), + array_pop_front_consume: array_module.extern_function_id("array_pop_front_consume"), + array_snapshot_pop_front: array_module.extern_function_id("array_snapshot_pop_front"), + array_snapshot_pop_back: array_module.extern_function_id("array_snapshot_pop_back"), + } } /// Runs equality analysis on a lowered function. /// Returns the equality state at the exit of each block. - pub fn analyze(lowered: &'a Lowered<'db>) -> Vec>> { - ForwardDataflowAnalysis::new(lowered, EqualityAnalysis::new(lowered)).run() + pub fn analyze( + db: &'db dyn Database, + lowered: &'a Lowered<'db>, + ) -> Vec>> { + ForwardDataflowAnalysis::new(lowered, EqualityAnalysis::new(db, lowered)).run() + } + + /// Handles extern match arms for array operations. + /// + /// Array pop operations act as "destructures" on the struct-hashcons representation: + /// - `array_pop_front` / `array_pop_front_consume`: On the Some arm, if the input array was + /// tracked as `[e0, e1, ..., eN]`, the popped element (boxed) is `Box(e0)` and the remaining + /// array is `[e1, ..., eN]`. + /// - `array_snapshot_pop_front`: Same as above but through snapshot/box-of-snapshot wrappers. + /// - `array_snapshot_pop_back`: Like pop_front but pops from the back: element is `Box(eN)`, + /// remaining is `[e0, ..., eN-1]`. + fn transfer_extern_match_arm( + &self, + info: &mut EqualityState<'db>, + extern_info: &MatchExternInfo<'db>, + arm: &MatchArm<'db>, + ) { + let Some((id, _)) = extern_info.function.get_extern(self.db) else { return }; + // TODO(eytan-starkware): Add support for multipop. + if id == self.array_pop_front || id == self.array_pop_front_consume { + // Some arm: var_ids = [remaining_arr, boxed_elem] + if arm.var_ids.len() == 2 { + let input_arr = extern_info.inputs[0].var_id; + let remaining_arr = arm.var_ids[0]; + let boxed_elem = arm.var_ids[1]; + + let arr_rep = info.find(input_arr); + if let Some((ty, elems)) = info.struct_hashcons_rev.get(&arr_rep).cloned() + && let Some((&first, rest)) = elems.split_first() + { + // Popped element is boxed: boxed_elem = Box(first_element) + info.set_box_relationship(first, boxed_elem); + // Remaining array is the tail + let rest_reps: Vec<_> = rest.iter().map(|&v| info.find(v)).collect(); + info.set_struct_construct(ty, rest_reps, remaining_arr); + } + } else { + // None arm for array_pop_front: var_ids = [original_arr]. Union with input. + // None arm for array_pop_front_consume: var_ids = []. + let old_array_var = extern_info.inputs[0].var_id; + let ty = self.lowered.variables[old_array_var].ty; + // TODO(eytan-starkware): This introduces a backedge to our hashcons updates, + // so we might need to support updating the structures accordingly. + // For example, if this is empty after a pop, then we know previous array was a + // singleton. + info.set_struct_construct(ty, vec![], old_array_var); + if arm.var_ids.len() == 1 { + let empty_array_var = arm.var_ids[0]; + info.union(old_array_var, empty_array_var); + } + } + } else if id == self.array_snapshot_pop_front || id == self.array_snapshot_pop_back { + // Some arm: var_ids = [remaining_snap_arr, boxed_snap_elem] + if arm.var_ids.len() == 2 { + let input_snap_arr = extern_info.inputs[0].var_id; + let remaining_snap_arr = arm.var_ids[0]; + let boxed_snap_elem = arm.var_ids[1]; + + // The input is @Array. Look up the tracked elements. + // Two paths: (1) the input snapshot was created via `snapshot(arr)` where `arr` + // has a struct-hashcons entry under `Array`, or (2) the input is itself a + // remaining snapshot array from a prior snapshot pop, stored directly in the + // struct hashcons under its snapshot type `@Array`. + let snap_rep = info.find(input_snap_arr); + let elems_opt = info + .class_info + .get(&snap_rep) + .and_then(|ci| ci.original_class) + .and_then(|orig| { + let orig = info.find_immut(orig); + info.struct_hashcons_rev.get(&orig).cloned() + }) + .or_else(|| info.struct_hashcons_rev.get(&snap_rep).cloned()); + + if let Some((_orig_ty, elems)) = elems_opt { + let pop_front = id == self.array_snapshot_pop_front; + let (elem, rest) = if pop_front { + let Some((&first, tail)) = elems.split_first() else { return }; + (first, tail.to_vec()) + } else { + let Some((&last, init)) = elems.split_last() else { return }; + (last, init.to_vec()) + }; + + // The popped element is `Box<@T>`. The box wraps the *snapshot* of the + // original element. We can only record this relationship if a variable + // for `@elem` already exists (i.e., elem has a snapshot class). + // TODO(eytan-starkware): Support relationships even no variable to represent + // `@elem` yet. + let elem_rep = info.find(elem); + if let Some(snap_of_elem) = + info.class_info.get(&elem_rep).and_then(|ci| ci.snapshot_class) + { + info.set_box_relationship(snap_of_elem, boxed_snap_elem); + } + + // Record the remaining snapshot array under its snapshot type + // (`@Array`). This is a hack: `@Array` is not a struct, but we + // reuse `struct_hashcons` to store element info for it. This also + // requires the two-path lookup above (path 2). + // TODO(eytan-starkware): Once placeholder vars are supported, store + // this as a proper `Array` linked via `original_class` instead, + // since we may not have a var representing the non-snapshot array at the + // moment. + let snap_ty = self.lowered.variables[remaining_snap_arr].ty; + let rest_reps: Vec<_> = rest.iter().map(|&v| info.find(v)).collect(); + info.set_struct_construct(snap_ty, rest_reps, remaining_snap_arr); + } + } else { + // None arm: var_ids = [original_snap_arr]. Snapshot array was empty. + let old_snap_arr = extern_info.inputs[0].var_id; + let snap_ty = self.lowered.variables[old_snap_arr].ty; + info.set_struct_construct(snap_ty, vec![], old_snap_arr); + info.union(arm.var_ids[0], old_snap_arr); + } + } } } @@ -600,7 +750,22 @@ impl<'db, 'a> DataflowAnalyzer<'db, 'a> for EqualityAnalysis<'a, 'db> { info.set_struct_construct(ty, output_reps, struct_stmt.input.var_id); } - Statement::Const(_) | Statement::Call(_) => {} + Statement::Call(call_stmt) => { + let Some((id, _)) = call_stmt.function.get_extern(self.db) else { return }; + if id == self.array_new { + let ty = self.lowered.variables[call_stmt.outputs[0]].ty; + info.set_struct_construct(ty, vec![], call_stmt.outputs[0]); + } else if id == self.array_append { + let arr_rep = info.find(call_stmt.inputs[0].var_id); + if let Some((ty, elems)) = info.struct_hashcons_rev.get(&arr_rep).cloned() { + let mut new_elems = elems; + new_elems.push(info.find(call_stmt.inputs[1].var_id)); + info.set_struct_construct(ty, new_elems, call_stmt.outputs[0]); + } + } + } + + Statement::Const(_) => {} } } @@ -635,6 +800,11 @@ impl<'db, 'a> DataflowAnalyzer<'db, 'a> for EqualityAnalysis<'a, 'db> { // Record the relationship: matched_var = Variant(arm_var) new_info.set_enum_construct(variant, arm_var, matched_var); } + + // For extern matches on array operations, track pop/destructure relationships. + if let MatchInfo::Extern(extern_info) = match_info { + self.transfer_extern_match_arm(&mut new_info, extern_info, arm); + } } Edge::Return { .. } | Edge::Panic { .. } => {} } diff --git a/crates/cairo-lang-lowering/src/analysis/equality_analysis_test.rs b/crates/cairo-lang-lowering/src/analysis/equality_analysis_test.rs index e134445b584..b38ee4572d9 100644 --- a/crates/cairo-lang-lowering/src/analysis/equality_analysis_test.rs +++ b/crates/cairo-lang-lowering/src/analysis/equality_analysis_test.rs @@ -9,6 +9,7 @@ use super::equality_analysis::EqualityAnalysis; use crate::LoweringStage; use crate::db::LoweringGroup; use crate::ids::ConcreteFunctionWithBodyId; +use crate::optimizations::strategy::OptimizationPhase; use crate::test_utils::{LoweringDatabaseForTesting, formatted_lowered}; cairo_lang_test_utils::test_file_test!( @@ -30,12 +31,23 @@ fn test_equality_analysis( let function_id = ConcreteFunctionWithBodyId::from_semantic(db, test_function.concrete_function_id); - // Use an earlier stage to see the snapshot/box operations before they're optimized away. + // Use an earlier stage to keep snapshot/box operations visible, then apply inlining so that + // trait methods (e.g. `ArrayTrait::new`, `.append`) resolve to extern calls (`array_new`, + // `array_append`). The remaining phases clean up the IR for readable test output. let lowered = db.lowered_body(function_id, LoweringStage::PostBaseline); - let (lowering_str, analysis_state_str) = if let Ok(lowered) = lowered { - let lowering_str = formatted_lowered(db, Some(lowered)); - let block_states = EqualityAnalysis::analyze(lowered); + let (lowering_str, analysis_state_str) = if let Ok(mut lowered) = lowered.cloned() { + OptimizationPhase::ReorganizeBlocks.apply(db, function_id, &mut lowered).unwrap(); + OptimizationPhase::ApplyInlining { enable_const_folding: true } + .apply(db, function_id, &mut lowered) + .unwrap(); + OptimizationPhase::ReturnOptimization.apply(db, function_id, &mut lowered).unwrap(); + OptimizationPhase::ReorganizeBlocks.apply(db, function_id, &mut lowered).unwrap(); + OptimizationPhase::ReorderStatements.apply(db, function_id, &mut lowered).unwrap(); + OptimizationPhase::BranchInversion.apply(db, function_id, &mut lowered).unwrap(); + + let lowering_str = formatted_lowered(db, Some(&lowered)); + let block_states = EqualityAnalysis::analyze(db, &lowered); // Format each block's state let analysis_state_str = block_states diff --git a/crates/cairo-lang-lowering/src/analysis/test_data/equality b/crates/cairo-lang-lowering/src/analysis/test_data/equality index aeff8b669c6..362bb7d81bb 100644 --- a/crates/cairo-lang-lowering/src/analysis/test_data/equality +++ b/crates/cairo-lang-lowering/src/analysis/test_data/equality @@ -935,3 +935,372 @@ Block 2: Block 3: @v0 = v3, @v9 = v14, test::MyStruct(v3) = v9, v0 = v2, v9 = v13 + +//! > ========================================================================== + +//! > Test array new and append tracked via struct hashcons + +//! > test_runner_name +test_equality_analysis + +//! > function_code +fn foo(a: felt252, b: felt252) -> Array { + let mut arr = ArrayTrait::new(); + arr.append(a); + arr.append(b); + arr +} + +//! > function_name +foo + +//! > module_code + +//! > semantic_diagnostics + +//! > lowering +Parameters: v0: core::felt252, v1: core::felt252 +blk0 (root): +Statements: + (v2: core::array::Array::) <- core::array::array_new::() + (v3: core::array::Array::) <- core::array::array_append::(v2, v0) + (v4: core::array::Array::) <- core::array::array_append::(v3, v1) +End: + Return(v4) + +//! > analysis_state +Block 0: +core::array::Array::() = v2, core::array::Array::(v0) = v3, core::array::Array::(v0, v1) = v4 + +//! > ========================================================================== + +//! > Test array pop_front recovers elements and merge works correctly + +//! > test_runner_name +test_equality_analysis + +//! > function_code +fn foo(a: felt252, b: felt252) -> Array { + let mut arr = ArrayTrait::new(); + arr.append(a); + arr.append(b); + let result = match arr.pop_front() { + Option::Some(val) => { + let mut remaining = ArrayTrait::new(); + remaining.append(val); + remaining + }, + Option::None => { arr }, + }; + use_arr(@result); + result +} + +//! > function_name +foo + +//! > module_code +extern fn use_arr(x: @Array) nopanic; + +//! > semantic_diagnostics + +//! > lowering +Parameters: v0: core::felt252, v1: core::felt252 +blk0 (root): +Statements: + (v2: core::array::Array::) <- core::array::array_new::() + (v3: core::array::Array::) <- core::array::array_append::(v2, v0) + (v4: core::array::Array::) <- core::array::array_append::(v3, v1) +End: + Match(match core::array::array_pop_front::(v4) { + Option::Some(v5, v6) => blk1, + Option::None(v7) => blk2, + }) + +blk1: +Statements: + (v8: core::array::Array::) <- core::array::array_new::() + (v9: core::array::Array::) <- core::array::array_append::(v8, v0) +End: + Goto(blk3, {v9 -> v10}) + +blk2: +Statements: +End: + Goto(blk3, {v7 -> v10}) + +blk3: +Statements: + (v11: core::array::Array::, v12: @core::array::Array::) <- snapshot(v10) + () <- test::use_arr(v12) +End: + Return(v11) + +//! > analysis_state +Block 0: +core::array::Array::() = v2, core::array::Array::(v0) = v3, core::array::Array::(v0, v1) = v4 + +Block 1: +Box(v0) = v6, core::array::Array::() = v2, core::array::Array::(v0) = v3, core::array::Array::(v0, v1) = v4, core::array::Array::(v1) = v5, v2 = v8, v3 = v9 + +Block 2: +core::array::Array::() = v2, core::array::Array::(v0) = v3, core::array::Array::(v0, v1) = v2, v2 = v4, v2 = v7 + +Block 3: +@v10 = v12, core::array::Array::() = v2, core::array::Array::(v0) = v3, core::array::Array::(v0, v1) = v4, v10 = v11 + +//! > ========================================================================== + +//! > Test chained snapshot pop_front recovers elements + +//! > test_runner_name +test_equality_analysis + +//! > We snapshot a and b before building the span so they have snapshot classes. + +//! > This allows the analysis to track Box(@a)/Box(@b) relationships when popping. + +//! > function_code +fn foo(a: felt252, b: felt252) { + let mut arr = ArrayTrait::new(); + arr.append(a); + arr.append(b); + use_snap_felt(@a); + use_snap_felt(@b); + let mut span = arr.span(); + match span.pop_front() { + Option::Some(first) => { + use_snap_felt(first); + match span.pop_front() { + Option::Some(second) => { use_snap_felt(second); }, + Option::None => {}, + }; + }, + Option::None => {}, + }; +} + +//! > function_name +foo + +//! > module_code +extern fn use_snap_felt(x: @felt252) nopanic; + +//! > semantic_diagnostics + +//! > lowering +Parameters: v0: core::felt252, v1: core::felt252 +blk0 (root): +Statements: + (v2: core::felt252, v3: @core::felt252) <- snapshot(v0) + () <- test::use_snap_felt(v3) + (v4: core::felt252, v5: @core::felt252) <- snapshot(v1) + () <- test::use_snap_felt(v5) + (v6: core::array::Array::) <- core::array::array_new::() + (v7: core::array::Array::) <- core::array::array_append::(v6, v0) + (v8: core::array::Array::) <- core::array::array_append::(v7, v1) + (v9: core::array::Array::, v10: @core::array::Array::) <- snapshot(v8) +End: + Match(match core::array::array_snapshot_pop_front::(v10) { + Option::Some(v11, v12) => blk1, + Option::None(v13) => blk4, + }) + +blk1: +Statements: + (v14: @core::felt252) <- unbox(v12) + () <- test::use_snap_felt(v14) +End: + Match(match core::array::array_snapshot_pop_front::(v11) { + Option::Some(v15, v16) => blk2, + Option::None(v17) => blk3, + }) + +blk2: +Statements: + (v18: @core::felt252) <- unbox(v16) + () <- test::use_snap_felt(v18) +End: + Return() + +blk3: +Statements: +End: + Return() + +blk4: +Statements: +End: + Return() + +//! > analysis_state +Block 0: +@v0 = v3, @v1 = v5, @v8 = v10, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v8 = v9 + +Block 1: +@core::array::Array::(v1) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v3) = v12, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v3 = v14, v8 = v9 + +Block 2: +@core::array::Array::() = v15, @core::array::Array::(v1) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v3) = v12, Box(v5) = v16, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v3 = v14, v5 = v18, v8 = v9 + +Block 3: +@core::array::Array::() = v11, @core::array::Array::(v1) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v3) = v12, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v11 = v17, v3 = v14, v8 = v9 + +Block 4: +@core::array::Array::() = v10, @v0 = v3, @v1 = v5, @v8 = v10, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v10 = v13, v8 = v9 + +//! > ========================================================================== + +//! > Test chained snapshot pop_back recovers elements + +//! > test_runner_name +test_equality_analysis + +//! > We snapshot a and b before building the span so they have snapshot classes. + +//! > This allows the analysis to track Box(@a)/Box(@b) relationships when popping. + +//! > function_code +fn foo(a: felt252, b: felt252) { + let mut arr = ArrayTrait::new(); + arr.append(a); + arr.append(b); + use_snap_felt(@a); + use_snap_felt(@b); + let mut span = arr.span(); + match span.pop_back() { + Option::Some(last) => { + use_snap_felt(last); + match span.pop_back() { + Option::Some(second_last) => { use_snap_felt(second_last); }, + Option::None => {}, + }; + }, + Option::None => {}, + }; +} + +//! > function_name +foo + +//! > module_code +extern fn use_snap_felt(x: @felt252) nopanic; + +//! > semantic_diagnostics + +//! > lowering +Parameters: v0: core::felt252, v1: core::felt252 +blk0 (root): +Statements: + (v2: core::felt252, v3: @core::felt252) <- snapshot(v0) + () <- test::use_snap_felt(v3) + (v4: core::felt252, v5: @core::felt252) <- snapshot(v1) + () <- test::use_snap_felt(v5) + (v6: core::array::Array::) <- core::array::array_new::() + (v7: core::array::Array::) <- core::array::array_append::(v6, v0) + (v8: core::array::Array::) <- core::array::array_append::(v7, v1) + (v9: core::array::Array::, v10: @core::array::Array::) <- snapshot(v8) +End: + Match(match core::array::array_snapshot_pop_back::(v10) { + Option::Some(v11, v12) => blk1, + Option::None(v13) => blk4, + }) + +blk1: +Statements: + (v14: @core::felt252) <- unbox(v12) + () <- test::use_snap_felt(v14) +End: + Match(match core::array::array_snapshot_pop_back::(v11) { + Option::Some(v15, v16) => blk2, + Option::None(v17) => blk3, + }) + +blk2: +Statements: + (v18: @core::felt252) <- unbox(v16) + () <- test::use_snap_felt(v18) +End: + Return() + +blk3: +Statements: +End: + Return() + +blk4: +Statements: +End: + Return() + +//! > analysis_state +Block 0: +@v0 = v3, @v1 = v5, @v8 = v10, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v8 = v9 + +Block 1: +@core::array::Array::(v0) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v5) = v12, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v5 = v14, v8 = v9 + +Block 2: +@core::array::Array::() = v15, @core::array::Array::(v0) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v3) = v16, Box(v5) = v12, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v3 = v18, v5 = v14, v8 = v9 + +Block 3: +@core::array::Array::() = v11, @core::array::Array::(v0) = v11, @v0 = v3, @v1 = v5, @v8 = v10, Box(v5) = v12, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v11 = v17, v5 = v14, v8 = v9 + +Block 4: +@core::array::Array::() = v10, @v0 = v3, @v1 = v5, @v8 = v10, core::array::Array::() = v6, core::array::Array::(v0) = v7, core::array::Array::(v0, v1) = v8, v0 = v2, v1 = v4, v10 = v13, v8 = v9 + +//! > ========================================================================== + +//! > Test pop_front None arm records array as empty + +//! > test_runner_name +test_equality_analysis + +//! > function_code +fn foo(mut arr: Array) -> Array { + match arr.pop_front() { + Option::Some(_val) => arr, + Option::None => { + let empty = ArrayTrait::new(); + empty + }, + } +} + +//! > function_name +foo + +//! > module_code + +//! > semantic_diagnostics + +//! > lowering +Parameters: v0: core::array::Array:: +blk0 (root): +Statements: +End: + Match(match core::array::array_pop_front::(v0) { + Option::Some(v1, v2) => blk1, + Option::None(v3) => blk2, + }) + +blk1: +Statements: +End: + Return(v1) + +blk2: +Statements: + (v4: core::array::Array::) <- core::array::array_new::() +End: + Return(v4) + +//! > analysis_state +Block 0: +(empty) + +Block 1: +(empty) + +Block 2: +core::array::Array::() = v0, v0 = v3, v0 = v4