From a1e13ab4442119be265dfdba46cdaf0b860676c1 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 11 Mar 2024 13:13:10 -0500 Subject: [PATCH 01/12] Add Instruction::DecrementRc --- compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs | 2 +- compiler/noirc_evaluator/src/ssa/ir/instruction.rs | 14 +++++++++++++- compiler/noirc_evaluator/src/ssa/ir/printer.rs | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 8d4d0668534..0c5817119e3 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -503,7 +503,7 @@ impl Context { Instruction::Load { .. } => { unreachable!("Expected all load instructions to be removed before acir_gen") } - Instruction::IncrementRc { .. } => { + Instruction::IncrementRc { .. } | Instruction::DecrementRc { .. } => { // Do nothing. Only Brillig needs to worry about reference counted arrays } Instruction::RangeCheck { value, max_bit_size, assert_message } => { diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 0b6c7074e45..c1ca760d7f1 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -194,6 +194,13 @@ pub(crate) enum Instruction { /// implemented via reference counting. In ACIR code this is done with im::Vector and these /// IncrementRc instructions are ignored. IncrementRc { value: ValueId }, + + /// An instruction to decrement the reference count of a value. + /// + /// This currently only has an effect in Brillig code where array sharing and copy on write is + /// implemented via reference counting. In ACIR code this is done with im::Vector and these + /// DecrementRc instructions are ignored. + DecrementRc { value: ValueId }, } impl Instruction { @@ -214,6 +221,7 @@ impl Instruction { Instruction::Constrain(..) | Instruction::Store { .. } | Instruction::IncrementRc { .. } + | Instruction::DecrementRc { .. } | Instruction::RangeCheck { .. } | Instruction::EnableSideEffects { .. } => InstructionResultType::None, Instruction::Allocate { .. } @@ -250,6 +258,7 @@ impl Instruction { | Load { .. } | Store { .. } | IncrementRc { .. } + | DecrementRc { .. } | RangeCheck { .. } => false, Call { func, .. } => match dfg[*func] { @@ -285,6 +294,7 @@ impl Instruction { | Store { .. } | EnableSideEffects { .. } | IncrementRc { .. } + | DecrementRc { .. } | RangeCheck { .. } => true, // Some `Intrinsic`s have side effects so we must check what kind of `Call` this is. @@ -353,6 +363,7 @@ impl Instruction { Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value) } } Instruction::IncrementRc { value } => Instruction::IncrementRc { value: f(*value) }, + Instruction::DecrementRc { value } => Instruction::DecrementRc { value: f(*value) }, Instruction::RangeCheck { value, max_bit_size, assert_message } => { Instruction::RangeCheck { value: f(*value), @@ -409,7 +420,7 @@ impl Instruction { Instruction::EnableSideEffects { condition } => { f(*condition); } - Instruction::IncrementRc { value } | Instruction::RangeCheck { value, .. } => { + Instruction::IncrementRc { value } | Instruction::DecrementRc { value } | Instruction::RangeCheck { value, .. } => { f(*value); } } @@ -554,6 +565,7 @@ impl Instruction { Instruction::Load { .. } => None, Instruction::Store { .. } => None, Instruction::IncrementRc { .. } => None, + Instruction::DecrementRc { .. } => None, Instruction::RangeCheck { value, max_bit_size, .. } => { if let Some(numeric_constant) = dfg.get_numeric_constant(*value) { if numeric_constant.num_bits() < *max_bit_size { diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 9bd43fab1ff..6ef618fba6f 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -188,6 +188,9 @@ fn display_instruction_inner( Instruction::IncrementRc { value } => { writeln!(f, "inc_rc {}", show(*value)) } + Instruction::DecrementRc { value } => { + writeln!(f, "dec_rc {}", show(*value)) + } Instruction::RangeCheck { value, max_bit_size, .. } => { writeln!(f, "range_check {} to {} bits", show(*value), *max_bit_size,) } From fbb05d61fa0dbcd032a9916b2aba2f7cdbf4c278 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 11 Mar 2024 14:23:16 -0500 Subject: [PATCH 02/12] Issue DecrementRc instruction at the end of functions --- .../src/brillig/brillig_gen/brillig_block.rs | 18 ++++++++--- .../src/ssa/function_builder/mod.rs | 21 ++++++++++++- .../noirc_evaluator/src/ssa/ir/instruction.rs | 4 ++- .../src/ssa/ssa_gen/context.rs | 31 +++++++++++++++++++ .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 9 +++--- 5 files changed, 72 insertions(+), 11 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index c04d8475f08..47584b984e7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -225,9 +225,7 @@ impl<'block> BrilligBlock<'block> { dfg, ); } - _ => { - todo!("ICE: Param type not supported") - } + Type::Function => todo!("ICE: Type::Function Param not supported"), } } } @@ -663,11 +661,21 @@ impl<'block> BrilligBlock<'block> { let rc_register = match self.convert_ssa_value(*value, dfg) { BrilligVariable::BrilligArray(BrilligArray { rc, .. }) | BrilligVariable::BrilligVector(BrilligVector { rc, .. }) => rc, - _ => unreachable!("ICE: increment rc on non-array"), + other => unreachable!("ICE: increment rc on non-array: {other:?}"), }; self.brillig_context.usize_op_in_place(rc_register, BinaryIntOp::Add, 1); } - _ => todo!("ICE: Instruction not supported {instruction:?}"), + Instruction::DecrementRc { value } => { + let rc_register = match self.convert_ssa_value(*value, dfg) { + BrilligVariable::BrilligArray(BrilligArray { rc, .. }) + | BrilligVariable::BrilligVector(BrilligVector { rc, .. }) => rc, + other => unreachable!("ICE: decrement rc on non-array: {other:?}"), + }; + self.brillig_context.usize_op_in_place(rc_register, BinaryIntOp::Sub, 1); + } + Instruction::EnableSideEffects { .. } => { + todo!("enable_side_effects not supported by brillig") + } }; let dead_variables = self diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index bf34a47485b..2c39c83b342 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -384,6 +384,20 @@ impl FunctionBuilder { /// within the given value. If the given value is not an array and does not contain /// any arrays, this does nothing. pub(crate) fn increment_array_reference_count(&mut self, value: ValueId) { + self.update_array_reference_count(value, true); + } + + /// Insert instructions to decrement the reference count of any array(s) stored + /// within the given value. If the given value is not an array and does not contain + /// any arrays, this does nothing. + pub(crate) fn decrement_array_reference_count(&mut self, value: ValueId) { + self.update_array_reference_count(value, false); + } + + /// Increment or decrement the given value's reference count if it is an array. + /// If it is not an array, this does nothing. Note that inc_rc and dec_rc instructions + /// are ignored outside of unconstrained code. + pub(crate) fn update_array_reference_count(&mut self, value: ValueId, increment: bool) { match self.type_of_value(value) { Type::Numeric(_) => (), Type::Function => (), @@ -396,7 +410,12 @@ impl FunctionBuilder { typ @ Type::Array(..) | typ @ Type::Slice(..) => { // If there are nested arrays or slices, we wait until ArrayGet // is issued to increment the count of that array. - self.insert_instruction(Instruction::IncrementRc { value }, None); + let instruction = if increment { + Instruction::IncrementRc { value } + } else { + Instruction::DecrementRc { value } + }; + self.insert_instruction(instruction, None); // This is a bit odd, but in brillig the inc_rc instruction operates on // a copy of the array's metadata, so we need to re-store a loaded array diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index c1ca760d7f1..afade4b0616 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -420,7 +420,9 @@ impl Instruction { Instruction::EnableSideEffects { condition } => { f(*condition); } - Instruction::IncrementRc { value } | Instruction::DecrementRc { value } | Instruction::RangeCheck { value, .. } => { + Instruction::IncrementRc { value } + | Instruction::DecrementRc { value } + | Instruction::RangeCheck { value, .. } => { f(*value); } } diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 9c760c013a9..409b99958a9 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -10,6 +10,7 @@ use noirc_frontend::{BinaryOpKind, Signedness}; use crate::errors::RuntimeError; use crate::ssa::function_builder::FunctionBuilder; +use crate::ssa::ir::basic_block::BasicBlockId; use crate::ssa::ir::dfg::DataFlowGraph; use crate::ssa::ir::function::FunctionId as IrFunctionId; use crate::ssa::ir::function::{Function, RuntimeType}; @@ -1022,6 +1023,36 @@ impl<'a> FunctionContext<'a> { } } } + + /// Increments the reference count of all parameters. Returns the entry block of the function. + /// + /// This is done on parameters rather than call arguments so that we can optimize out + /// paired inc/dec instructions within brillig functions more easily. + pub(crate) fn increment_parameter_rcs(&mut self) -> BasicBlockId { + let entry = self.builder.current_function.entry_block(); + let parameters = self.builder.current_function.dfg.block_parameters(entry).to_vec(); + + for parameter in parameters { + self.builder.increment_array_reference_count(parameter); + } + + entry + } + + /// Ends a local scope of a function. + /// This will issue DecrementRc instructions for any arrays in the given starting scope + /// block's parameters. Arrays that are also used in terminator instructions for the scope are + /// ignored. + pub(crate) fn end_scope(&mut self, scope: BasicBlockId, terminator_args: &[ValueId]) { + let mut dropped_parameters = + self.builder.current_function.dfg.block_parameters(scope).to_vec(); + + dropped_parameters.retain(|parameter| !terminator_args.contains(parameter)); + + for parameter in dropped_parameters { + self.builder.decrement_array_reference_count(parameter); + } + } } /// True if the given operator cannot be encoded directly and needs diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index f3fa5d1d2f8..3d8ae0bb3eb 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -121,8 +121,11 @@ impl<'a> FunctionContext<'a> { /// Codegen a function's body and set its return value to that of its last parameter. /// For functions returning nothing, this will be an empty list. fn codegen_function_body(&mut self, body: &Expression) -> Result<(), RuntimeError> { + let entry_block = self.increment_parameter_rcs(); let return_value = self.codegen_expression(body)?; let results = return_value.into_value_list(self); + self.end_scope(entry_block, &results); + self.builder.terminate_with_return(results); Ok(()) } @@ -595,10 +598,8 @@ impl<'a> FunctionContext<'a> { arguments.append(&mut values); } - // If an array is passed as an argument we increase its reference count - for argument in &arguments { - self.builder.increment_array_reference_count(*argument); - } + // Don't need to increment array reference counts when passed in as arguments + // since it is done within the function to each parameter already. self.codegen_intrinsic_call_checks(function, &arguments, call.location); Ok(self.insert_call(function, arguments, &call.return_type, call.location)) From ef162c7f8120f35cae02cabf287364a4e9a64662 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 12 Mar 2024 15:35:17 -0500 Subject: [PATCH 03/12] Start RC optimization pass --- compiler/noirc_evaluator/src/ssa.rs | 6 ++---- compiler/noirc_evaluator/src/ssa/opt/mod.rs | 1 + compiler/noirc_evaluator/src/ssa/opt/rc.rs | 13 +++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 compiler/noirc_evaluator/src/ssa/opt/rc.rs diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index 56cb76adbe4..808cf7533c9 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -48,6 +48,7 @@ pub(crate) fn optimize_into_acir( let ssa_gen_span_guard = ssa_gen_span.enter(); let ssa = SsaBuilder::new(program, print_ssa_passes, force_brillig_output)? .run_pass(Ssa::defunctionalize, "After Defunctionalization:") + .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") .run_pass(Ssa::inline_functions, "After Inlining:") // Run mem2reg with the CFG separated into blocks .run_pass(Ssa::mem2reg, "After Mem2Reg:") @@ -59,10 +60,7 @@ pub(crate) fn optimize_into_acir( // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores .run_pass(Ssa::mem2reg, "After Mem2Reg:") .run_pass(Ssa::fold_constants, "After Constant Folding:") - .run_pass( - Ssa::fold_constants_using_constraints, - "After Constant Folding With Constraint Info:", - ) + .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") .finish(); diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index a315695f7db..8f98b3fb17f 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -12,6 +12,7 @@ mod die; pub(crate) mod flatten_cfg; mod inlining; mod mem2reg; +mod rc; mod remove_bit_shifts; mod simplify_cfg; mod unrolling; diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs new file mode 100644 index 00000000000..03c8adec5f5 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -0,0 +1,13 @@ +use crate::ssa::{ir::function::Function, ssa_gen::Ssa}; + +impl Ssa { + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn remove_paired_rc(mut self) -> Ssa { + for function in self.functions.values_mut() { + remove_paired_rc(function); + } + self + } +} + +fn remove_paired_rc(function: &mut Function) {} From af5a47592f34592978ba80892ab2f03f9b803bf2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 13 Mar 2024 11:59:01 -0500 Subject: [PATCH 04/12] Start pass; contemplate what we should do across blocks --- .../src/ssa/function_builder/mod.rs | 4 ++ compiler/noirc_evaluator/src/ssa/opt/rc.rs | 56 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 2c39c83b342..4084ddcd51c 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -384,6 +384,7 @@ impl FunctionBuilder { /// within the given value. If the given value is not an array and does not contain /// any arrays, this does nothing. pub(crate) fn increment_array_reference_count(&mut self, value: ValueId) { + eprintln!("Incrementing RC of {}", value); self.update_array_reference_count(value, true); } @@ -403,7 +404,9 @@ impl FunctionBuilder { Type::Function => (), Type::Reference(element) => { if element.contains_an_array() { + eprint!("{} is a reference containing an array", value); let value = self.insert_load(value, element.as_ref().clone()); + eprintln!(", {} is its load", value); self.increment_array_reference_count(value); } } @@ -415,6 +418,7 @@ impl FunctionBuilder { } else { Instruction::DecrementRc { value } }; + eprint!("{} is an array", value); self.insert_instruction(instruction, None); // This is a bit odd, but in brillig the inc_rc instruction operates on diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 03c8adec5f5..e97d468f844 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -1,6 +1,16 @@ -use crate::ssa::{ir::function::Function, ssa_gen::Ssa}; +use std::collections::HashMap; + +use crate::ssa::{ir::{function::Function, instruction::InstructionId, types::Type}, ssa_gen::Ssa}; impl Ssa { + /// This pass removes `inc_rc` and `dec_rc` instructions + /// as long as there are no `array_set` instructions to an array + /// of the same type in between. + /// + /// Note that this pass is very conservative since the array_set + /// instruction does not need to be to the same array. This is because + /// the given array may alias another array (e.g. function parameters or + /// a `load`ed array from a reference). #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_paired_rc(mut self) -> Ssa { for function in self.functions.values_mut() { @@ -10,4 +20,46 @@ impl Ssa { } } -fn remove_paired_rc(function: &mut Function) {} +#[derive(Default)] +struct Context { + // All inc_rc instructions encountered without a corresponding dec_rc. + // The type of the array being operated on is recorded. + // If an array_set to that array type is encountered, that is also recorded. + inc_rcs: HashMap>, + + // When a dec_rc is encountered, the most recent inc_rc (of a matching array type) + // is popped off the inc_rc_stack. If the IncDec object was not possibly mutated, + // then the inc_rc and dec_rc instructions are both pushed here to be removed + // from the program later. + inc_decs_to_remove: Vec<(InstructionId, InstructionId)>, +} + +struct IncRc { + id: InstructionId, + + // This is currently set to true whenever an array_set to the + // same array type is encountered before the closing dec_rc to this inc_rc. + possibly_mutated: bool, +} + +fn remove_paired_rc(function: &mut Function) { + // `dec_rc` is only issued for parameters currently so we can speed things + // up a bit by skipping any functions without them. + if !contains_array_parameter(function) { + return; + } + + let mut context = Context::default(); + + // Iterate through each block in the function to find array_sets first. + // We'll combine the results of each block later. + for block in function.reachable_blocks() { + + } +} + +fn contains_array_parameter(function: &mut Function) -> bool { + function.parameters().iter().any(|parameter| { + function.dfg.type_of_value(*parameter).contains_an_array() + }) +} From f8778e0d85a4fd9e75f6dc16f51257a956cce345 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 14 Mar 2024 13:28:51 -0500 Subject: [PATCH 05/12] Finish pass --- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 135 ++++++++++++++++++--- 1 file changed, 118 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 36d1deb0a92..2d5be3af17d 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -1,6 +1,15 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use crate::ssa::{ir::{function::Function, instruction::InstructionId, types::Type}, ssa_gen::Ssa}; +use crate::ssa::{ + ir::{ + basic_block::BasicBlockId, + function::Function, + instruction::{Instruction, InstructionId, TerminatorInstruction}, + types::Type, + value::ValueId, + }, + ssa_gen::Ssa, +}; impl Ssa { /// This pass removes `inc_rc` and `dec_rc` instructions @@ -23,19 +32,26 @@ impl Ssa { #[derive(Default)] struct Context { // All inc_rc instructions encountered without a corresponding dec_rc. + // These are only searched for in the first block of a function. + // // The type of the array being operated on is recorded. // If an array_set to that array type is encountered, that is also recorded. - inc_rcs: HashMap, - - dec_rcs: HashMap, + inc_rcs: HashMap>, +} - // When a dec_rc is encountered, the most recent inc_rc (of a matching array type) - // is popped off the inc_rc_stack. If the IncDec object was not possibly mutated, - // then the inc_rc and dec_rc instructions are both pushed here to be removed - // from the program later. - inc_decs_to_remove: Vec<(InstructionId, InstructionId)>, +struct IncRc { + id: InstructionId, + array: ValueId, + possibly_mutated: bool, } +/// This function is very simplistic for now. It takes advantage of the fact that dec_rc +/// instructions are currently issued only at the end of a function for parameters and will +/// only check the first and last block for inc & dec rc instructions to be removed. The rest +/// of the function is still checked for array_set instructions. +/// +/// This restriction lets this function largely ignore merging intermediate results from other +/// blocks and handling loops. fn remove_paired_rc(function: &mut Function) { // `dec_rc` is only issued for parameters currently so we can speed things // up a bit by skipping any functions without them. @@ -45,15 +61,100 @@ fn remove_paired_rc(function: &mut Function) { let mut context = Context::default(); - // Iterate through each block in the function to find array_sets first. - // We'll combine the results of each block later. - for block in function.reachable_blocks() { + context.find_rcs_in_entry_block(function); + context.scan_for_array_sets(function); + let to_remove = context.find_rcs_to_remove(function); + remove_instructions(to_remove, function); +} + +fn contains_array_parameter(function: &mut Function) -> bool { + let mut parameters = function.parameters().iter(); + parameters.any(|parameter| function.dfg.type_of_value(*parameter).contains_an_array()) +} + +impl Context { + fn find_rcs_in_entry_block(&mut self, function: &Function) { + let entry = function.entry_block(); + + for instruction in function.dfg[entry].instructions() { + if let Instruction::IncrementRc { value } = &function.dfg[*instruction] { + let typ = function.dfg.type_of_value(*value); + + // We assume arrays aren't mutated until we find an array_set + let inc_rc = IncRc { id: *instruction, array: *value, possibly_mutated: false }; + self.inc_rcs.entry(typ).or_default().push(inc_rc); + } + } + } + + /// Find each array_set instruction in the function and mark any arrays used + /// by the inc_rc instructions as possibly mutated if they're the same type. + fn scan_for_array_sets(&mut self, function: &Function) { + for block in function.reachable_blocks() { + for instruction in function.dfg[block].instructions() { + if let Instruction::ArraySet { array, .. } = function.dfg[*instruction] { + let typ = function.dfg.type_of_value(array); + if let Some(inc_rcs) = self.inc_rcs.get_mut(&typ) { + for inc_rc in inc_rcs { + inc_rc.possibly_mutated = true; + } + } + } + } + } + } + + /// Find each dec_rc instruction and if the most recent inc_rc instruction for the same value + /// is not possibly mutated, then we can remove them both. Returns each such pair. + fn find_rcs_to_remove(&mut self, function: &Function) -> HashSet { + let last_block = Self::find_last_block(function); + let mut to_remove = HashSet::new(); + + for instruction in function.dfg[last_block].instructions() { + if let Instruction::DecrementRc { value } = &function.dfg[*instruction] { + if let Some(inc_rc) = self.pop_rc_for(*value, function) { + if !inc_rc.possibly_mutated { + to_remove.insert(inc_rc.id); + to_remove.insert(*instruction); + } + } + } + } + + to_remove + } + + /// Finds the block of the function with the Return instruction + fn find_last_block(function: &Function) -> BasicBlockId { + for block in function.reachable_blocks() { + if matches!( + function.dfg[block].terminator(), + Some(TerminatorInstruction::Return { .. }) + ) { + return block; + } + } + + unreachable!("SSA Function {} has no reachable return instruction!", function.id()) + } + + /// Finds and pops the IncRc for the given array value if possible. + fn pop_rc_for(&mut self, value: ValueId, function: &Function) -> Option { + let typ = function.dfg.type_of_value(value); + + let rcs = self.inc_rcs.get_mut(&typ)?; + let position = rcs.iter().position(|inc_rc| inc_rc.array == value)?; + Some(rcs.remove(position)) } } -fn contains_array_parameter(function: &mut Function) -> bool { - function.parameters().iter().any(|parameter| { - function.dfg.type_of_value(*parameter).contains_an_array() - }) +fn remove_instructions(to_remove: HashSet, function: &mut Function) { + if !to_remove.is_empty() { + for block in function.reachable_blocks() { + function.dfg[block] + .instructions_mut() + .retain(|instruction| !to_remove.contains(instruction)) + } + } } From b5abe6d91bec069e4e779a518cd8b31d98905f4a Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 14 Mar 2024 14:56:24 -0500 Subject: [PATCH 06/12] Add tests --- .../src/ssa/function_builder/mod.rs | 33 +++- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 173 ++++++++++++++++++ 2 files changed, 196 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 0d4b8da654a..b514b3b956b 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -309,6 +309,18 @@ impl FunctionBuilder { self.insert_instruction(Instruction::ArraySet { array, index, value }, None).first() } + /// Insert an instruction to increment an array's reference count. This only has an effect + /// in unconstrained code where arrays are reference counted and copy on write. + pub(crate) fn insert_inc_rc(&mut self, value: ValueId) { + self.insert_instruction(Instruction::IncrementRc { value }, None); + } + + /// Insert an instruction to decrement an array's reference count. This only has an effect + /// in unconstrained code where arrays are reference counted and copy on write. + pub(crate) fn insert_dec_rc(&mut self, value: ValueId) { + self.insert_instruction(Instruction::DecrementRc { value }, None); + } + /// Terminates the current block with the given terminator instruction fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { self.current_function.dfg.set_block_terminator(self.current_block, terminator); @@ -403,21 +415,22 @@ impl FunctionBuilder { Type::Function => (), Type::Reference(element) => { if element.contains_an_array() { - eprint!("{} is a reference containing an array", value); let value = self.insert_load(value, element.as_ref().clone()); - eprintln!(", {} is its load", value); - self.increment_array_reference_count(value); + self.update_array_reference_count(value, increment); } } typ @ Type::Array(..) | typ @ Type::Slice(..) => { // If there are nested arrays or slices, we wait until ArrayGet // is issued to increment the count of that array. - let instruction = if increment { - Instruction::IncrementRc { value } - } else { - Instruction::DecrementRc { value } + let update_rc = |this: &mut Self, value| { + if increment { + this.insert_inc_rc(value); + } else { + this.insert_dec_rc(value); + } }; - self.insert_instruction(instruction, None); + + update_rc(self, value); // This is a bit odd, but in brillig the inc_rc instruction operates on // a copy of the array's metadata, so we need to re-store a loaded array @@ -428,8 +441,8 @@ impl FunctionBuilder { // We can't re-use `value` in case the original address was stored // to again in the meantime. So introduce another load. let address = *address; - let value = self.insert_load(address, typ); - self.insert_instruction(Instruction::IncrementRc { value }, None); + let new_load = self.insert_load(address, typ); + update_rc(self, new_load); self.insert_store(address, value); } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 2d5be3af17d..8afa5b9ead3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -158,3 +158,176 @@ fn remove_instructions(to_remove: HashSet, function: &mut Functio } } } + +#[cfg(test)] +mod test { + use std::rc::Rc; + + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{ + basic_block::BasicBlockId, dfg::DataFlowGraph, function::RuntimeType, + instruction::Instruction, map::Id, types::Type, + }, + }; + + fn count_inc_rcs(block: BasicBlockId, dfg: &DataFlowGraph) -> usize { + dfg[block] + .instructions() + .iter() + .filter(|instruction_id| { + matches!(dfg[**instruction_id], Instruction::IncrementRc { .. }) + }) + .count() + } + + fn count_dec_rcs(block: BasicBlockId, dfg: &DataFlowGraph) -> usize { + dfg[block] + .instructions() + .iter() + .filter(|instruction_id| { + matches!(dfg[**instruction_id], Instruction::DecrementRc { .. }) + }) + .count() + } + + #[test] + fn single_block_fn_return_array() { + // This is the output for the program with a function: + // unconstrained fn foo(x: [Field; 2]) -> [[Field; 2]; 1] { + // [array] + // } + // + // fn foo { + // b0(v0: [Field; 2]): + // inc_rc v0 + // inc_rc v0 + // dec_rc v0 + // return [v0] + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("foo".into(), main_id, RuntimeType::Brillig); + + let inner_array_type = Type::Array(Rc::new(vec![Type::field()]), 2); + let v0 = builder.add_parameter(inner_array_type.clone()); + + builder.insert_inc_rc(v0); + builder.insert_inc_rc(v0); + builder.insert_dec_rc(v0); + + let outer_array_type = Type::Array(Rc::new(vec![inner_array_type]), 1); + let array = builder.array_constant(vec![v0].into(), outer_array_type); + builder.terminate_with_return(vec![array]); + + let ssa = builder.finish().remove_paired_rc(); + let main = ssa.main(); + let entry = main.entry_block(); + + assert_eq!(count_inc_rcs(entry, &main.dfg), 1); + assert_eq!(count_dec_rcs(entry, &main.dfg), 0); + } + + #[test] + fn single_block_mutation() { + // fn mutator(mut array: [Field; 2]) { + // array[0] = 5; + // } + // + // fn mutator { + // b0(v0: [Field; 2]): + // v1 = allocate + // store v0 at v1 + // inc_rc v0 + // v2 = load v1 + // v7 = array_set v2, index u64 0, value Field 5 + // store v7 at v1 + // dec_rc v0 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("mutator".into(), main_id, RuntimeType::Acir); + + let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); + let v0 = builder.add_parameter(array_type.clone()); + + let v1 = builder.insert_allocate(array_type.clone()); + builder.insert_store(v1, v0); + builder.insert_inc_rc(v0); + let v2 = builder.insert_load(v1, array_type); + + let zero = builder.numeric_constant(0u128, Type::unsigned(64)); + let five = builder.field_constant(5u128); + let v7 = builder.insert_array_set(v2, zero, five); + + builder.insert_store(v1, v7); + builder.insert_dec_rc(v0); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish().remove_paired_rc(); + let main = ssa.main(); + let entry = main.entry_block(); + + // No changes, the array is possibly mutated + assert_eq!(count_inc_rcs(entry, &main.dfg), 1); + assert_eq!(count_dec_rcs(entry, &main.dfg), 1); + } + + // Similar to single_block_mutation but for a function which + // uses a mutable reference parameter. + #[test] + fn single_block_mutation_through_reference() { + // fn mutator2(array: &mut [Field; 2]) { + // array[0] = 5; + // } + // + // fn mutator2 { + // b0(v0: &mut [Field; 2]): + // v1 = load v0 + // inc_rc v1 + // v2 = load v0 + // inc_rc v2 + // store v2 at v0 + // v3 = load v0 + // v8 = array_set v3, index u64 0, value Field 5 + // store v8 at v0 + // v9 = load v0 + // inc_rc v9 + // v10 = load v0 + // inc_rc v10 + // store v10 at v0 + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("mutator2".into(), main_id, RuntimeType::Acir); + + let array_type = Type::Array(Rc::new(vec![Type::field()]), 2); + let reference_type = Type::Reference(Rc::new(array_type.clone())); + + let v0 = builder.add_parameter(reference_type); + + // This inc_rc is from being used as a parameter + let v1 = builder.insert_load(v0, array_type.clone()); + builder.insert_inc_rc(v1); + + let v1 = builder.insert_allocate(array_type.clone()); + builder.insert_store(v1, v0); + builder.insert_inc_rc(v0); + let v2 = builder.insert_load(v1, array_type); + + let zero = builder.numeric_constant(0u128, Type::unsigned(64)); + let five = builder.field_constant(5u128); + let v7 = builder.insert_array_set(v2, zero, five); + + builder.insert_store(v1, v7); + builder.insert_dec_rc(v0); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish().remove_paired_rc(); + let main = ssa.main(); + let entry = main.entry_block(); + + // No changes, the array is possibly mutated + assert_eq!(count_inc_rcs(entry, &main.dfg), 1); + assert_eq!(count_dec_rcs(entry, &main.dfg), 1); + } +} From fcca384e45e68c59f8005e748b3753a87784db28 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 14 Mar 2024 15:51:12 -0500 Subject: [PATCH 07/12] Fix typo --- .../src/ssa/function_builder/mod.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index b514b3b956b..a8ff3618905 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -195,12 +195,9 @@ impl FunctionBuilder { self.call_stack.clone() } - /// Insert a Load instruction at the end of the current block, loading from the given offset - /// of the given address which should point to a previous Allocate instruction. Note that - /// this is limited to loading a single value. Loading multiple values (such as a tuple) - /// will require multiple loads. - /// 'offset' is in units of FieldElements here. So loading the fourth FieldElement stored in - /// an array will have an offset of 3. + /// Insert a Load instruction at the end of the current block, loading from the given address + /// which should point to a previous Allocate instruction. Note that this is limited to loading + /// a single value. Loading multiple values (such as a tuple) will require multiple loads. /// Returns the element that was loaded. pub(crate) fn insert_load(&mut self, address: ValueId, type_to_load: Type) -> ValueId { self.insert_instruction(Instruction::Load { address }, Some(vec![type_to_load])).first() @@ -221,11 +218,9 @@ impl FunctionBuilder { operator: BinaryOp, rhs: ValueId, ) -> ValueId { - assert_eq!( - self.type_of_value(lhs), - self.type_of_value(rhs), - "ICE - Binary instruction operands must have the same type" - ); + let lhs_type = self.type_of_value(lhs); + let rhs_type = self.type_of_value(rhs); + assert_eq!(lhs_type, rhs_type, "ICE - Binary instruction operands must have the same type"); let instruction = Instruction::Binary(Binary { lhs, rhs, operator }); self.insert_instruction(instruction, None).first() } @@ -443,7 +438,7 @@ impl FunctionBuilder { let address = *address; let new_load = self.insert_load(address, typ); update_rc(self, new_load); - self.insert_store(address, value); + self.insert_store(address, new_load); } } } From 410aa4fbebe91275c9ecfbeb597db3ff2c7af8f2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 14 Mar 2024 15:56:59 -0500 Subject: [PATCH 08/12] clippy --- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 8afa5b9ead3..4ee0ec70dc3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -154,7 +154,7 @@ fn remove_instructions(to_remove: HashSet, function: &mut Functio for block in function.reachable_blocks() { function.dfg[block] .instructions_mut() - .retain(|instruction| !to_remove.contains(instruction)) + .retain(|instruction| !to_remove.contains(instruction)); } } } From a12b90c1796961db95d044e68f7829f5d8fa745f Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 15 Mar 2024 10:41:48 -0500 Subject: [PATCH 09/12] Remove extra inc/dec rc for arrays behind references --- .../src/ssa/function_builder/mod.rs | 25 ++++++++++++----- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 28 ++++++++----------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index a8ff3618905..aa5a7fedd92 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -391,27 +391,33 @@ impl FunctionBuilder { /// within the given value. If the given value is not an array and does not contain /// any arrays, this does nothing. pub(crate) fn increment_array_reference_count(&mut self, value: ValueId) { - self.update_array_reference_count(value, true); + self.update_array_reference_count(value, true, None); } /// Insert instructions to decrement the reference count of any array(s) stored /// within the given value. If the given value is not an array and does not contain /// any arrays, this does nothing. pub(crate) fn decrement_array_reference_count(&mut self, value: ValueId) { - self.update_array_reference_count(value, false); + self.update_array_reference_count(value, false, None); } /// Increment or decrement the given value's reference count if it is an array. /// If it is not an array, this does nothing. Note that inc_rc and dec_rc instructions /// are ignored outside of unconstrained code. - pub(crate) fn update_array_reference_count(&mut self, value: ValueId, increment: bool) { + fn update_array_reference_count( + &mut self, + value: ValueId, + increment: bool, + load_address: Option, + ) { match self.type_of_value(value) { Type::Numeric(_) => (), Type::Function => (), Type::Reference(element) => { if element.contains_an_array() { - let value = self.insert_load(value, element.as_ref().clone()); - self.update_array_reference_count(value, increment); + let reference = value; + let value = self.insert_load(reference, element.as_ref().clone()); + self.update_array_reference_count(value, increment, Some(reference)); } } typ @ Type::Array(..) | typ @ Type::Slice(..) => { @@ -426,12 +432,17 @@ impl FunctionBuilder { }; update_rc(self, value); + let dfg = &self.current_function.dfg; // This is a bit odd, but in brillig the inc_rc instruction operates on // a copy of the array's metadata, so we need to re-store a loaded array // even if there have been no other changes to it. - if let Value::Instruction { instruction, .. } = &self.current_function.dfg[value] { - let instruction = &self.current_function.dfg[*instruction]; + if let Some(address) = load_address { + // If we already have a load from the Type::Reference case, avoid inserting + // another load and rc update. + self.insert_store(address, value); + } else if let Value::Instruction { instruction, .. } = &dfg[value] { + let instruction = &dfg[*instruction]; if let Instruction::Load { address } = instruction { // We can't re-use `value` in case the original address was stored // to again in the meantime. So introduce another load. diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 4ee0ec70dc3..4766bc3e8d2 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -284,17 +284,13 @@ mod test { // b0(v0: &mut [Field; 2]): // v1 = load v0 // inc_rc v1 + // store v1 at v0 // v2 = load v0 - // inc_rc v2 - // store v2 at v0 - // v3 = load v0 - // v8 = array_set v3, index u64 0, value Field 5 + // v7 = array_set v2, index u64 0, value Field 5 + // store v7 at v0 + // v8 = load v0 + // dec_rc v8 // store v8 at v0 - // v9 = load v0 - // inc_rc v9 - // v10 = load v0 - // inc_rc v10 - // store v10 at v0 // return // } let main_id = Id::test_new(0); @@ -305,21 +301,19 @@ mod test { let v0 = builder.add_parameter(reference_type); - // This inc_rc is from being used as a parameter let v1 = builder.insert_load(v0, array_type.clone()); builder.insert_inc_rc(v1); + builder.insert_store(v0, v1); - let v1 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v1, v0); - builder.insert_inc_rc(v0); - let v2 = builder.insert_load(v1, array_type); - + let v2 = builder.insert_load(v1, array_type.clone()); let zero = builder.numeric_constant(0u128, Type::unsigned(64)); let five = builder.field_constant(5u128); let v7 = builder.insert_array_set(v2, zero, five); - builder.insert_store(v1, v7); - builder.insert_dec_rc(v0); + builder.insert_store(v0, v7); + let v8 = builder.insert_load(v0, array_type); + builder.insert_dec_rc(v8); + builder.insert_store(v0, v8); builder.terminate_with_return(vec![]); let ssa = builder.finish().remove_paired_rc(); From 0cc93d92b0ef59687bf5f584eaf1c75ba80e1302 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 12:05:35 -0500 Subject: [PATCH 10/12] Add new test --- .../brillig_rc_opt/Nargo.toml | 7 +++++++ .../brillig_rc_opt/src/main.nr | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 test_programs/execution_success/brillig_rc_opt/Nargo.toml create mode 100644 test_programs/execution_success/brillig_rc_opt/src/main.nr diff --git a/test_programs/execution_success/brillig_rc_opt/Nargo.toml b/test_programs/execution_success/brillig_rc_opt/Nargo.toml new file mode 100644 index 00000000000..c70adae211c --- /dev/null +++ b/test_programs/execution_success/brillig_rc_opt/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "brillig_rc_opt" +type = "bin" +authors = [""] +compiler_version = ">=0.25.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/brillig_rc_opt/src/main.nr b/test_programs/execution_success/brillig_rc_opt/src/main.nr new file mode 100644 index 00000000000..fd2d918f2e1 --- /dev/null +++ b/test_programs/execution_success/brillig_rc_opt/src/main.nr @@ -0,0 +1,21 @@ + +unconstrained fn main() { + let mut array = [1, 2, 3]; + + let mut alias = get_alias(array); + alias[0] = 0; + + assert(array[0] == 1); + assert(alias[0] == 0); + + let alias2 = get_alias(&mut array); + alias2[0] = 4; + + assert(array[0] == 4); + assert(alias[0] == 0); + assert((*alias2)[0] == 4); +} + +unconstrained fn get_alias(x: T) -> T { + x +} From 383ad40bb43e2ddb375f1b0f818597b0992e0bb9 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 12:46:46 -0500 Subject: [PATCH 11/12] Remove extra line --- test_programs/execution_success/brillig_rc_opt/src/main.nr | 1 - 1 file changed, 1 deletion(-) diff --git a/test_programs/execution_success/brillig_rc_opt/src/main.nr b/test_programs/execution_success/brillig_rc_opt/src/main.nr index fd2d918f2e1..93bacd30d9d 100644 --- a/test_programs/execution_success/brillig_rc_opt/src/main.nr +++ b/test_programs/execution_success/brillig_rc_opt/src/main.nr @@ -1,4 +1,3 @@ - unconstrained fn main() { let mut array = [1, 2, 3]; From ded5a89402f74ae0575942f4543292e8c56c9f91 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 13:35:35 -0500 Subject: [PATCH 12/12] Remove test --- .../src/monomorphization/debug.rs | 4 ++-- .../brillig_rc_opt/Nargo.toml | 7 ------- .../brillig_rc_opt/src/main.nr | 20 ------------------- 3 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 test_programs/execution_success/brillig_rc_opt/Nargo.toml delete mode 100644 test_programs/execution_success/brillig_rc_opt/src/main.nr diff --git a/compiler/noirc_frontend/src/monomorphization/debug.rs b/compiler/noirc_frontend/src/monomorphization/debug.rs index cf4e0ab792e..3a03177f8ec 100644 --- a/compiler/noirc_frontend/src/monomorphization/debug.rs +++ b/compiler/noirc_frontend/src/monomorphization/debug.rs @@ -195,8 +195,8 @@ fn element_type_at_index(ptype: &PrintableType, i: usize) -> &PrintableType { PrintableType::Tuple { types } => &types[i], PrintableType::Struct { name: _name, fields } => &fields[i].1, PrintableType::String { length: _length } => &PrintableType::UnsignedInteger { width: 8 }, - _ => { - panic!["expected type with sub-fields, found terminal type"] + other => { + panic!["expected type with sub-fields, found terminal type: {other:?}"] } } } diff --git a/test_programs/execution_success/brillig_rc_opt/Nargo.toml b/test_programs/execution_success/brillig_rc_opt/Nargo.toml deleted file mode 100644 index c70adae211c..00000000000 --- a/test_programs/execution_success/brillig_rc_opt/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "brillig_rc_opt" -type = "bin" -authors = [""] -compiler_version = ">=0.25.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/brillig_rc_opt/src/main.nr b/test_programs/execution_success/brillig_rc_opt/src/main.nr deleted file mode 100644 index 93bacd30d9d..00000000000 --- a/test_programs/execution_success/brillig_rc_opt/src/main.nr +++ /dev/null @@ -1,20 +0,0 @@ -unconstrained fn main() { - let mut array = [1, 2, 3]; - - let mut alias = get_alias(array); - alias[0] = 0; - - assert(array[0] == 1); - assert(alias[0] == 0); - - let alias2 = get_alias(&mut array); - alias2[0] = 4; - - assert(array[0] == 4); - assert(alias[0] == 0); - assert((*alias2)[0] == 4); -} - -unconstrained fn get_alias(x: T) -> T { - x -}