From 3d8741a19f18498e4ab27604e443539b4946af25 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 03:15:47 +0000 Subject: [PATCH 1/7] enable MakeArray in loop invariant code motion by inserting an inc rc in its place --- .../src/ssa/opt/loop_invariant.rs | 116 ++++++++++++++++++ .../gates_report_brillig_execution.sh | 2 - tooling/nargo_cli/build.rs | 4 +- 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index 87e7f8bcff3..9920ec2e682 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -9,6 +9,7 @@ //! We also check that we are not hoisting instructions with side effects. use acvm::{acir::AcirField, FieldElement}; use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; +use iter_extended::vecmap; use crate::ssa::{ ir::{ @@ -113,6 +114,29 @@ impl<'f> LoopInvariantContext<'f> { if hoist_invariant { self.inserter.push_instruction(instruction_id, pre_header); + + // We track whether we may mutate MakeArray instructions before we deduplicate + // them but we still need to issue an extra inc_rc in case they're mutated afterward. + if matches!( + self.inserter.function.dfg[instruction_id], + Instruction::MakeArray { .. } + ) { + let results = + self.inserter.function.dfg.instruction_results(instruction_id).to_vec(); + let results = vecmap(results, |value| self.inserter.resolve(value)); + assert_eq!( + results.len(), + 1, + "ICE: We expect only a single result from an `Instruction::MakeArray`" + ); + dbg!(results.clone()); + let inc_rc = Instruction::IncrementRc { value: results[0] }; + let call_stack = self.inserter.function.dfg.get_call_stack(instruction_id); + self.inserter + .function + .dfg + .insert_instruction_and_results(inc_rc, *block, None, call_stack); + } } else { self.inserter.push_instruction(instruction_id, *block); } @@ -190,6 +214,7 @@ impl<'f> LoopInvariantContext<'f> { }); let can_be_deduplicated = instruction.can_be_deduplicated(self.inserter.function, false) + || matches!(instruction, Instruction::MakeArray { .. }) || self.can_be_deduplicated_from_upper_bound(&instruction); is_loop_invariant && can_be_deduplicated @@ -559,4 +584,95 @@ mod test { let ssa = ssa.loop_invariant_code_motion(); assert_normalized_ssa_equals(ssa, expected); } + + #[test] + fn insert_inc_rc_when_moving_make_array() { + // SSA for the following program: + // + // unconstrained fn main(x: u32, y: u32) { + // let mut a1 = [1, 2, 3, 4, 5]; + // a1[x] = 64; + // for i in 0 .. 5 { + // let mut a2 = [1, 2, 3, 4, 5]; + // a2[y + i] = 128; + // foo(a2); + // } + // foo(a1); + // } + // + // We want to make sure move a loop invariant make_array instruction, + // to account for whether that array has been marked as mutable. + // To do so, we increment the reference counter on the array we are moving. + // In the SSA below, we want to move `v42` out of the loop. + let src = " + brillig(inline) fn main f0 { + b0(v0: u32, v1: u32): + v8 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v9 = allocate -> &mut [Field; 5] + v11 = array_set v8, index v0, value Field 64 + v13 = add v0, u32 1 + store v11 at v9 + jmp b1(u32 0) + b1(v2: u32): + v16 = lt v2, u32 5 + jmpif v16 then: b3, else: b2 + b2(): + v17 = load v9 -> [Field; 5] + call f1(v17) + return + b3(): + v19 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v20 = allocate -> &mut [Field; 5] + v21 = add v1, v2 + v23 = array_set v19, index v21, value Field 128 + call f1(v23) + v25 = add v2, u32 1 + jmp b1(v25) + } + + brillig(inline) fn foo f1 { + b0(v0: [Field; 5]): + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + + // We expect the `make_array` at the top of `b3` to be replaced with an `inc_rc` + // of the newly hoisted `make_array` at the end of `b0`. + let expected = " + brillig(inline) fn main f0 { + b0(v0: u32, v1: u32): + v32 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v33 = allocate -> &mut [Field; 5] + v34 = array_set v32, index v0, value Field 64 + v35 = add v0, u32 1 + store v34 at v33 + v36 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + jmp b1(u32 0) + b1(v2: u32): + v37 = lt v2, u32 5 + jmpif v37 then: b3, else: b2 + b2(): + v38 = load v33 -> [Field; 5] + call f1(v38) + return + b3(): + inc_rc v36 + v39 = allocate -> &mut [Field; 5] + v40 = add v1, v2 + v41 = array_set v36, index v40, value Field 128 + call f1(v41) + v42 = add v2, u32 1 + jmp b1(v42) + } + brillig(inline) fn foo f1 { + b0(v0: [Field; 5]): + return + } + "; + + let ssa = ssa.loop_invariant_code_motion(); + assert_normalized_ssa_equals(ssa, expected); + } } diff --git a/test_programs/gates_report_brillig_execution.sh b/test_programs/gates_report_brillig_execution.sh index 024c7612541..b3f4a8bda98 100755 --- a/test_programs/gates_report_brillig_execution.sh +++ b/test_programs/gates_report_brillig_execution.sh @@ -8,8 +8,6 @@ excluded_dirs=( "double_verify_nested_proof" "overlapping_dep_and_mod" "comptime_println" - # Takes a very long time to execute as large loops do not get simplified. - "regression_4709" # bit sizes for bigint operation doesn't match up. "bigint" # Expected to fail as test asserts on which runtime it is in. diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index db6177366d3..8db2c1786d8 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -42,9 +42,7 @@ fn main() { /// Some tests are explicitly ignored in brillig due to them failing. /// These should be fixed and removed from this list. -const IGNORED_BRILLIG_TESTS: [&str; 11] = [ - // Takes a very long time to execute as large loops do not get simplified. - "regression_4709", +const IGNORED_BRILLIG_TESTS: [&str; 10] = [ // bit sizes for bigint operation doesn't match up. "bigint", // ICE due to looking for function which doesn't exist. From c87c924e35f75915c4feda0bf2c57a6ba5eb7bf5 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 11 Dec 2024 22:27:21 -0500 Subject: [PATCH 2/7] Update compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs --- compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index 9920ec2e682..2fa9d837d26 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -129,7 +129,6 @@ impl<'f> LoopInvariantContext<'f> { 1, "ICE: We expect only a single result from an `Instruction::MakeArray`" ); - dbg!(results.clone()); let inc_rc = Instruction::IncrementRc { value: results[0] }; let call_stack = self.inserter.function.dfg.get_call_stack(instruction_id); self.inserter From 456aabe4b4331ff9aed7745c1c4508d1a24c03e5 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 03:29:49 +0000 Subject: [PATCH 3/7] fix weird formatting in test --- .../src/ssa/opt/loop_invariant.rs | 121 +++++++++--------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index 2fa9d837d26..23d2c5b5172 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -604,72 +604,73 @@ mod test { // To do so, we increment the reference counter on the array we are moving. // In the SSA below, we want to move `v42` out of the loop. let src = " - brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): - v8 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] - v9 = allocate -> &mut [Field; 5] - v11 = array_set v8, index v0, value Field 64 - v13 = add v0, u32 1 - store v11 at v9 - jmp b1(u32 0) - b1(v2: u32): - v16 = lt v2, u32 5 - jmpif v16 then: b3, else: b2 - b2(): - v17 = load v9 -> [Field; 5] - call f1(v17) - return - b3(): - v19 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] - v20 = allocate -> &mut [Field; 5] - v21 = add v1, v2 - v23 = array_set v19, index v21, value Field 128 - call f1(v23) - v25 = add v2, u32 1 - jmp b1(v25) - } - - brillig(inline) fn foo f1 { - b0(v0: [Field; 5]): - return - } - "; + brillig(inline) fn main f0 { + b0(v0: u32, v1: u32): + v8 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v9 = allocate -> &mut [Field; 5] + v11 = array_set v8, index v0, value Field 64 + v13 = add v0, u32 1 + store v11 at v9 + jmp b1(u32 0) + b1(v2: u32): + v16 = lt v2, u32 5 + jmpif v16 then: b3, else: b2 + b2(): + v17 = load v9 -> [Field; 5] + call f1(v17) + return + b3(): + v19 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v20 = allocate -> &mut [Field; 5] + v21 = add v1, v2 + v23 = array_set v19, index v21, value Field 128 + call f1(v23) + v25 = add v2, u32 1 + jmp b1(v25) + } + + brillig(inline) fn foo f1 { + b0(v0: [Field; 5]): + return + } + "; let ssa = Ssa::from_str(src).unwrap(); // We expect the `make_array` at the top of `b3` to be replaced with an `inc_rc` // of the newly hoisted `make_array` at the end of `b0`. let expected = " - brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): - v32 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] - v33 = allocate -> &mut [Field; 5] - v34 = array_set v32, index v0, value Field 64 - v35 = add v0, u32 1 - store v34 at v33 - v36 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] - jmp b1(u32 0) - b1(v2: u32): - v37 = lt v2, u32 5 - jmpif v37 then: b3, else: b2 - b2(): - v38 = load v33 -> [Field; 5] - call f1(v38) - return - b3(): - inc_rc v36 - v39 = allocate -> &mut [Field; 5] - v40 = add v1, v2 - v41 = array_set v36, index v40, value Field 128 - call f1(v41) - v42 = add v2, u32 1 - jmp b1(v42) - } - brillig(inline) fn foo f1 { - b0(v0: [Field; 5]): - return - } - "; + brillig(inline) fn main f0 { + b0(v0: u32, v1: u32): + v32 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v33 = allocate -> &mut [Field; 5] + v34 = array_set v32, index v0, value Field 64 + v35 = add v0, u32 1 + store v34 at v33 + v36 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + jmp b1(u32 0) + b1(v2: u32): + v37 = lt v2, u32 5 + jmpif v37 then: b3, else: b2 + b2(): + v38 = load v33 -> [Field; 5] + call f1(v38) + return + b3(): + inc_rc v36 + v39 = allocate -> &mut [Field; 5] + v40 = add v1, v2 + v41 = array_set v36, index v40, value Field 128 + call f1(v41) + v42 = add v2, u32 1 + jmp b1(v42) + } + + brillig(inline) fn foo f1 { + b0(v0: [Field; 5]): + return + } + "; let ssa = ssa.loop_invariant_code_motion(); assert_normalized_ssa_equals(ssa, expected); From c6147e48cf8f5d6187889db53f0808082b49d228 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 03:45:56 +0000 Subject: [PATCH 4/7] initial rc tracker, currently would break in some cases, need to account for calls --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 108 +++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index b0843f327c1..148c9eafa23 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -10,12 +10,14 @@ use crate::ssa::{ function::Function, instruction::{BinaryOp, Instruction, InstructionId, Intrinsic}, post_order::PostOrder, - types::NumericType, + types::{NumericType, Type}, value::{Value, ValueId}, }, ssa_gen::Ssa, }; +use super::rc::{pop_rc_for, RcInstruction}; + impl Ssa { /// Performs Dead Instruction Elimination (DIE) to remove any instructions with /// unused results. @@ -104,6 +106,8 @@ impl Context { let instructions_len = block.instructions().len(); + let mut rc_tracker = RcTracker::default(); + // Indexes of instructions that might be out of bounds. // We'll remove those, but before that we'll insert bounds checks for them. let mut possible_index_out_of_bounds_indexes = Vec::new(); @@ -131,8 +135,12 @@ impl Context { }); } } + + rc_tracker.track_inc_rcs_to_remove(*instruction_id, function); } + self.instructions_to_remove.extend(rc_tracker.rc_pairs_to_remove); + // If there are some instructions that might trigger an out of bounds error, // first add constrain checks. Then run the DIE pass again, which will remove those // but leave the constrains (any any value needed by those constrains) @@ -517,6 +525,104 @@ fn apply_side_effects( (lhs, rhs) } +#[derive(Default)] +struct RcTracker { + // We can track IncrementRc instructions per block to determine whether they are useless. + // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove + // them if their value is not used anywhere in the function. However, even when their value is used, their existence + // is pointless logic if there is no array set between the increment and the decrement of the reference counter. + // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction + // with the same value but no array set in between. + // If we see an inc/dec RC pair within a block we can safely remove both instructions. + rcs_with_possible_pairs: HashMap>, + rc_pairs_to_remove: HashSet, + // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed. + // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array. + inc_rcs: HashMap>, + mut_borrowed_arrays: HashSet, + // The SSA often creates patterns where after simplifications we end up with repeat + // IncrementRc instructions on the same value. We track whether the previous instruction was an IncrementRc, + // and if the current instruction is also an IncrementRc on the same value we remove the current instruction. + // `None` if the previous instruction was anything other than an IncrementRc + previous_inc_rc: Option, +} + +impl RcTracker { + fn track_inc_rcs_to_remove(&mut self, instruction_id: InstructionId, function: &Function) { + let instruction = &function.dfg[instruction_id]; + + if let Instruction::IncrementRc { value } = instruction { + if let Some(previous_value) = self.previous_inc_rc { + if previous_value == *value { + self.rc_pairs_to_remove.insert(instruction_id); + } + } + self.previous_inc_rc = Some(*value); + } else { + self.previous_inc_rc = None; + } + + // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal + // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. + match instruction { + Instruction::IncrementRc { value } => { + if let Some(inc_rc) = + pop_rc_for(*value, function, &mut self.rcs_with_possible_pairs) + { + if !inc_rc.possibly_mutated { + self.rc_pairs_to_remove.insert(inc_rc.id); + self.rc_pairs_to_remove.insert(instruction_id); + } + } + + self.inc_rcs.entry(*value).or_default().insert(instruction_id); + } + Instruction::DecrementRc { value } => { + let typ = function.dfg.type_of_value(*value); + + // We assume arrays aren't mutated until we find an array_set + let dec_rc = + RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; + self.rcs_with_possible_pairs.entry(typ).or_default().push(dec_rc); + } + Instruction::ArraySet { array, .. } => { + let typ = function.dfg.type_of_value(*array); + if let Some(dec_rcs) = self.rcs_with_possible_pairs.get_mut(&typ) { + for dec_rc in dec_rcs { + dec_rc.possibly_mutated = true; + } + } + + self.mut_borrowed_arrays.insert(*array); + } + Instruction::Store { value, .. } => { + // We are very conservative and say that any store of an array value means it has the potential + // to be mutated. This is done due to the tracking of mutable borrows still being per block. + let typ = function.dfg.type_of_value(*value); + if matches!(&typ, Type::Array(..) | Type::Slice(..)) { + self.mut_borrowed_arrays.insert(*value); + } + } + _ => {} + } + } + + fn get_non_mutated_arrays(&self) -> HashSet { + self.inc_rcs + .keys() + .filter_map(|value| { + if !self.mut_borrowed_arrays.contains(value) { + Some(&self.inc_rcs[value]) + } else { + None + } + }) + .flatten() + .copied() + .collect() + } +} + #[cfg(test)] mod test { use std::sync::Arc; From 1777215fe1cf4bd4fdecc5f78a43e4e0ad8231d2 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 03:53:21 +0000 Subject: [PATCH 5/7] fix expected to be normalized --- .../src/ssa/opt/loop_invariant.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index 23d2c5b5172..23617390d2f 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -628,7 +628,6 @@ mod test { v25 = add v2, u32 1 jmp b1(v25) } - brillig(inline) fn foo f1 { b0(v0: [Field; 5]): return @@ -642,30 +641,29 @@ mod test { let expected = " brillig(inline) fn main f0 { b0(v0: u32, v1: u32): - v32 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] - v33 = allocate -> &mut [Field; 5] - v34 = array_set v32, index v0, value Field 64 - v35 = add v0, u32 1 - store v34 at v33 - v36 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v8 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] + v9 = allocate -> &mut [Field; 5] + v11 = array_set v8, index v0, value Field 64 + v13 = add v0, u32 1 + store v11 at v9 + v14 = make_array [Field 1, Field 2, Field 3, Field 4, Field 5] : [Field; 5] jmp b1(u32 0) b1(v2: u32): - v37 = lt v2, u32 5 - jmpif v37 then: b3, else: b2 + v17 = lt v2, u32 5 + jmpif v17 then: b3, else: b2 b2(): - v38 = load v33 -> [Field; 5] - call f1(v38) + v18 = load v9 -> [Field; 5] + call f1(v18) return b3(): - inc_rc v36 - v39 = allocate -> &mut [Field; 5] - v40 = add v1, v2 - v41 = array_set v36, index v40, value Field 128 - call f1(v41) - v42 = add v2, u32 1 - jmp b1(v42) + inc_rc v14 + v20 = allocate -> &mut [Field; 5] + v21 = add v1, v2 + v23 = array_set v14, index v21, value Field 128 + call f1(v23) + v25 = add v2, u32 1 + jmp b1(v25) } - brillig(inline) fn foo f1 { b0(v0: [Field; 5]): return From 8f7bbb1ffa6f8b41524094b4e508723f616d07dc Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 04:06:31 +0000 Subject: [PATCH 6/7] check non mutated arrays but by type --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 148c9eafa23..609dbb13dec 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -139,6 +139,7 @@ impl Context { rc_tracker.track_inc_rcs_to_remove(*instruction_id, function); } + self.instructions_to_remove.extend(rc_tracker.get_non_mutated_arrays(&function.dfg)); self.instructions_to_remove.extend(rc_tracker.rc_pairs_to_remove); // If there are some instructions that might trigger an out of bounds error, @@ -539,7 +540,7 @@ struct RcTracker { // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed. // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array. inc_rcs: HashMap>, - mut_borrowed_arrays: HashSet, + mutated_array_types: HashSet, // The SSA often creates patterns where after simplifications we end up with repeat // IncrementRc instructions on the same value. We track whether the previous instruction was an IncrementRc, // and if the current instruction is also an IncrementRc on the same value we remove the current instruction. @@ -593,25 +594,25 @@ impl RcTracker { } } - self.mut_borrowed_arrays.insert(*array); + self.mutated_array_types.insert(typ); } Instruction::Store { value, .. } => { - // We are very conservative and say that any store of an array value means it has the potential - // to be mutated. This is done due to the tracking of mutable borrows still being per block. + // We are very conservative and say that any store of an array type means it has the potential to be mutated. let typ = function.dfg.type_of_value(*value); if matches!(&typ, Type::Array(..) | Type::Slice(..)) { - self.mut_borrowed_arrays.insert(*value); + self.mutated_array_types.insert(typ); } } _ => {} } } - fn get_non_mutated_arrays(&self) -> HashSet { + fn get_non_mutated_arrays(&self, dfg: &DataFlowGraph) -> HashSet { self.inc_rcs .keys() .filter_map(|value| { - if !self.mut_borrowed_arrays.contains(value) { + let typ = dfg.type_of_value(*value); + if !self.mutated_array_types.contains(&typ) { Some(&self.inc_rcs[value]) } else { None From 3f962d1e1fa1a06e825a782710e290c7db9a8d39 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 12 Dec 2024 04:20:52 +0000 Subject: [PATCH 7/7] add call args to rc tracker --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 609dbb13dec..5a0a6585697 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -603,6 +603,14 @@ impl RcTracker { self.mutated_array_types.insert(typ); } } + Instruction::Call { arguments, .. } => { + for arg in arguments { + let typ = function.dfg.type_of_value(*arg); + if matches!(&typ, Type::Array(..) | Type::Slice(..)) { + self.mutated_array_types.insert(typ); + } + } + } _ => {} } }