diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index deffe84baea..5d59f6978e2 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -72,6 +72,7 @@ pub(crate) fn optimize_into_acir( let ssa = ssa_builder .run_pass(Ssa::fill_internal_slices, "After Fill Internal Slice Dummy Data:") .finish(); + drop(ssa_gen_span_guard); let last_array_uses = ssa.find_last_array_uses(); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index a1c96a3cd23..f29e4e365a4 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -678,27 +678,16 @@ impl Context { instruction: InstructionId, dfg: &DataFlowGraph, index: ValueId, - array: ValueId, + array_id: ValueId, store_value: Option, ) -> Result { let index_const = dfg.get_numeric_constant(index); - let value_type = dfg.type_of_value(array); - let (Type::Array(element_types, _) | Type::Slice(element_types)) = &value_type else { + let value_type = dfg.type_of_value(array_id); + let (Type::Array(_, _) | Type::Slice(_)) = &value_type else { unreachable!("ICE: expected array or slice type"); - }; - // TODO(#3188): Need to be able to handle constant index for slices to seriously reduce - // constraint sizes of nested slices - // This can only be done if we accurately flatten nested slices as otherwise we will reach - // index out of bounds errors. If the slice is already flat then we can treat them similarly to arrays. - if matches!(value_type, Type::Slice(_)) - && element_types.iter().any(|element| element.contains_slice_element()) - { - return Ok(false); - } - - match self.convert_value(array, dfg) { + match self.convert_value(array_id, dfg) { AcirValue::Var(acir_var, _) => { return Err(RuntimeError::InternalError(InternalError::Unexpected { expected: "an array value".to_string(), @@ -720,9 +709,10 @@ impl Context { }); } }; + if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) { // Report the error if side effects are enabled. - if index >= array_size { + if index >= array_size && !matches!(value_type, Type::Slice(_)) { let call_stack = self.acir_context.get_call_stack(); return Err(RuntimeError::IndexOutOfBounds { index, @@ -735,7 +725,13 @@ impl Context { let store_value = self.convert_value(store_value, dfg); AcirValue::Array(array.update(index, store_value)) } - None => array[index].clone(), + None => { + if index >= array_size { + self.construct_dummy_slice_value(instruction, dfg, array_id) + } else { + array[index].clone() + } + } }; self.define_result(dfg, instruction, value); @@ -747,9 +743,49 @@ impl Context { self.define_result(dfg, instruction, array[index].clone()); return Ok(true); } + // If there is a non constant predicate and the index is out of range for a slice we should still directly create dummy data + // These situations should only happen during flattening of slices when the same slice after an if statement + // would have different lengths depending upon the condition + else if index >= array_size + && value_type.contains_slice_element() + && store_value.is_none() + { + if index >= array_size { + let value = + self.construct_dummy_slice_value(instruction, dfg, array_id); + + self.define_result(dfg, instruction, value); + return Ok(true); + } else { + return Ok(false); + } + } + } + } + AcirValue::DynamicArray(AcirDynamicArray { len, .. }) => { + if let Some(index_const) = index_const { + let index = match index_const.try_to_u64() { + Some(index_const) => index_const as usize, + None => { + let call_stack = self.acir_context.get_call_stack(); + return Err(RuntimeError::TypeConversion { + from: "array index".to_string(), + into: "u64".to_string(), + call_stack, + }); + } + }; + + if index >= len { + let value = self.construct_dummy_slice_value(instruction, dfg, array_id); + + self.define_result(dfg, instruction, value); + return Ok(true); + } } + + return Ok(false); } - AcirValue::DynamicArray(_) => (), }; Ok(false) @@ -790,6 +826,7 @@ impl Context { // We must setup the dummy value to match the type of the value we wish to store let slice_sizes = if store_type.contains_slice_element() { self.compute_slice_sizes(store, None, dfg); + self.slice_sizes.get(&store).cloned().ok_or_else(|| { InternalError::Unexpected { expected: "Store value should have slice sizes computed".to_owned(), @@ -800,6 +837,7 @@ impl Context { } else { vec![] }; + let dummy = self.array_get_value( &store_type, block_id, @@ -1001,6 +1039,65 @@ impl Context { } } + fn construct_dummy_slice_value( + &mut self, + instruction: InstructionId, + dfg: &DataFlowGraph, + array_id: ValueId, + ) -> AcirValue { + let results = dfg.instruction_results(instruction); + let res_typ = dfg.type_of_value(results[0]); + if res_typ.contains_slice_element() { + self.compute_slice_sizes(array_id, None, dfg); + + let slice_sizes = self + .slice_sizes + .get(&array_id) + .expect("ICE: Array with slices should have associated slice sizes"); + + let slice_sizes = slice_sizes[1..].to_vec(); + + self.construct_dummy_array_value(&res_typ, &slice_sizes) + } else { + self.construct_dummy_array_value(&res_typ, &[]) + } + } + + fn construct_dummy_array_value(&mut self, ssa_type: &Type, slice_sizes: &[usize]) -> AcirValue { + match ssa_type.clone() { + Type::Numeric(numeric_type) => { + let zero = self.acir_context.add_constant(FieldElement::zero()); + let typ = AcirType::NumericType(numeric_type); + AcirValue::Var(zero, typ) + } + Type::Array(element_types, len) => { + let mut values = Vector::new(); + for _ in 0..len { + for typ in element_types.as_ref() { + values.push_back(self.construct_dummy_array_value(typ, slice_sizes)); + } + } + AcirValue::Array(values) + } + Type::Slice(element_types) => { + // It is not enough to execute this loop and simply pass the size from the parent definition. + // We need the internal sizes of each type in case of a nested slice. + let mut values = Vector::new(); + + let (current_size, new_sizes) = + slice_sizes.split_first().expect("should be able to split"); + + for _ in 0..*current_size { + for typ in element_types.as_ref() { + values.push_back(self.construct_dummy_array_value(typ, new_sizes)); + } + } + AcirValue::Array(values) + } + _ => unreachable!("ICE - expected an array or slice"), + } + } + /// Copy the array and generates a write opcode on the new array /// /// Note: Copying the array is inefficient and is not the way we want to do it in the end. @@ -1076,6 +1173,16 @@ impl Context { } } + if array_typ.contains_slice_element() { + let slice_sizes = self + .slice_sizes + .get(&array_id) + .expect("ICE: Expected array with internal slices to have associated slice sizes") + .clone(); + let results = dfg.instruction_results(instruction); + self.slice_sizes.insert(results[0], slice_sizes); + } + let element_type_sizes = if !can_omit_element_sizes_array(&array_typ) { Some(self.init_element_type_sizes_array(&array_typ, array_id, None, dfg)?) } else { @@ -1836,6 +1943,12 @@ impl Context { len: new_elem_size, element_type_sizes, }); + + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[1], inner_sizes); + Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), result]) } Intrinsic::SlicePushFront => { @@ -1910,6 +2023,11 @@ impl Context { element_type_sizes, }); + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[1], inner_sizes); + Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), result]) } Intrinsic::SlicePopBack => { @@ -1926,7 +2044,7 @@ impl Context { let (slice_contents, slice_typ, block_id) = self.check_array_is_initialized(arguments[1], dfg)?; let slice = self.convert_value(slice_contents, dfg); - + let new_slice_size = Self::flattened_value_size(&slice); let element_size = slice_typ.element_size(); let mut popped_elements = Vec::new(); @@ -1977,12 +2095,39 @@ impl Context { let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; - let mut results = vec![ - AcirValue::Var(new_slice_length, AcirType::field()), - AcirValue::Array(new_slice), - ]; + let new_slice_val = AcirValue::Array(new_slice); + + let result_block_id = self.block_id(&result_ids[1]); + self.initialize_array( + result_block_id, + new_slice_size, + Some(new_slice_val.clone()), + )?; + + let element_type_sizes = if !can_omit_element_sizes_array(&slice_typ) { + Some(self.init_element_type_sizes_array( + &slice_typ, + slice_contents, + Some(new_slice_val), + dfg, + )?) + } else { + None + }; + let result_slice = AcirValue::DynamicArray(AcirDynamicArray { + block_id: result_block_id, + len: new_slice_size, + element_type_sizes, + }); + let mut results = + vec![AcirValue::Var(new_slice_length, AcirType::field()), result_slice]; results.append(&mut popped_elements); + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[1], inner_sizes); + Ok(results) } Intrinsic::SlicePopFront => { @@ -1991,6 +2136,7 @@ impl Context { let (slice_contents, slice_typ, block_id) = self.check_array_is_initialized(arguments[1], dfg)?; + let slice = self.convert_value(slice_contents, dfg); let one = self.acir_context.add_constant(FieldElement::one()); @@ -2041,12 +2187,54 @@ impl Context { popped_elements.push(element); } } + // It is expected that the `popped_elements_size` is the flattened size of the elements, // as the input slice should be a dynamic array which is represented by flat memory. new_slice = new_slice.slice(popped_elements_size..); popped_elements.push(AcirValue::Var(new_slice_length, AcirType::field())); - popped_elements.push(AcirValue::Array(new_slice)); + + let zero = self.acir_context.add_constant(FieldElement::zero()); + let dummy_elements = Vector::from(vec![ + AcirValue::Var(zero, AcirType::field()); + popped_elements_size + ]); + new_slice.append(dummy_elements); + let new_slice_val = AcirValue::Array(new_slice); + let new_slice_size = Self::flattened_value_size(&new_slice_val); + let result_block_id = self.block_id(&result_ids[result_ids.len() - 1]); + self.initialize_array( + result_block_id, + new_slice_size, + Some(new_slice_val.clone()), + )?; + + let element_type_sizes = if !can_omit_element_sizes_array(&slice_typ) { + Some(self.init_element_type_sizes_array( + &slice_typ, + slice_contents, + // Do not supply the ACIR value here as we do not maintain the type structure + // inside of `new_slice_val` which is a flat dynamic array. + // We differ from the other slice intrinsics here as the result of a pop front can continue + // to be used throughout a program and thus we do not want to change its type array. + None, + dfg, + )?) + } else { + None + }; + + let result_slice = AcirValue::DynamicArray(AcirDynamicArray { + block_id: result_block_id, + len: new_slice_size, + element_type_sizes, + }); + popped_elements.push(result_slice); + + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[result_ids.len() - 1], inner_sizes); Ok(popped_elements) } @@ -2178,6 +2366,11 @@ impl Context { element_type_sizes, }); + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[1], inner_sizes); + Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), result]) } Intrinsic::SliceRemove => { @@ -2322,6 +2515,11 @@ impl Context { let mut result = vec![AcirValue::Var(new_slice_length, AcirType::field()), result]; result.append(&mut popped_elements); + let inner_sizes = + self.slice_sizes.get(&slice_contents).expect("ICE: expected slice sizes"); + let inner_sizes = inner_sizes.clone(); + self.slice_sizes.insert(result_ids[1], inner_sizes); + Ok(result) } _ => todo!("expected a black box function"), diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 9691017f04b..426edfd0091 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -236,7 +236,10 @@ impl Instruction { // In ACIR, a division with a false predicate outputs (0,0), so it cannot replace another instruction unless they have the same predicate bin.operator != BinaryOp::Div } - Cast(_, _) | Truncate { .. } | Not(_) | ArrayGet { .. } | ArraySet { .. } => true, + Cast(_, _) | Not(_) | Truncate { .. } => true, + + // These are not pure when working with nested slices + ArrayGet { .. } | ArraySet { .. } => false, // These either have side-effects or interact with memory Constrain(..) @@ -507,6 +510,7 @@ impl Instruction { if let (Some((array, _)), Some(index)) = (array, index) { let index = index.try_to_u64().expect("Expected array index to fit in u64") as usize; + if index < array.len() { return SimplifiedTo(array[index]); } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index edfc50a700f..24e2683556b 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -1,3 +1,4 @@ +use fxhash::FxHashMap as HashMap; use std::{collections::VecDeque, rc::Rc}; use acvm::{acir::BlackBoxFunc, BlackBoxResolutionError, FieldElement}; @@ -315,6 +316,8 @@ fn simplify_slice_push_back( for elem in &arguments[2..] { slice.push_back(*elem); } + let slice_size = slice.len(); + let element_size = element_type.element_size(); let new_slice = dfg.make_array(slice, element_type); let set_last_slice_value_instr = @@ -323,7 +326,11 @@ fn simplify_slice_push_back( .insert_instruction_and_results(set_last_slice_value_instr, block, None, call_stack) .first(); - let mut value_merger = ValueMerger::new(dfg, block, None, None); + let mut slice_sizes = HashMap::default(); + slice_sizes.insert(set_last_slice_value, (slice_size / element_size, vec![])); + slice_sizes.insert(new_slice, (slice_size / element_size, vec![])); + + let mut value_merger = ValueMerger::new(dfg, block, &mut slice_sizes); let new_slice = value_merger.merge_values( len_not_equals_capacity, len_equals_capacity, diff --git a/compiler/noirc_evaluator/src/ssa/ir/types.rs b/compiler/noirc_evaluator/src/ssa/ir/types.rs index ae53c7705c2..be831045c81 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -120,7 +120,7 @@ impl Type { } Type::Slice(_) => true, Type::Numeric(_) => false, - Type::Reference(_) => false, + Type::Reference(element) => element.contains_slice_element(), Type::Function => false, } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices.rs b/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices.rs index 5ee8e42fe3a..23b7eaf5022 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices.rs @@ -15,7 +15,7 @@ //! array_get [Field 3, [Field 1, Field 1, Field 1, Field 0], Field 4, [Field 2, Field 2, Field 2, Field 2]], index Field v0 //! //! -//! TODO(#3188): Currently the pass only works on a single flattened block. This should be updated in followup work. +//! Currently the pass only works on a single flattened block and should only come at the end of SSA right before ACIR generation. //! The steps of the pass are as follows: //! - Process each instruction of the block to collect relevant slice size information. We want to find the maximum size that a nested slice //! potentially could be. Slices can potentially be set to larger array values or used in intrinsics that increase or shorten their size. @@ -48,7 +48,7 @@ use crate::ssa::{ dfg::CallStack, function::{Function, RuntimeType}, function_inserter::FunctionInserter, - instruction::{Instruction, InstructionId, Intrinsic}, + instruction::{Instruction, InstructionId}, post_order::PostOrder, types::Type, value::{Value, ValueId}, @@ -59,6 +59,10 @@ use crate::ssa::{ use acvm::FieldElement; use fxhash::FxHashMap as HashMap; +use self::capacity_tracker::SliceCapacityTracker; + +pub(crate) mod capacity_tracker; + impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn fill_internal_slices(mut self) -> Ssa { @@ -93,6 +97,12 @@ struct Context<'f> { /// values being used in array operations. /// Maps result -> original value slice_parents: HashMap, + + // Values containing nested slices to be replaced + slice_values: Vec, + + // This is set after collecting information from all instructions + nested_max: Option, } impl<'f> Context<'f> { @@ -105,6 +115,8 @@ impl<'f> Context<'f> { inserter, mapped_slice_values: HashMap::default(), slice_parents: HashMap::default(), + slice_values: Vec::new(), + nested_max: None, } } @@ -120,178 +132,60 @@ impl<'f> Context<'f> { // Fetch SSA values potentially with internal slices let instructions = self.inserter.function.dfg[block].take_instructions(); - // Values containing nested slices to be replaced - let mut slice_values = Vec::new(); // Maps SSA array ID representing slice contents to its length and a list of its potential internal slices // This map is constructed once for an array constant and is then updated // according to the rules in `collect_slice_information`. let mut slice_sizes: HashMap)> = HashMap::default(); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); // Update the slice sizes map to help find the potential max size of each nested slice. for instruction in instructions.iter() { - self.collect_slice_information(*instruction, &mut slice_values, &mut slice_sizes); - } - - // Add back every instruction with the updated nested slices. - for instruction in instructions { - self.push_updated_instruction(instruction, &slice_values, &slice_sizes, block); + let results = self.inserter.function.dfg.instruction_results(*instruction).to_vec(); + let instruction = &self.inserter.function.dfg[*instruction]; + capacity_tracker.collect_slice_information(instruction, &mut slice_sizes, results); } - self.inserter.map_terminator_in_place(block); - } - - /// Determine how the slice sizes map needs to be updated according to the provided instruction. - fn collect_slice_information( - &mut self, - instruction: InstructionId, - slice_values: &mut Vec, - slice_sizes: &mut HashMap)>, - ) { - let results = self.inserter.function.dfg.instruction_results(instruction); - match &self.inserter.function.dfg[instruction] { - Instruction::ArrayGet { array, .. } => { - let array_typ = self.inserter.function.dfg.type_of_value(*array); - let array_value = &self.inserter.function.dfg[*array]; - // If we have an SSA value containing nested slices we should mark it - // as a slice that potentially requires to be filled with dummy data. - if matches!(array_value, Value::Array { .. }) && array_typ.contains_slice_element() - { - slice_values.push(*array); - // Initial insertion into the slice sizes map - // Any other insertions should only occur if the value is already - // a part of the map. - self.compute_slice_sizes(*array, slice_sizes); - } - - let res_typ = self.inserter.function.dfg.type_of_value(results[0]); - if res_typ.contains_slice_element() { - if let Some(inner_sizes) = slice_sizes.get_mut(array) { - // Include the result in the parent array potential children - // If the result has internal slices and is called in an array set - // we could potentially have a new larger slice which we need to account for - inner_sizes.1.push(results[0]); - self.slice_parents.insert(results[0], *array); - - let inner_sizes_iter = inner_sizes.1.clone(); - for slice_value in inner_sizes_iter { - let inner_slice = slice_sizes.get(&slice_value).unwrap_or_else(|| { - panic!("ICE: should have inner slice set for {slice_value}") - }); - slice_sizes.insert(results[0], inner_slice.clone()); - if slice_value != results[0] { - self.mapped_slice_values.insert(slice_value, results[0]); - } - } - } - } - } - Instruction::ArraySet { array, value, .. } => { - let array_typ = self.inserter.function.dfg.type_of_value(*array); - let array_value = &self.inserter.function.dfg[*array]; - // If we have an SSA value containing nested slices we should mark it - // as a slice that potentially requires to be filled with dummy data. - if matches!(array_value, Value::Array { .. }) && array_typ.contains_slice_element() - { - slice_values.push(*array); - // Initial insertion into the slice sizes map - // Any other insertions should only occur if the value is already - // a part of the map. - self.compute_slice_sizes(*array, slice_sizes); - } - - let value_typ = self.inserter.function.dfg.type_of_value(*value); - if value_typ.contains_slice_element() { - self.compute_slice_sizes(*value, slice_sizes); + self.slice_values = capacity_tracker.constant_nested_slices(); + self.mapped_slice_values = capacity_tracker.slice_values_map(); + self.slice_parents = capacity_tracker.slice_parents_map(); - let inner_sizes = slice_sizes.get_mut(array).expect("ICE expected slice sizes"); - inner_sizes.1.push(*value); - } + // Compute slice nested max + // Here we are currently assuming the nested max throughout the block + // TODO: This can be optimized to better track the nested max for specific slices. + // TODO: Tracking the nested max for each slice was simpler before enabling the merging of nested slices. + let mut nested_max = 0; + for (slice_value, size_and_children) in slice_sizes.iter() { + let typ = self.inserter.function.dfg.type_of_value(*slice_value); + let depth = Self::compute_nested_slice_depth(&typ); - if let Some(inner_sizes) = slice_sizes.get_mut(array) { - let inner_sizes = inner_sizes.clone(); + let mut max_sizes = Vec::new(); + max_sizes.resize(depth, 0); - slice_sizes.insert(results[0], inner_sizes); + max_sizes[0] = size_and_children.0; + self.compute_slice_max_sizes(*slice_value, &slice_sizes, &mut max_sizes, 1); - self.mapped_slice_values.insert(*array, results[0]); - self.slice_parents.insert(results[0], *array); + for size in max_sizes[1..].iter() { + if *size > nested_max { + nested_max = *size; } } - Instruction::Call { func, arguments } => { - let func = &self.inserter.function.dfg[*func]; - if let Value::Intrinsic(intrinsic) = func { - let (argument_index, result_index) = match intrinsic { - Intrinsic::SlicePushBack - | Intrinsic::SlicePushFront - | Intrinsic::SlicePopBack - | Intrinsic::SliceInsert - | Intrinsic::SliceRemove => (1, 1), - // `pop_front` returns the popped element, and then the respective slice. - // This means in the case of a slice with structs, the result index of the popped slice - // will change depending on the number of elements in the struct. - // For example, a slice with four elements will look as such in SSA: - // v3, v4, v5, v6, v7, v8 = call slice_pop_front(v1, v2) - // where v7 is the slice length and v8 is the popped slice itself. - Intrinsic::SlicePopFront => (1, results.len() - 1), - _ => return, - }; - let slice_contents = arguments[argument_index]; - match intrinsic { - Intrinsic::SlicePushBack - | Intrinsic::SlicePushFront - | Intrinsic::SliceInsert => { - for arg in &arguments[(argument_index + 1)..] { - let element_typ = self.inserter.function.dfg.type_of_value(*arg); - if element_typ.contains_slice_element() { - slice_values.push(*arg); - self.compute_slice_sizes(*arg, slice_sizes); - } - } - if let Some(inner_sizes) = slice_sizes.get_mut(&slice_contents) { - inner_sizes.0 += 1; - - let inner_sizes = inner_sizes.clone(); - slice_sizes.insert(results[result_index], inner_sizes); + } + self.nested_max = Some(nested_max); - self.mapped_slice_values - .insert(slice_contents, results[result_index]); - self.slice_parents.insert(results[result_index], slice_contents); - } - } - Intrinsic::SlicePopBack - | Intrinsic::SliceRemove - | Intrinsic::SlicePopFront => { - // We do not decrement the size on intrinsics that could remove values from a slice. - // This is because we could potentially go back to the smaller slice and not fill in dummies. - // This pass should be tracking the potential max that a slice ***could be*** - if let Some(inner_sizes) = slice_sizes.get(&slice_contents) { - let inner_sizes = inner_sizes.clone(); - slice_sizes.insert(results[result_index], inner_sizes); - - self.mapped_slice_values - .insert(slice_contents, results[result_index]); - self.slice_parents.insert(results[result_index], slice_contents); - } - } - _ => {} - } - } - } - _ => {} + // Add back every instruction with the updated nested slices. + for instruction in instructions.iter() { + self.push_updated_instruction(*instruction, block); } + + self.inserter.map_terminator_in_place(block); } - fn push_updated_instruction( - &mut self, - instruction: InstructionId, - slice_values: &[ValueId], - slice_sizes: &HashMap)>, - block: BasicBlockId, - ) { + fn push_updated_instruction(&mut self, instruction: InstructionId, block: BasicBlockId) { match &self.inserter.function.dfg[instruction] { Instruction::ArrayGet { array, .. } | Instruction::ArraySet { array, .. } => { - if slice_values.contains(array) { + if self.slice_values.contains(array) { let (new_array_op_instr, call_stack) = - self.get_updated_array_op_instr(*array, slice_sizes, instruction); + self.get_updated_array_op_instr(*array, instruction); self.inserter.push_instruction_value( new_array_op_instr, instruction, @@ -302,35 +196,20 @@ impl<'f> Context<'f> { self.inserter.push_instruction(instruction, block); } } - Instruction::Call { func: _, arguments } => { + Instruction::Call { arguments, .. } => { let mut args_to_replace = Vec::new(); for (i, arg) in arguments.iter().enumerate() { let element_typ = self.inserter.function.dfg.type_of_value(*arg); - if slice_values.contains(arg) && element_typ.contains_slice_element() { + if self.slice_values.contains(arg) && element_typ.contains_slice_element() { args_to_replace.push((i, *arg)); } } if args_to_replace.is_empty() { self.inserter.push_instruction(instruction, block); } else { - // Using the original slice is ok to do as during collection of slice information - // we guarantee that only the arguments to slice intrinsic calls can be replaced. - let slice_contents = arguments[1]; - - let element_typ = self.inserter.function.dfg.type_of_value(arguments[1]); - let elem_depth = Self::compute_nested_slice_depth(&element_typ); - - let mut max_sizes = Vec::new(); - max_sizes.resize(elem_depth, 0); - // We want the max for the parent of the argument - let parent = self.resolve_slice_parent(slice_contents); - self.compute_slice_max_sizes(parent, slice_sizes, &mut max_sizes, 0); - for (index, arg) in args_to_replace { let element_typ = self.inserter.function.dfg.type_of_value(arg); - max_sizes.remove(0); - let new_array = - self.attach_slice_dummies(&element_typ, Some(arg), false, &max_sizes); + let new_array = self.attach_slice_dummies(&element_typ, Some(arg), false); let instruction_id = instruction; let (instruction, call_stack) = @@ -363,26 +242,11 @@ impl<'f> Context<'f> { fn get_updated_array_op_instr( &mut self, array_id: ValueId, - slice_sizes: &HashMap)>, instruction: InstructionId, ) -> (Instruction, CallStack) { - let mapped_slice_value = self.resolve_slice_value(array_id); - - let (current_size, _) = slice_sizes - .get(&mapped_slice_value) - .unwrap_or_else(|| panic!("should have slice sizes: {mapped_slice_value}")); - - let mut max_sizes = Vec::new(); - let typ = self.inserter.function.dfg.type_of_value(array_id); - let depth = Self::compute_nested_slice_depth(&typ); - max_sizes.resize(depth, 0); - - max_sizes[0] = *current_size; - self.compute_slice_max_sizes(array_id, slice_sizes, &mut max_sizes, 1); - - let new_array = self.attach_slice_dummies(&typ, Some(array_id), true, &max_sizes); + let new_array = self.attach_slice_dummies(&typ, Some(array_id), true); let instruction_id = instruction; let (instruction, call_stack) = self.inserter.map_instruction(instruction_id); let new_array_op_instr = match instruction { @@ -403,7 +267,6 @@ impl<'f> Context<'f> { typ: &Type, value: Option, is_parent_slice: bool, - max_sizes: &[usize], ) -> ValueId { match typ { Type::Numeric(_) => { @@ -421,20 +284,21 @@ impl<'f> Context<'f> { let mut array = im::Vector::new(); for _ in 0..*len { for typ in element_types.iter() { - array.push_back(self.attach_slice_dummies(typ, None, false, max_sizes)); + array.push_back(self.attach_slice_dummies(typ, None, false)); } } self.inserter.function.dfg.make_array(array, typ.clone()) } } Type::Slice(element_types) => { - let (current_size, max_sizes) = - max_sizes.split_first().expect("ICE: Missing internal slice max size"); - let mut max_size = *current_size; - if let Some(value) = value { + let mut max_size = self + .nested_max + .expect("ICE: should have nested max when attaching slice dummy data"); + + if let Some(value_id) = value { let mut slice = im::Vector::new(); - let value = self.inserter.function.dfg[value].clone(); + let value = self.inserter.function.dfg[value_id].clone(); let array = match value { Value::Array { array, .. } => array, _ => { @@ -445,6 +309,7 @@ impl<'f> Context<'f> { if is_parent_slice { max_size = array.len() / element_types.len(); } + for i in 0..max_size { for (element_index, element_type) in element_types.iter().enumerate() { let index_usize = i * element_types.len() + element_index; @@ -455,7 +320,6 @@ impl<'f> Context<'f> { element_type, maybe_value, false, - max_sizes, )); } } @@ -465,7 +329,7 @@ impl<'f> Context<'f> { let mut slice = im::Vector::new(); for _ in 0..max_size { for typ in element_types.iter() { - slice.push_back(self.attach_slice_dummies(typ, None, false, max_sizes)); + slice.push_back(self.attach_slice_dummies(typ, None, false)); } } self.inserter.function.dfg.make_array(slice, typ.clone()) @@ -480,48 +344,6 @@ impl<'f> Context<'f> { } } - // This methods computes a map representing a nested slice. - // The method also automatically computes the given max slice size - // at each depth of the recursive type. - // For example if we had a next slice - fn compute_slice_sizes( - &self, - array_id: ValueId, - slice_sizes: &mut HashMap)>, - ) { - if let Value::Array { array, typ } = &self.inserter.function.dfg[array_id].clone() { - if let Type::Slice(_) = typ { - let element_size = typ.element_size(); - let len = array.len() / element_size; - let mut slice_value = (len, vec![]); - for value in array { - let typ = self.inserter.function.dfg.type_of_value(*value); - if let Type::Slice(_) = typ { - slice_value.1.push(*value); - self.compute_slice_sizes(*value, slice_sizes); - } - } - // Mark the correct max size based upon an array values internal structure - let mut max_size = 0; - for inner_value in slice_value.1.iter() { - let inner_slice = - slice_sizes.get(inner_value).expect("ICE: should have inner slice set"); - if inner_slice.0 > max_size { - max_size = inner_slice.0; - } - } - for inner_value in slice_value.1.iter() { - let inner_slice = - slice_sizes.get_mut(inner_value).expect("ICE: should have inner slice set"); - if inner_slice.0 < max_size { - inner_slice.0 = max_size; - } - } - slice_sizes.insert(array_id, slice_value); - } - } - } - /// Determine the maximum possible size of an internal slice at each /// layer of a nested slice. /// @@ -563,10 +385,23 @@ impl<'f> Context<'f> { /// The depth follows the recursive type structure of a slice. fn compute_nested_slice_depth(typ: &Type) -> usize { let mut depth = 0; - if let Type::Slice(element_types) = typ { - depth += 1; - for typ in element_types.as_ref() { - depth += Self::compute_nested_slice_depth(typ); + match typ { + Type::Slice(element_types) => { + depth += 1; + for typ in element_types.as_ref() { + depth += Self::compute_nested_slice_depth(typ); + } + } + Type::Reference(element) => { + depth += Self::compute_nested_slice_depth(element); + } + Type::Array(element_types, _) => { + for typ in element_types.as_ref() { + depth += Self::compute_nested_slice_depth(typ); + } + } + _ => { + // Do nothing } } depth diff --git a/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices/capacity_tracker.rs b/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices/capacity_tracker.rs new file mode 100644 index 00000000000..82cef63854a --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/fill_internal_slices/capacity_tracker.rs @@ -0,0 +1,306 @@ +use crate::ssa::ir::{ + dfg::DataFlowGraph, + instruction::{Instruction, Intrinsic}, + types::Type, + value::{Value, ValueId}, +}; + +use fxhash::FxHashMap as HashMap; + +pub(crate) struct SliceCapacityTracker<'a> { + dfg: &'a DataFlowGraph, + /// Maps SSA array values representing a slice's contents to its updated array value + /// after an array set or a slice intrinsic operation. + /// Maps original value -> result + mapped_slice_values: HashMap, + + /// Maps an updated array value following an array operation to its previous value. + /// When used in conjunction with `mapped_slice_values` we form a two way map of all array + /// values being used in array operations. + /// Maps result -> original value + slice_parents: HashMap, + + // Values containing nested slices to be replaced + slice_values: Vec, +} + +impl<'a> SliceCapacityTracker<'a> { + pub(crate) fn new(dfg: &'a DataFlowGraph) -> Self { + SliceCapacityTracker { + dfg, + mapped_slice_values: HashMap::default(), + slice_parents: HashMap::default(), + slice_values: Vec::new(), + } + } + + /// Determine how the slice sizes map needs to be updated according to the provided instruction. + pub(crate) fn collect_slice_information( + &mut self, + instruction: &Instruction, + slice_sizes: &mut HashMap)>, + results: Vec, + ) { + match instruction { + Instruction::ArrayGet { array, .. } => { + let array_typ = self.dfg.type_of_value(*array); + let array_value = &self.dfg[*array]; + // If we have an SSA value containing nested slices we should mark it + // as a slice that potentially requires to be filled with dummy data. + if matches!(array_value, Value::Array { .. }) && array_typ.contains_slice_element() + { + self.slice_values.push(*array); + // Initial insertion into the slice sizes map + // Any other insertions should only occur if the value is already + // a part of the map. + self.compute_slice_sizes(*array, slice_sizes); + } + + let res_typ = self.dfg.type_of_value(results[0]); + if res_typ.contains_slice_element() { + if let Some(inner_sizes) = slice_sizes.get_mut(array) { + // Include the result in the parent array potential children + // If the result has internal slices and is called in an array set + // we could potentially have a new larger slice which we need to account for + inner_sizes.1.push(results[0]); + self.slice_parents.insert(results[0], *array); + + let inner_sizes_iter = inner_sizes.1.clone(); + for slice_value in inner_sizes_iter { + let inner_slice = slice_sizes.get(&slice_value).unwrap_or_else(|| { + panic!("ICE: should have inner slice set for {slice_value}") + }); + let previous_res_size = slice_sizes.get(&results[0]); + if let Some(previous_res_size) = previous_res_size { + if inner_slice.0 > previous_res_size.0 { + slice_sizes.insert(results[0], inner_slice.clone()); + } + } else { + slice_sizes.insert(results[0], inner_slice.clone()); + } + let resolved_result = self.resolve_slice_value(results[0]); + if resolved_result != slice_value { + self.mapped_slice_values.insert(slice_value, results[0]); + } + } + } + } + } + Instruction::ArraySet { array, value, .. } => { + let array_typ = self.dfg.type_of_value(*array); + let array_value = &self.dfg[*array]; + // If we have an SSA value containing nested slices we should mark it + // as a slice that potentially requires to be filled with dummy data. + if matches!(array_value, Value::Array { .. }) && array_typ.contains_slice_element() + { + self.slice_values.push(*array); + // Initial insertion into the slice sizes map + // Any other insertions should only occur if the value is already + // a part of the map. + self.compute_slice_sizes(*array, slice_sizes); + } + + let value_typ = self.dfg.type_of_value(*value); + if value_typ.contains_slice_element() { + self.compute_slice_sizes(*value, slice_sizes); + + let inner_sizes = slice_sizes.get_mut(array).expect("ICE expected slice sizes"); + inner_sizes.1.push(*value); + } + + if let Some(inner_sizes) = slice_sizes.get_mut(array) { + let inner_sizes = inner_sizes.clone(); + + slice_sizes.insert(results[0], inner_sizes); + + if let Some(fetched_val) = self.mapped_slice_values.get(&results[0]) { + if *fetched_val != *array { + self.mapped_slice_values.insert(*array, results[0]); + } + } else if *array != results[0] { + self.mapped_slice_values.insert(*array, results[0]); + } + + self.slice_parents.insert(results[0], *array); + } + } + Instruction::Call { func, arguments } => { + let func = &self.dfg[*func]; + if let Value::Intrinsic(intrinsic) = func { + let (argument_index, result_index) = match intrinsic { + Intrinsic::SlicePushBack + | Intrinsic::SlicePushFront + | Intrinsic::SlicePopBack + | Intrinsic::SliceInsert + | Intrinsic::SliceRemove => (1, 1), + // `pop_front` returns the popped element, and then the respective slice. + // This means in the case of a slice with structs, the result index of the popped slice + // will change depending on the number of elements in the struct. + // For example, a slice with four elements will look as such in SSA: + // v3, v4, v5, v6, v7, v8 = call slice_pop_front(v1, v2) + // where v7 is the slice length and v8 is the popped slice itself. + Intrinsic::SlicePopFront => (1, results.len() - 1), + _ => return, + }; + let slice_contents = arguments[argument_index]; + match intrinsic { + Intrinsic::SlicePushBack + | Intrinsic::SlicePushFront + | Intrinsic::SliceInsert => { + for arg in &arguments[(argument_index + 1)..] { + let element_typ = self.dfg.type_of_value(*arg); + if element_typ.contains_slice_element() { + self.slice_values.push(*arg); + self.compute_slice_sizes(*arg, slice_sizes); + } + } + if let Some(inner_sizes) = slice_sizes.get_mut(&slice_contents) { + inner_sizes.0 += 1; + + let inner_sizes = inner_sizes.clone(); + slice_sizes.insert(results[result_index], inner_sizes); + + if let Some(fetched_val) = + self.mapped_slice_values.get(&results[result_index]) + { + if *fetched_val != slice_contents { + self.mapped_slice_values + .insert(slice_contents, results[result_index]); + } + } else if slice_contents != results[result_index] { + self.mapped_slice_values + .insert(slice_contents, results[result_index]); + } + + self.slice_parents.insert(results[result_index], slice_contents); + } + } + Intrinsic::SlicePopBack + | Intrinsic::SliceRemove + | Intrinsic::SlicePopFront => { + // We do not decrement the size on intrinsics that could remove values from a slice. + // This is because we could potentially go back to the smaller slice and not fill in dummies. + // This pass should be tracking the potential max that a slice ***could be*** + if let Some(inner_sizes) = slice_sizes.get(&slice_contents) { + let inner_sizes = inner_sizes.clone(); + slice_sizes.insert(results[result_index], inner_sizes); + + if let Some(fetched_val) = + self.mapped_slice_values.get(&results[result_index]) + { + if *fetched_val != slice_contents { + self.mapped_slice_values + .insert(slice_contents, results[result_index]); + } + } else if slice_contents != results[result_index] { + self.mapped_slice_values + .insert(slice_contents, results[result_index]); + } + + self.slice_parents.insert(results[result_index], slice_contents); + } + } + _ => {} + } + } + } + Instruction::Store { address, value } => { + let value_typ = self.dfg.type_of_value(*value); + if value_typ.contains_slice_element() { + self.compute_slice_sizes(*value, slice_sizes); + + let mut inner_sizes = slice_sizes.get(value).unwrap_or_else(|| { + panic!("ICE: should have inner slice set for value {value} being stored at {address}") + }).clone(); + + if let Some(previous_store) = slice_sizes.get(address) { + inner_sizes.1.append(&mut previous_store.1.clone()); + } + + slice_sizes.insert(*address, inner_sizes); + } + } + Instruction::Load { address } => { + let load_typ = self.dfg.type_of_value(*address); + if load_typ.contains_slice_element() { + let result = results[0]; + let mut inner_sizes = slice_sizes.get(address).unwrap_or_else(|| { + panic!("ICE: should have inner slice set at addres {address} being loaded into {result}") + }).clone(); + + if let Some(previous_load_value) = slice_sizes.get(&result) { + inner_sizes.1.append(&mut previous_load_value.1.clone()); + } + + slice_sizes.insert(result, inner_sizes); + } + } + _ => {} + } + } + + // This methods computes a map representing a nested slice. + // The method also automatically computes the given max slice size + // at each depth of the recursive type. + // For example if we had a next slice + pub(crate) fn compute_slice_sizes( + &self, + array_id: ValueId, + slice_sizes: &mut HashMap)>, + ) { + if let Value::Array { array, typ } = &self.dfg[array_id].clone() { + if let Type::Slice(_) = typ { + let element_size = typ.element_size(); + let len = array.len() / element_size; + let mut slice_value = (len, vec![]); + for value in array { + let typ = self.dfg.type_of_value(*value); + if let Type::Slice(_) = typ { + slice_value.1.push(*value); + self.compute_slice_sizes(*value, slice_sizes); + } + } + // Mark the correct max size based upon an array values internal structure + let mut max_size = 0; + for inner_value in slice_value.1.iter() { + let inner_slice = + slice_sizes.get(inner_value).expect("ICE: should have inner slice set"); + if inner_slice.0 > max_size { + max_size = inner_slice.0; + } + } + for inner_value in slice_value.1.iter() { + let inner_slice = + slice_sizes.get_mut(inner_value).expect("ICE: should have inner slice set"); + if inner_slice.0 < max_size { + inner_slice.0 = max_size; + } + } + slice_sizes.insert(array_id, slice_value); + } + } + } + + /// Resolves a ValueId representing a slice's contents to its updated value. + /// If there is no resolved value for the supplied value, the value which + /// was passed to the method is returned. + fn resolve_slice_value(&self, array_id: ValueId) -> ValueId { + let val = match self.mapped_slice_values.get(&array_id) { + Some(value) => self.resolve_slice_value(*value), + None => array_id, + }; + val + } + + pub(crate) fn constant_nested_slices(&mut self) -> Vec { + std::mem::take(&mut self.slice_values) + } + + pub(crate) fn slice_parents_map(&mut self) -> HashMap { + std::mem::take(&mut self.slice_parents) + } + + pub(crate) fn slice_values_map(&mut self) -> HashMap { + std::mem::take(&mut self.mapped_slice_values) + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 6bdf2ab1c0a..c6113bde8f4 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -148,6 +148,7 @@ use crate::ssa::{ types::Type, value::ValueId, }, + opt::fill_internal_slices::capacity_tracker::SliceCapacityTracker, ssa_gen::Ssa, }; @@ -184,17 +185,6 @@ struct Context<'f> { /// between inlining of branches. store_values: HashMap, - /// Maps an address to the old and new value of the element at that address - /// The difference between this map and store_values is that this stores - /// the old and new value of an element from the outer block whose jmpif - /// terminator is being flattened. - /// - /// This map persists throughout the flattening process, where addresses - /// are overwritten as new stores are found. This overwriting is the desired behavior, - /// as we want the most update to date value to be stored at a given address as - /// we walk through blocks to flatten. - outer_block_stores: HashMap, - /// Stores all allocations local to the current branch. /// Since these branches are local to the current branch (ie. only defined within one branch of /// an if expression), they should not be merged with their previous value or stored value in @@ -209,6 +199,8 @@ struct Context<'f> { /// condition. If we are under multiple conditions (a nested if), the topmost condition is /// the most recent condition combined with all previous conditions via `And` instructions. conditions: Vec<(BasicBlockId, ValueId)>, + + slice_sizes: HashMap)>, } pub(crate) struct Store { @@ -239,7 +231,7 @@ fn flatten_function_cfg(function: &mut Function) { local_allocations: HashSet::new(), branch_ends, conditions: Vec::new(), - outer_block_stores: HashMap::default(), + slice_sizes: HashMap::default(), }; context.flatten(); } @@ -262,21 +254,18 @@ impl<'f> Context<'f> { /// Returns the last block to be inlined. This is either the return block of the function or, /// if self.conditions is not empty, the end block of the most recent condition. fn handle_terminator(&mut self, block: BasicBlockId) -> BasicBlockId { - if let TerminatorInstruction::JmpIf { .. } = - self.inserter.function.dfg[block].unwrap_terminator() - { - // Find stores in the outer block and insert into the `outer_block_stores` map. - // Not using this map can lead to issues when attempting to merge slices. - // When inlining a branch end, only the then branch and the else branch are checked for stores. - // However, there are cases where we want to load a value that comes from the outer block - // that we are handling the terminator for here. - let instructions = self.inserter.function.dfg[block].instructions().to_vec(); - for instruction in instructions { - let (instruction, _) = self.inserter.map_instruction(instruction); - if let Instruction::Store { address, value } = instruction { - self.outer_block_stores.insert(address, value); - } - } + // As we recursively flatten inner blocks, we need to track the slice information + // for the outer block before we start recursively inlining + let outer_block_instructions = self.inserter.function.dfg[block].instructions(); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + for instruction in outer_block_instructions { + let results = self.inserter.function.dfg.instruction_results(*instruction); + let instruction = &self.inserter.function.dfg[*instruction]; + capacity_tracker.collect_slice_information( + instruction, + &mut self.slice_sizes, + results.to_vec(), + ); } match self.inserter.function.dfg[block].unwrap_terminator() { @@ -494,12 +483,16 @@ impl<'f> Context<'f> { }); let block = self.inserter.function.entry_block(); - let mut value_merger = ValueMerger::new( - &mut self.inserter.function.dfg, - block, - Some(&self.store_values), - Some(&self.outer_block_stores), - ); + + // Make sure we have tracked the slice sizes of any block arguments + let capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + for (then_arg, else_arg) in args.iter() { + capacity_tracker.compute_slice_sizes(*then_arg, &mut self.slice_sizes); + capacity_tracker.compute_slice_sizes(*else_arg, &mut self.slice_sizes); + } + + let mut value_merger = + ValueMerger::new(&mut self.inserter.function.dfg, block, &mut self.slice_sizes); // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { @@ -543,12 +536,8 @@ impl<'f> Context<'f> { let block = self.inserter.function.entry_block(); - let mut value_merger = ValueMerger::new( - &mut self.inserter.function.dfg, - block, - Some(&self.store_values), - Some(&self.outer_block_stores), - ); + let mut value_merger = + ValueMerger::new(&mut self.inserter.function.dfg, block, &mut self.slice_sizes); // Merging must occur in a separate loop as we cannot borrow `self` as mutable while `value_merger` does let mut new_values = HashMap::default(); @@ -571,6 +560,16 @@ impl<'f> Context<'f> { .insert(address, Store { old_value: *old_value, new_value: value }); } } + + // Collect any potential slice information on the stores we are merging + for (address, (_, _, _)) in &new_map { + let value = new_values[address]; + let address = *address; + let instruction = Instruction::Store { address, value }; + + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + capacity_tracker.collect_slice_information(&instruction, &mut self.slice_sizes, vec![]); + } } fn remember_store(&mut self, address: ValueId, new_value: ValueId) { @@ -582,6 +581,11 @@ impl<'f> Context<'f> { let load_type = Some(vec![self.inserter.function.dfg.type_of_value(new_value)]); let old_value = self.insert_instruction_with_typevars(load, load_type).first(); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + // Need this or else we will be missing slice stores that we wish to merge + let store = Instruction::Store { address: old_value, value: new_value }; + capacity_tracker.collect_slice_information(&store, &mut self.slice_sizes, vec![]); + self.store_values.insert(address, Store { old_value, new_value }); } } @@ -602,8 +606,15 @@ impl<'f> Context<'f> { // unnecessary, when removing it actually causes an aliasing/mutability error. let instructions = self.inserter.function.dfg[destination].instructions().to_vec(); - for instruction in instructions { - self.push_instruction(instruction); + for instruction in instructions.iter() { + let results = self.push_instruction(*instruction); + let (instruction, _) = self.inserter.map_instruction(*instruction); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + capacity_tracker.collect_slice_information( + &instruction, + &mut self.slice_sizes, + results, + ); } self.handle_terminator(destination) @@ -615,7 +626,7 @@ impl<'f> Context<'f> { /// As a result, the instruction that will be pushed will actually be a new instruction /// with a different InstructionId from the original. The results of the given instruction /// will also be mapped to the results of the new instruction. - fn push_instruction(&mut self, id: InstructionId) { + fn push_instruction(&mut self, id: InstructionId) -> Vec { let (instruction, call_stack) = self.inserter.map_instruction(id); let instruction = self.handle_instruction_side_effects(instruction, call_stack.clone()); let is_allocate = matches!(instruction, Instruction::Allocate); @@ -628,6 +639,8 @@ impl<'f> Context<'f> { if is_allocate { self.local_allocations.insert(results.first()); } + + results.results().into_owned() } /// If we are currently in a branch, we need to modify constrain instructions @@ -694,6 +707,7 @@ impl<'f> Context<'f> { for (address, store) in &then_branch.store_values { let address = *address; let value = store.old_value; + self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs index 446560f45f1..cdaac772048 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs @@ -4,35 +4,25 @@ use fxhash::FxHashMap as HashMap; use crate::ssa::ir::{ basic_block::BasicBlockId, dfg::{CallStack, DataFlowGraph}, - instruction::{BinaryOp, Instruction, Intrinsic}, + instruction::{BinaryOp, Instruction}, types::Type, - value::{Value, ValueId}, + value::ValueId, }; -use crate::ssa::opt::flatten_cfg::Store; - pub(crate) struct ValueMerger<'a> { dfg: &'a mut DataFlowGraph, block: BasicBlockId, - store_values: Option<&'a HashMap>, - outer_block_stores: Option<&'a HashMap>, - slice_sizes: HashMap, + // Maps SSA array values to their size and any respective nested slice children it may have + slice_sizes: &'a mut HashMap)>, } impl<'a> ValueMerger<'a> { pub(crate) fn new( dfg: &'a mut DataFlowGraph, block: BasicBlockId, - store_values: Option<&'a HashMap>, - outer_block_stores: Option<&'a HashMap>, + slice_sizes: &'a mut HashMap)>, ) -> Self { - ValueMerger { - dfg, - block, - store_values, - outer_block_stores, - slice_sizes: HashMap::default(), - } + ValueMerger { dfg, block, slice_sizes } } /// Merge two values a and b from separate basic blocks to a single value. @@ -50,6 +40,13 @@ impl<'a> ValueMerger<'a> { then_value: ValueId, else_value: ValueId, ) -> ValueId { + let then_type = self.dfg.type_of_value(then_value); + let else_type = self.dfg.type_of_value(else_value); + assert_eq!( + then_type, else_type, + "Expected values merged to be of the same type but found {then_type} and {else_type}" + ); + match self.dfg.type_of_value(then_value) { Type::Numeric(_) => { self.merge_numeric_values(then_condition, else_condition, then_value, else_value) @@ -184,14 +181,15 @@ impl<'a> ValueMerger<'a> { _ => panic!("Expected slice type"), }; - let then_len = self.get_slice_length(then_value_id); - self.slice_sizes.insert(then_value_id, then_len); + let then_len = self.slice_sizes.get(&then_value_id).unwrap_or_else(|| { + panic!("ICE: Merging values during flattening encountered slice {then_value_id} without a preset size"); + }).0; - let else_len = self.get_slice_length(else_value_id); - self.slice_sizes.insert(else_value_id, else_len); + let else_len = self.slice_sizes.get(&else_value_id).unwrap_or_else(|| { + panic!("ICE: Merging values during flattening encountered slice {else_value_id} without a preset size"); + }).0; let len = then_len.max(else_len); - for i in 0..len { for (element_index, element_type) in element_types.iter().enumerate() { let index_usize = i * element_types.len() + element_index; @@ -203,7 +201,7 @@ impl<'a> ValueMerger<'a> { let mut get_element = |array, typevars, len| { // The smaller slice is filled with placeholder data. Codegen for slice accesses must // include checks against the dynamic slice length so that this placeholder data is not incorrectly accessed. - if len <= index_usize { + let res = if len <= index_usize { self.make_slice_dummy_data(element_type) } else { let get = Instruction::ArrayGet { array, index }; @@ -215,11 +213,38 @@ impl<'a> ValueMerger<'a> { CallStack::new(), ) .first() + }; + + if let Type::Slice(_) = element_type { + let inner_sizes = self + .slice_sizes + .get_mut(&array) + .unwrap_or_else(|| panic!("should have slice sizes")); + + let inner_sizes_iter = inner_sizes.1.clone(); + for slice_value in inner_sizes_iter { + let inner_slice = + self.slice_sizes.get(&slice_value).unwrap_or_else(|| { + panic!("ICE: should have inner slice set for {slice_value}") + }); + let previous_res_size = self.slice_sizes.get(&res); + if let Some(previous_res_size) = previous_res_size { + if inner_slice.0 > previous_res_size.0 { + self.slice_sizes.insert(res, inner_slice.clone()); + } + } else { + self.slice_sizes.insert(res, inner_slice.clone()); + } + } } + + res }; - let then_element = get_element(then_value_id, typevars.clone(), then_len); - let else_element = get_element(else_value_id, typevars, else_len); + let then_element = + get_element(then_value_id, typevars.clone(), then_len * element_types.len()); + let else_element = + get_element(else_value_id, typevars, else_len * element_types.len()); merged.push_back(self.merge_values( then_condition, @@ -233,82 +258,6 @@ impl<'a> ValueMerger<'a> { self.dfg.make_array(merged, typ) } - fn get_slice_length(&mut self, value_id: ValueId) -> usize { - let value = &self.dfg[value_id]; - match value { - Value::Array { array, .. } => array.len(), - Value::Instruction { instruction: instruction_id, .. } => { - let instruction = &self.dfg[*instruction_id]; - match instruction { - // TODO(#3188): A slice can be the result of an ArrayGet when it is the - // fetched from a slice of slices or as a struct field. - // However, we need to incorporate nested slice support in flattening - // in order for this to be valid - // Instruction::ArrayGet { array, .. } => {} - Instruction::ArraySet { array, .. } => { - let array = *array; - let len = self.get_slice_length(array); - self.slice_sizes.insert(array, len); - len - } - Instruction::Load { address } => { - let outer_block_stores = self.outer_block_stores.expect("ICE: A map of previous stores is required in order to resolve a slice load"); - let store_values = self.store_values.expect("ICE: A map of previous stores is required in order to resolve a slice load"); - let store_value = outer_block_stores - .get(address) - .expect("ICE: load in merger should have store from outer block"); - - if let Some(len) = self.slice_sizes.get(store_value) { - return *len; - } - - let store_value = if let Some(store) = store_values.get(address) { - if let Some(len) = self.slice_sizes.get(&store.new_value) { - return *len; - } - - store.new_value - } else { - *store_value - }; - - self.get_slice_length(store_value) - } - Instruction::Call { func, arguments } => { - let slice_contents = arguments[1]; - let func = &self.dfg[*func]; - match func { - Value::Intrinsic(intrinsic) => match intrinsic { - Intrinsic::SlicePushBack - | Intrinsic::SlicePushFront - | Intrinsic::SliceInsert => { - // `get_slice_length` needs to be called here as it is borrows self as mutable - let initial_len = self.get_slice_length(slice_contents); - self.slice_sizes.insert(slice_contents, initial_len); - initial_len + 1 - } - Intrinsic::SlicePopBack - | Intrinsic::SlicePopFront - | Intrinsic::SliceRemove => { - // `get_slice_length` needs to be called here as it is borrows self as mutable - let initial_len = self.get_slice_length(slice_contents); - self.slice_sizes.insert(slice_contents, initial_len); - initial_len - 1 - } - _ => { - unreachable!("ICE: Intrinsic not supported, got {intrinsic:?}") - } - }, - _ => unreachable!("ICE: Expected intrinsic value but got {func:?}"), - } - } - _ => unreachable!("ICE: Got unexpected instruction: {instruction:?}"), - } - } - _ => unreachable!("ICE: Got unexpected value when resolving slice length {value:?}"), - } - } - /// Construct a dummy value to be attached to the smaller of two slices being merged. /// We need to make sure we follow the internal element type structure of the slice type /// even for dummy data to ensure that we do not have errors later in the compiler, @@ -328,10 +277,15 @@ impl<'a> ValueMerger<'a> { } self.dfg.make_array(array, typ.clone()) } - Type::Slice(_) => { - // TODO(#3188): Need to update flattening to use true user facing length of slices - // to accurately construct dummy data - unreachable!("ICE: Cannot return a slice of slices from an if expression") + Type::Slice(element_types) => { + let mut array = im::Vector::new(); + // Make a slice of size one + // This is ok to do as this is dummy data that goes past a slices dynamic length, + // and there should be a codegen'd access check using this dynamic length before any slice accesses. + for typ in element_types.iter() { + array.push_back(self.make_slice_dummy_data(typ)); + } + self.dfg.make_array(array, typ.clone()) } Type::Reference(_) => { unreachable!("ICE: Merging references is unsupported") diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 95784194d28..62441e138cc 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -8,7 +8,7 @@ mod assert_constant; mod constant_folding; mod defunctionalize; mod die; -mod fill_internal_slices; +pub(crate) mod fill_internal_slices; pub(crate) mod flatten_cfg; mod inlining; mod mem2reg; diff --git a/test_programs/execution_success/slice_dynamic_index/src/main.nr b/test_programs/execution_success/slice_dynamic_index/src/main.nr index 374d2ba4c26..64442098720 100644 --- a/test_programs/execution_success/slice_dynamic_index/src/main.nr +++ b/test_programs/execution_success/slice_dynamic_index/src/main.nr @@ -17,7 +17,6 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { dynamic_slice_index_if(slice, x); dynamic_array_index_if([0, 1, 2, 3, 4], x); dynamic_slice_index_else(slice, x); - dynamic_slice_merge_if(slice, x); dynamic_slice_merge_else(slice, x); dynamic_slice_merge_two_ifs(slice, x); diff --git a/test_programs/execution_success/slice_struct_field/src/main.nr b/test_programs/execution_success/slice_struct_field/src/main.nr index a5b971ada4b..40533642a8a 100644 --- a/test_programs/execution_success/slice_struct_field/src/main.nr +++ b/test_programs/execution_success/slice_struct_field/src/main.nr @@ -28,7 +28,7 @@ fn main(y: pub Field) { let mut x = [foo_one, foo_two]; x = x.push_back(foo_three); x = x.push_back(foo_four); - + assert(x[y - 3].a == 1); let struct_slice = x[y - 3].b; for i in 0..4 { @@ -68,35 +68,11 @@ fn main(y: pub Field) { // Check that switching the lhs and rhs is still valid assert([109, 110, 111] == x[y].bar.inner); - // TODO: Enable merging nested slices - // if y != 2 { - // x[y].a = 50; - // } else { - // x[y].a = 100; - // } - // assert(x[3].a == 50); - // if y == 2 { - // x[y - 1].b = [50, 51, 52]; - // } else { - // x[y - 1].b = [100, 101, 102]; - // } - // assert(x[2].b[0] == 100); - // assert(x[2].b[1] == 101); - // assert(x[2].b[2] == 102); - let q = x.push_back(foo_four); let foo_parent_one = FooParent { parent_arr: [0, 1, 2], foos: x }; let foo_parent_two = FooParent { parent_arr: [3, 4, 5], foos: q }; let mut foo_parents = [foo_parent_one]; foo_parents = foo_parents.push_back(foo_parent_two); - // TODO: make a separate test for entirely compile time - // foo_parents[1].foos.push_back(foo_four); - // TODO: Merging nested slices is broken - // if y == 3 { - // foo_parents[y - 2].foos[y - 1].b[y - 1] = 5000; - // } else { - // foo_parents[y - 2].foos[y - 1].b[y - 1] = 1000; - // } assert(foo_parents[y - 2].foos[y - 2].b[y - 1] == 21); foo_parents[y - 2].foos[y - 2].b[y - 1] = 5000; @@ -120,13 +96,14 @@ fn main(y: pub Field) { foo_parents[y - 2].foos[y - 1].a = 50; assert(foo_parents[y - 2].foos[y - 1].a == 50); + // This slice should be altered from the if statement above let b_array = foo_parents[y - 2].foos[y - 1].b; assert(b_array[0] == 8); assert(b_array[1] == 9); assert(b_array[2] == 22); assert(b_array.len() == 3); - // // Test setting a nested array with non-dynamic + // Test setting a nested array with non-dynamic let x = [5, 6, 5000, 21, 100, 101].as_slice(); foo_parents[y - 2].foos[y - 1].b = x; @@ -173,7 +150,7 @@ fn main(y: pub Field) { assert(foo_parents[y - 2].foos.len() == 9); foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo_four); - assert(foo_parents[y - 2].foos.len() == 10); + assert(foo_parents[y - 2].foos.len() == 10); let b_array = foo_parents[y - 2].foos[y - 1].b; assert(b_array[0] == 11); @@ -258,6 +235,7 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(last_foo.b[1] == 15); assert(last_foo.b[2] == 16); assert(last_foo.bar.inner == [109, 110, 111]); + assert(foo_parents[1].foos[2].b.len() == 7); foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_front(foo); assert(foo_parents[1].foos.len() == 6); @@ -265,6 +243,23 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(foo_parents[1].foos[0].b[0] == 14); assert(foo_parents[1].foos[0].b[1] == 15); assert(foo_parents[1].foos[0].b[2] == 16); + assert(foo_parents[1].foos[0].bar.inner == [109, 110, 111]); + assert(foo_parents[1].foos[3].b.len() == 7); + + assert(foo_parents[1].foos[1].a == 1); + assert(foo_parents[1].foos[1].b[0] == 2); + assert(foo_parents[1].foos[1].b[1] == 3); + assert(foo_parents[1].foos[1].b[2] == 20); + assert(foo_parents[1].foos[1].b[3] == 20); + assert(foo_parents[1].foos[1].bar.inner == [100, 101, 102]); + + assert(foo_parents[1].foos[2].a == 4); + assert(foo_parents[1].foos[2].b[0] == 5); + assert(foo_parents[1].foos[2].b[1] == 6); + assert(foo_parents[1].foos[2].b[2] == 5000); + assert(foo_parents[1].foos[2].b[3] == 21); + assert(foo_parents[1].foos[2].bar.inner == [103, 104, 105]); + assert(foo_parents[1].foos[5].a == 10); assert(foo_parents[1].foos[5].b.len() == 3); assert(foo_parents[1].foos[5].b[0] == 11); @@ -275,6 +270,22 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(foo_parents[1].foos[1].bar.inner == [100, 101, 102]); let (first_foo, rest_of_slice) = foo_parents[y - 2].foos.pop_front(); + // Test whether we can operate on the returned slice directly before setting it to another value + assert(rest_of_slice[0].a == 1); + assert(rest_of_slice[0].b.len() == 4); + assert(rest_of_slice[0].b[0] == 2); + assert(rest_of_slice[0].b[1] == 3); + assert(rest_of_slice[0].b[2] == 20); + assert(rest_of_slice[0].b[3] == 20); + assert(rest_of_slice[0].bar.inner == [100, 101, 102]); + + assert(rest_of_slice[1].a == 4); + assert(rest_of_slice[1].b.len() == 4); + assert(rest_of_slice[1].b[0] == 5); + assert(rest_of_slice[1].b[1] == 6); + assert(rest_of_slice[1].b[2] == 5000); + assert(rest_of_slice[1].b[3] == 21); + assert(rest_of_slice[1].bar.inner == [103, 104, 105]); foo_parents[y - 2].foos = rest_of_slice; assert(first_foo.a == 40); @@ -290,6 +301,14 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(foo_parents[1].foos[0].b[3] == 20); assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); + assert(foo_parents[1].foos[1].a == 4); + assert(foo_parents[1].foos[1].b.len() == 4); + assert(foo_parents[1].foos[1].b[0] == 5); + assert(foo_parents[1].foos[1].b[1] == 6); + assert(foo_parents[1].foos[1].b[2] == 5000); + assert(foo_parents[1].foos[1].b[3] == 21); + assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); + test_insert_remove_const_index(foo_parents, y, foo); // Check values before insertion @@ -350,6 +369,29 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); let (rest_of_slice, removed_elem) = foo_parents[y - 2].foos.remove(y - 1); + + // Test whether we can operate on the returned slice directly before setting it to another value + assert(rest_of_slice[1].a == 4); + assert(rest_of_slice[1].b[0] == 5); + assert(rest_of_slice[1].b[1] == 6); + assert(rest_of_slice[1].b[2] == 5000); + assert(rest_of_slice[1].b[3] == 21); + assert(rest_of_slice[1].bar.inner == [103, 104, 105]); + + assert(rest_of_slice[2].a == 50); + assert(rest_of_slice[2].b[0] == 5); + assert(rest_of_slice[2].b[2] == 5000); + assert(rest_of_slice[2].bar.inner == [106, 107, 108]); + + assert(rest_of_slice[3].a == 10); + assert(rest_of_slice[3].b[0] == 11); + assert(rest_of_slice[3].b[2] == 23); + assert(rest_of_slice[3].bar.inner == [109, 110, 111]); + + assert(rest_of_slice[4].b[0] == 11); + assert(rest_of_slice[4].b[2] == 23); + assert(rest_of_slice[4].bar.inner == [109, 110, 111]); + foo_parents[1].foos = rest_of_slice; // Check that the accurate element was removed @@ -370,7 +412,7 @@ fn test_complex_intrinsic_nested_slices(mut foo_parents: [FooParent], y: Field) assert(foo_parents[1].foos[2].b[0] == 5); assert(foo_parents[1].foos[2].b[2] == 5000); assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - + assert(foo_parents[1].foos[3].a == 10); assert(foo_parents[1].foos[3].b[0] == 11); assert(foo_parents[1].foos[3].b[2] == 23); @@ -440,6 +482,29 @@ fn test_insert_remove_const_index(mut foo_parents: [FooParent], y: Field, foo: F assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); let (rest_of_slice, removed_elem) = foo_parents[y - 2].foos.remove(2); + + // Test whether we can operate on the returned slice directly before setting it to another value + assert(rest_of_slice[1].a == 4); + assert(rest_of_slice[1].b[0] == 5); + assert(rest_of_slice[1].b[1] == 6); + assert(rest_of_slice[1].b[2] == 5000); + assert(rest_of_slice[1].b[3] == 21); + assert(rest_of_slice[1].bar.inner == [103, 104, 105]); + + assert(rest_of_slice[2].a == 50); + assert(rest_of_slice[2].b[0] == 5); + assert(rest_of_slice[2].b[2] == 5000); + assert(rest_of_slice[2].bar.inner == [106, 107, 108]); + + assert(rest_of_slice[3].a == 10); + assert(rest_of_slice[3].b[0] == 11); + assert(rest_of_slice[3].b[2] == 23); + assert(rest_of_slice[3].bar.inner == [109, 110, 111]); + + assert(rest_of_slice[4].b[0] == 11); + assert(rest_of_slice[4].b[2] == 23); + assert(rest_of_slice[4].bar.inner == [109, 110, 111]); + foo_parents[1].foos = rest_of_slice; // Check that the accurate element was removed @@ -460,7 +525,7 @@ fn test_insert_remove_const_index(mut foo_parents: [FooParent], y: Field, foo: F assert(foo_parents[1].foos[2].b[0] == 5); assert(foo_parents[1].foos[2].b[2] == 5000); assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); - + assert(foo_parents[1].foos[3].a == 10); assert(foo_parents[1].foos[3].b[0] == 11); assert(foo_parents[1].foos[3].b[2] == 23); diff --git a/test_programs/execution_success/slices_nested_merge/Nargo.toml b/test_programs/execution_success/slices_nested_merge/Nargo.toml new file mode 100644 index 00000000000..85067fcb5eb --- /dev/null +++ b/test_programs/execution_success/slices_nested_merge/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slices_nested_merge" +type = "bin" +authors = [""] +compiler_version = ">=0.20.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/slices_nested_merge/Prover.toml b/test_programs/execution_success/slices_nested_merge/Prover.toml new file mode 100644 index 00000000000..7127baac5bf --- /dev/null +++ b/test_programs/execution_success/slices_nested_merge/Prover.toml @@ -0,0 +1 @@ +y = "3" diff --git a/test_programs/execution_success/slices_nested_merge/src/main.nr b/test_programs/execution_success/slices_nested_merge/src/main.nr new file mode 100644 index 00000000000..a38bedfaadf --- /dev/null +++ b/test_programs/execution_success/slices_nested_merge/src/main.nr @@ -0,0 +1,584 @@ +use dep::std::println; + +struct FooParent { + parent_arr: [Field; 3], + foos: [Foo], +} + +struct Bar { + inner: [Field; 3], +} + +struct Foo { + a: Field, + b: [Field], + bar: Bar, +} + +fn main(y: pub Field) { + let mut b_one = [2, 3, 20]; + b_one = b_one.push_back(20); + let foo_one = Foo { a: 1, b: b_one, bar: Bar { inner: [100, 101, 102] } }; + + let mut b_two = [5, 6, 21]; + b_two = b_two.push_back(21); + let foo_two = Foo { a: 4, b: b_two, bar: Bar { inner: [103, 104, 105] } }; + + let foo_three = Foo { a: 7, b: [8, 9, 22], bar: Bar { inner: [106, 107, 108] } }; + let mut foo_four = Foo { a: 10, b: [11, 12, 23], bar: Bar { inner: [109, 110, 111] } }; + + let mut x = [foo_one, foo_two]; + x = x.push_back(foo_three); + x = x.push_back(foo_four); + + regression_side_effectual_array_ops(x, y); + + let q = x.push_back(foo_four); + let foo_parent_one = FooParent { parent_arr: [0, 1, 2], foos: x }; + let foo_parent_two = FooParent { parent_arr: [3, 4, 5], foos: q }; + let mut foo_parents = [foo_parent_one]; + foo_parents = foo_parents.push_back(foo_parent_two); + + merge_nested_slices(foo_parents, y); +} + +fn merge_nested_slices(mut foo_parents: [FooParent], y: Field) { + let mut foo = Foo { a: 13, b: [14, 15, 16], bar: Bar { inner: [112, 113, 114] } }; + foo.a = 40; + + merge_nested_slice_if_no_push_front_asserts(foo_parents, y, foo); + merge_nested_slice_if(foo_parents, y, foo); + merge_nested_slice_else(foo_parents, y, foo); + merge_nested_if_else_else(foo_parents, y, foo); + merge_nested_if_else_if(foo_parents, y); + merge_nested_two_ifs(foo_parents, y, foo); + merge_nested_mutate_between_ifs(foo_parents, y, foo); + merge_nested_push_then_pop(foo_parents, y, foo); +} + +// This is a regression test as nested slices could possibly not be accurately tracked when certain +// followup asserts on the nested slice are missing. +// This method purely checks that we accurately track the slice of foo even when it is not asserted upon. +fn merge_nested_slice_if_no_push_front_asserts(mut foo_parents: [FooParent], y: Field, foo: Foo) { + if y as u32 < 10 { + assert(foo_parents[y - 2].foos.len() == 5); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[y - 2].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[1] == 15); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_front(foo); + assert(foo_parents[y - 2].foos.len() == 7); + // assert(foo_parents[y - 2].foos[0].a == 40); + } +} + +fn merge_nested_slice_if(mut foo_parents: [FooParent], y: Field, foo: Foo) { + if y as u32 < 10 { + assert(foo_parents[y - 2].foos.len() == 5); + assert(foo_parents[1].foos[3].a == 10); + assert(foo_parents[1].foos[3].b[0] == 11); + assert(foo_parents[1].foos[3].b[1] == 12); + assert(foo_parents[1].foos[3].b[2] == 23); + assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); + + foo_parents[y - 2].foos[y] = foo; + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[y - 2].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[1] == 15); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_front(foo); + assert(foo_parents[y - 2].foos.len() == 7); + assert(foo_parents[1].foos[0].a == 40); + assert(foo_parents[1].foos[0].b[0] == 14); + assert(foo_parents[1].foos[0].b[1] == 15); + assert(foo_parents[1].foos[0].b[2] == 16); + assert(foo_parents[1].foos[0].bar.inner == [112, 113, 114]); + + let (popped_slice, last_foo) = foo_parents[y - 2].foos.pop_back(); + foo_parents[y - 2].foos = popped_slice; + assert(foo_parents[y - 2].foos.len() == 6); + assert(last_foo.a == 40); + assert(last_foo.b[0] == 14); + assert(last_foo.b[1] == 15); + assert(last_foo.b[2] == 16); + assert(last_foo.bar.inner == [112, 113, 114]); + + let (first_elem, rest_of_slice) = foo_parents[y - 2].foos.pop_front(); + foo_parents[y - 2].foos = rest_of_slice; + assert(rest_of_slice.len() == 5); + assert(foo_parents[y - 2].foos.len() == 5); + assert(first_elem.a == 40); + assert(first_elem.b[0] == 14); + assert(first_elem.b[1] == 15); + assert(first_elem.b[2] == 16); + assert(first_elem.bar.inner == [112, 113, 114]); + + assert(foo_parents[1].foos[0].a == 1); + assert(foo_parents[1].foos[0].b[0] == 2); + assert(foo_parents[1].foos[0].b[1] == 3); + assert(foo_parents[1].foos[0].b[2] == 20); + assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); + + assert(foo_parents[1].foos[1].a == 4); + assert(foo_parents[1].foos[1].b[0] == 5); + assert(foo_parents[1].foos[1].b[1] == 6); + assert(foo_parents[1].foos[1].b[2] == 21); + assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); + + assert(foo_parents[1].foos[2].a == 7); + assert(foo_parents[1].foos[2].b[0] == 8); + assert(foo_parents[1].foos[2].b[1] == 9); + assert(foo_parents[1].foos[2].b[2] == 22); + assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); + + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.insert(y - 1, foo); + assert(foo_parents[y - 2].foos.len() == 6); + + // Check that the value before the insert is unchanged + assert(foo_parents[1].foos[1].a == 4); + assert(foo_parents[1].foos[1].b[0] == 5); + assert(foo_parents[1].foos[1].b[1] == 6); + assert(foo_parents[1].foos[1].b[2] == 21); + assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); + // Check that we have inserted the correct nested slice + assert(foo_parents[1].foos[2].a == 40); + assert(foo_parents[1].foos[2].b[0] == 14); + assert(foo_parents[1].foos[2].b[1] == 15); + assert(foo_parents[1].foos[2].b[2] == 16); + assert(foo_parents[1].foos[2].bar.inner == [112, 113, 114]); + // Check that tne nested slice has shifted following an insert + assert(foo_parents[1].foos[3].a == 7); + assert(foo_parents[1].foos[3].b[0] == 8); + assert(foo_parents[1].foos[3].b[1] == 9); + assert(foo_parents[1].foos[3].b[2] == 22); + assert(foo_parents[1].foos[3].bar.inner == [106, 107, 108]); + + assert(foo_parents[1].foos[4].a == 40); + assert(foo_parents[1].foos[4].b[y - 3] == 14); + assert(foo_parents[1].foos[4].b[y - 2] == 15); + assert(foo_parents[1].foos[4].b[y - 1] == 16); + assert(foo_parents[1].foos[4].bar.inner == [112, 113, 114]); + + let (removed_slice, removed_elem) = foo_parents[y - 2].foos.remove(y); + foo_parents[y - 2].foos = removed_slice; + assert(foo_parents[y - 2].foos.len() == 5); + // Check that the removed slice is what we expect + assert(removed_elem.a == 7); + assert(removed_elem.b[0] == 8); + assert(removed_elem.b[1] == 9); + assert(removed_elem.b[2] == 22); + assert(removed_elem.bar.inner == [106, 107, 108]); + // Check that the value before the remove is unchanged + assert(foo_parents[1].foos[2].a == 40); + assert(foo_parents[1].foos[2].b[0] == 14); + assert(foo_parents[1].foos[2].b[1] == 15); + assert(foo_parents[1].foos[2].b[2] == 16); + assert(foo_parents[1].foos[2].bar.inner == [112, 113, 114]); + // Check that the nested slice has shifted following a removal + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + assert(foo_parents[1].foos[4].a == 10); + assert(foo_parents[1].foos[4].b[y - 3] == 11); + assert(foo_parents[1].foos[4].b[y - 2] == 12); + assert(foo_parents[1].foos[4].b[y - 1] == 23); + assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); + } else { + foo_parents[y - 2].foos[y].a = 1000; + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[y - 2].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].b.len() == 3); + assert(foo_parents[1].foos[5].bar.inner == [109, 110, 111]); + } + // Check that we end with what we expect from the if case + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + assert(foo_parents[y - 2].foos.len() == 5); + assert(foo_parents[1].foos[4].a == 10); + assert(foo_parents[1].foos[4].b[y - 3] == 11); + assert(foo_parents[1].foos[4].b[y - 2] == 12); + assert(foo_parents[1].foos[4].b[y - 1] == 23); + assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); +} + +fn merge_nested_slice_else(mut foo_parents: [FooParent], y: Field, foo: Foo) { + if y as u32 > 10 { + assert(foo_parents[y - 2].foos.len() == 5); + foo_parents[y - 2].foos[y].a = 1000; + } else { + assert(foo_parents[y - 2].foos.len() == 5); + assert(foo_parents[1].foos[3].a == 10); + assert(foo_parents[1].foos[3].b[0] == 11); + assert(foo_parents[1].foos[3].b[1] == 12); + assert(foo_parents[1].foos[3].b[2] == 23); + assert(foo_parents[1].foos[3].bar.inner == [109, 110, 111]); + + foo_parents[y - 2].foos[y] = foo; + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[y - 2].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[1] == 15); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_front(foo); + assert(foo_parents[y - 2].foos.len() == 7); + assert(foo_parents[1].foos[0].a == 40); + assert(foo_parents[1].foos[0].b[0] == 14); + assert(foo_parents[1].foos[0].b[1] == 15); + assert(foo_parents[1].foos[0].b[2] == 16); + assert(foo_parents[1].foos[0].bar.inner == [112, 113, 114]); + + let (popped_slice, last_foo) = foo_parents[y - 2].foos.pop_back(); + foo_parents[y - 2].foos = popped_slice; + assert(foo_parents[y - 2].foos.len() == 6); + assert(last_foo.a == 40); + assert(last_foo.b[0] == 14); + assert(last_foo.b[1] == 15); + assert(last_foo.b[2] == 16); + assert(last_foo.bar.inner == [112, 113, 114]); + + let (first_elem, rest_of_slice) = foo_parents[y - 2].foos.pop_front(); + foo_parents[y - 2].foos = rest_of_slice; + assert(rest_of_slice.len() == 5); + assert(foo_parents[y - 2].foos.len() == 5); + assert(first_elem.a == 40); + assert(first_elem.b[0] == 14); + assert(first_elem.b[1] == 15); + assert(first_elem.b[2] == 16); + assert(first_elem.bar.inner == [112, 113, 114]); + + assert(foo_parents[1].foos[0].a == 1); + assert(foo_parents[1].foos[0].b[0] == 2); + assert(foo_parents[1].foos[0].b[1] == 3); + assert(foo_parents[1].foos[0].b[2] == 20); + assert(foo_parents[1].foos[0].bar.inner == [100, 101, 102]); + + assert(foo_parents[1].foos[1].a == 4); + assert(foo_parents[1].foos[1].b[0] == 5); + assert(foo_parents[1].foos[1].b[1] == 6); + assert(foo_parents[1].foos[1].b[2] == 21); + assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); + + assert(foo_parents[1].foos[2].a == 7); + assert(foo_parents[1].foos[2].b[0] == 8); + assert(foo_parents[1].foos[2].b[1] == 9); + assert(foo_parents[1].foos[2].b[2] == 22); + assert(foo_parents[1].foos[2].bar.inner == [106, 107, 108]); + + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.insert(y - 1, foo); + assert(foo_parents[y - 2].foos.len() == 6); + + // Check that the value before the insert is unchanged + assert(foo_parents[1].foos[1].a == 4); + assert(foo_parents[1].foos[1].b[0] == 5); + assert(foo_parents[1].foos[1].b[1] == 6); + assert(foo_parents[1].foos[1].b[2] == 21); + assert(foo_parents[1].foos[1].bar.inner == [103, 104, 105]); + // Check that we have inserted the correct nested slice + assert(foo_parents[1].foos[2].a == 40); + assert(foo_parents[1].foos[2].b[0] == 14); + assert(foo_parents[1].foos[2].b[1] == 15); + assert(foo_parents[1].foos[2].b[2] == 16); + assert(foo_parents[1].foos[2].bar.inner == [112, 113, 114]); + // Check that tne nested slice has shifted following an insert + assert(foo_parents[1].foos[3].a == 7); + assert(foo_parents[1].foos[3].b[0] == 8); + assert(foo_parents[1].foos[3].b[1] == 9); + assert(foo_parents[1].foos[3].b[2] == 22); + assert(foo_parents[1].foos[3].bar.inner == [106, 107, 108]); + + assert(foo_parents[1].foos[4].a == 40); + assert(foo_parents[1].foos[4].b[y - 3] == 14); + assert(foo_parents[1].foos[4].b[y - 2] == 15); + assert(foo_parents[1].foos[4].b[y - 1] == 16); + assert(foo_parents[1].foos[4].bar.inner == [112, 113, 114]); + + let (removed_slice, removed_elem) = foo_parents[y - 2].foos.remove(y); + foo_parents[y - 2].foos = removed_slice; + assert(foo_parents[y - 2].foos.len() == 5); + // Check that the removed slice is what we expect + assert(removed_elem.a == 7); + assert(removed_elem.b[0] == 8); + assert(removed_elem.b[1] == 9); + assert(removed_elem.b[2] == 22); + assert(removed_elem.bar.inner == [106, 107, 108]); + // Check that the value before the remove is unchanged + assert(foo_parents[1].foos[2].a == 40); + assert(foo_parents[1].foos[2].b[0] == 14); + assert(foo_parents[1].foos[2].b[1] == 15); + assert(foo_parents[1].foos[2].b[2] == 16); + assert(foo_parents[1].foos[2].bar.inner == [112, 113, 114]); + // Check that the nested slice has shifted following a removal + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + assert(foo_parents[1].foos[4].a == 10); + assert(foo_parents[1].foos[4].b[y - 3] == 11); + assert(foo_parents[1].foos[4].b[y - 2] == 12); + assert(foo_parents[1].foos[4].b[y - 1] == 23); + assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); + } + // Check that we end with what we expect from the else case + assert(foo_parents[1].foos[3].a == 40); + assert(foo_parents[1].foos[3].b[y - 3] == 14); + assert(foo_parents[1].foos[3].b[y - 2] == 15); + assert(foo_parents[1].foos[3].b[y - 1] == 16); + assert(foo_parents[1].foos[3].bar.inner == [112, 113, 114]); + + assert(foo_parents[y - 2].foos.len() == 5); + assert(foo_parents[1].foos[4].a == 10); + assert(foo_parents[1].foos[4].b[y - 3] == 11); + assert(foo_parents[1].foos[4].b[y - 2] == 12); + assert(foo_parents[1].foos[4].b[y - 1] == 23); + assert(foo_parents[1].foos[4].bar.inner == [109, 110, 111]); +} + +fn merge_nested_if_else_else(mut foo_parents: [FooParent], y: Field, foo: Foo) { + if y as u32 < 10 { + foo_parents[y - 2].foos[y].a = 1000; + + if y != 3 { + foo_parents[y - 2].foos[y].a = foo_parents[y - 2].foos[y].a + 20; + } else if y == 5 { + assert(foo_parents[1].foos[3].a == 1022); + } else { + assert(foo_parents[1].foos[3].a == 1000); + + foo_parents[y - 2].foos[y].a = 20; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[y - 2].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[1] == 15); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].bar.inner == [112, 113, 114]); + } + } else { + // Shadowing used to ignor warning lines from LSP + let (_, _) = foo_parents[y - 2].foos.pop_back(); + foo_parents[y - 2].foos[y].a = 0; + } + assert(foo_parents[y - 2].foos[y].a == 20); + assert(foo_parents[1].foos[3].a == 20); + + let foos_len = foo_parents[y - 2].foos.len(); + assert(foos_len == 6); + assert(foo_parents[1].foos[foos_len - 1].a == 40); + assert(foo_parents[1].foos[foos_len - 1].b[0] == 14); + assert(foo_parents[1].foos[foos_len - 1].b[1] == 15); + assert(foo_parents[1].foos[foos_len - 1].b[2] == 16); + assert(foo_parents[1].foos[foos_len - 1].bar.inner == [112, 113, 114]); + + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + let foos_len = foo_parents[y - 2].foos.len(); + assert(foos_len == 7); + assert(foo_parents[1].foos[6].a == 40); + assert(foo_parents[1].foos[6].b[0] == 14); + assert(foo_parents[1].foos[6].b[1] == 15); + assert(foo_parents[1].foos[6].b[2] == 16); + assert(foo_parents[1].foos[6].bar.inner == [112, 113, 114]); +} + +fn merge_nested_if_else_if(mut foo_parents: [FooParent], y: Field) { + if y as u32 < 10 { + foo_parents[y - 2].foos[y].a = 1000; + + if y == 10 { + foo_parents[y - 2].foos[y].a = foo_parents[y - 2].foos[y].a + 20; + } else { + assert(foo_parents[y - 2].foos[y].a == 1000); + if y == 3 { + assert(foo_parents[y - 2].foos[y].a == 1000); + + foo_parents[y - 2].foos[y].a = 5; + } + } + } else { + foo_parents[y - 2].foos[y].a = 999; + } + assert(foo_parents[y - 2].foos[y].a == 5); +} + +// Simple regression for side effectual array operations +fn regression_side_effectual_array_ops(mut foos: [Foo], y: Field) { + if y as u32 < 10 { + foos[y].a = 1000; + // If array ops (get/set) are labelled as pure instructions + // missing an assert here would cause the same assert in the + // else-case of the inner if statement to fail + // assert(foos[y].a == 1000); + + if y == 10 { + foos[y].a = foos[y].a + 20; + } else { + assert(foos[y].a == 1000); + foos[y].a = 5; + } + } else { + foos[y].a = 999; + } + assert(foos[y].a == 5); +} + +fn merge_nested_two_ifs(mut foo_parents: [FooParent], y: Field, mut foo: Foo) { + assert(foo_parents[1].foos.len() == 5); + if y as u32 > 10 { + assert(foo_parents[y - 2].foos[y].a == 999); + foo_parents[y - 2].foos[y].a = 2; + } else { + assert(foo_parents[y - 2].foos[y].a == 10); + foo_parents[y - 2].foos[y].a = foo_parents[y - 2].foos[y].a - 2; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + assert(foo_parents[1].foos.len() == 6); + assert(foo_parents[1].foos[5].a == 40); + assert(foo_parents[1].foos[5].b[0] == 14); + assert(foo_parents[1].foos[5].b[1] == 15); + assert(foo_parents[1].foos[5].b[2] == 16); + assert(foo_parents[1].foos[5].bar.inner == [112, 113, 114]); + + if y == 20 { + foo.a = 20; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + foo.a = 15; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + + assert(foo_parents[1].foos.len() == 7); + assert(foo_parents[1].foos[6].a == 15); + assert(foo_parents[1].foos[6].b[0] == 14); + assert(foo_parents[1].foos[6].b[1] == 15); + assert(foo_parents[1].foos[6].b[2] == 16); + assert(foo_parents[1].foos[6].bar.inner == [112, 113, 114]); + + foo.a = 20; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[1].foos.len() == 8); + assert(foo_parents[1].foos[7].a == 20); +} + +fn merge_nested_mutate_between_ifs(mut foo_parents: [FooParent], y: Field, mut foo: Foo) { + if y == 3 { + foo_parents[y - 2].foos[y].a = 50; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } else { + foo_parents[y - 2].foos[y].a = foo_parents[y - 2].foos[y].a - 2; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + foo.a = 30; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[1].foos.len() == 8); + assert(foo_parents[1].foos[7].a == 30); + + if y == 20 { + foo.a = 20; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + foo.a = 15; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + + if y != 20 { + foo.a = 50; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + foo.a = 60; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + let foos_len = foo_parents[1].foos.len(); + assert(foos_len == 11); + assert(foo_parents[1].foos[3].a == 50); + assert(foo_parents[1].foos[foos_len - 1].a == 60); + assert(foo_parents[1].foos[foos_len - 2].a == 50); + assert(foo_parents[1].foos[foos_len - 3].a == 15); + assert(foo_parents[1].foos[foos_len - 4].a == 30); +} + +fn merge_nested_push_then_pop(mut foo_parents: [FooParent], y: Field, mut foo: Foo) { + if y == 3 { + foo_parents[y - 2].foos[y].a = 50; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[1].foos.len() == 7); + + let (popped_slice, elem) = foo_parents[y - 2].foos.pop_back(); + assert(foo_parents[1].foos.len() == 7); + assert(elem.a == foo.a); + assert(elem.b[0] == foo.b[0]); + assert(elem.b[1] == foo.b[1]); + assert(elem.b[2] == foo.b[2]); + assert(elem.bar.inner == foo.bar.inner); + foo_parents[y - 2].foos = popped_slice; + } else { + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + foo.a = 30; + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + assert(foo_parents[1].foos.len() == 7); + assert(foo_parents[1].foos[6].a == 30); + + if y == 20 { + foo_parents[y - 2].foos = foo_parents[y - 2].foos.push_back(foo); + } + + let (popped_slice, elem) = foo_parents[y - 2].foos.pop_back(); + assert(elem.a == 30); + + let (_, elem) = popped_slice.pop_back(); + assert(elem.a == 40); +} +