diff --git a/crates/nargo_cli/tests/execution_success/references_aliasing/Nargo.toml b/crates/nargo_cli/tests/execution_success/references_aliasing/Nargo.toml new file mode 100644 index 00000000000..b52fdcf77f0 --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/references_aliasing/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "references" +type = "bin" +authors = [""] +compiler_version = "0.5.1" + +[dependencies] diff --git a/crates/nargo_cli/tests/execution_success/references_aliasing/Prover.toml b/crates/nargo_cli/tests/execution_success/references_aliasing/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/nargo_cli/tests/execution_success/references_aliasing/src/main.nr b/crates/nargo_cli/tests/execution_success/references_aliasing/src/main.nr new file mode 100644 index 00000000000..4582444c8f7 --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/references_aliasing/src/main.nr @@ -0,0 +1,10 @@ +fn increment(mut r: &mut Field) { + *r = *r + 1; +} + +fn main() { + let mut x = 100; + let mut xref = &mut x; + increment(xref); + assert(*xref == 101); +} diff --git a/crates/nargo_cli/tests/execution_success/references_aliasing/target/references.bytecode b/crates/nargo_cli/tests/execution_success/references_aliasing/target/references.bytecode new file mode 100644 index 00000000000..3e521923327 --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/references_aliasing/target/references.bytecode @@ -0,0 +1 @@ +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 diff --git a/crates/nargo_cli/tests/execution_success/references_aliasing/target/witness.tr b/crates/nargo_cli/tests/execution_success/references_aliasing/target/witness.tr new file mode 100644 index 00000000000..22a1c7fe109 Binary files /dev/null and b/crates/nargo_cli/tests/execution_success/references_aliasing/target/witness.tr differ diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 33bdfcd3946..08898ec4bff 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1089,7 +1089,7 @@ impl<'block> BrilligBlock<'block> { /// Returns the type of the operation considering the types of the operands /// TODO: SSA issues binary operations between fields and integers. /// This probably should be explicitly casted in SSA to avoid having to coerce at this level. -pub(crate) fn type_of_binary_operation(lhs_type: Type, rhs_type: Type) -> Type { +pub(crate) fn type_of_binary_operation(lhs_type: &Type, rhs_type: &Type) -> Type { match (lhs_type, rhs_type) { (_, Type::Function) | (Type::Function, _) => { unreachable!("Functions are invalid in binary operations") @@ -1106,7 +1106,7 @@ pub(crate) fn type_of_binary_operation(lhs_type: Type, rhs_type: Type) -> Type { // If either side is a Field constant then, we coerce into the type // of the other operand (Type::Numeric(NumericType::NativeField), typ) - | (typ, Type::Numeric(NumericType::NativeField)) => typ, + | (typ, Type::Numeric(NumericType::NativeField)) => typ.clone(), // If both sides are numeric type, then we expect their types to be // the same. (Type::Numeric(lhs_type), Type::Numeric(rhs_type)) => { @@ -1114,7 +1114,7 @@ pub(crate) fn type_of_binary_operation(lhs_type: Type, rhs_type: Type) -> Type { lhs_type, rhs_type, "lhs and rhs types in a binary operation are always the same" ); - Type::Numeric(lhs_type) + Type::Numeric(*lhs_type) } } } diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index 7dbd627a949..dd8fae50ade 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -63,6 +63,7 @@ pub(crate) fn optimize_into_acir( // and this pass is missed, slice merging will fail inside of flattening. .mem2reg() .print(print_ssa_passes, "After Mem2Reg:") + .fold_constants() .flatten_cfg() .print(print_ssa_passes, "After Flattening:") // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores diff --git a/crates/noirc_evaluator/src/ssa/ir/dfg.rs b/crates/noirc_evaluator/src/ssa/ir/dfg.rs index 7dcf652c6e6..a9523afb2a4 100644 --- a/crates/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa/ir/dfg.rs @@ -106,7 +106,7 @@ impl DataFlowGraph { let parameters = self.blocks[block].parameters(); let parameters = vecmap(parameters.iter().enumerate(), |(position, param)| { - let typ = self.values[*param].get_type(); + let typ = self.values[*param].get_type().clone(); self.values.insert(Value::Param { block: new_block, position, typ }) }); @@ -314,7 +314,13 @@ impl DataFlowGraph { /// Returns the type of a given value pub(crate) fn type_of_value(&self, value: ValueId) -> Type { - self.values[value].get_type() + self.values[value].get_type().clone() + } + + /// True if the type of this value is Type::Reference. + /// Using this method over type_of_value avoids cloning the value's type. + pub(crate) fn value_is_reference(&self, value: ValueId) -> bool { + matches!(self.values[value].get_type(), Type::Reference) } /// Appends a result type to the instruction. diff --git a/crates/noirc_evaluator/src/ssa/ir/value.rs b/crates/noirc_evaluator/src/ssa/ir/value.rs index 54831eb4a07..8c2a4e88ef3 100644 --- a/crates/noirc_evaluator/src/ssa/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa/ir/value.rs @@ -57,15 +57,15 @@ pub(crate) enum Value { impl Value { /// Retrieves the type of this Value - pub(crate) fn get_type(&self) -> Type { + pub(crate) fn get_type(&self) -> &Type { match self { - Value::Instruction { typ, .. } => typ.clone(), - Value::Param { typ, .. } => typ.clone(), - Value::NumericConstant { typ, .. } => typ.clone(), - Value::Array { typ, .. } => typ.clone(), - Value::Function { .. } => Type::Function, - Value::Intrinsic { .. } => Type::Function, - Value::ForeignFunction { .. } => Type::Function, + Value::Instruction { typ, .. } => typ, + Value::Param { typ, .. } => typ, + Value::NumericConstant { typ, .. } => typ, + Value::Array { typ, .. } => typ, + Value::Function { .. } => &Type::Function, + Value::Intrinsic { .. } => &Type::Function, + Value::ForeignFunction { .. } => &Type::Function, } } } diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index 3e63aec63b4..4d2531fa27c 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -121,6 +121,9 @@ struct Block { /// will map to a Reference value which tracks whether the last value stored /// to the reference is known. references: BTreeMap, + + /// The last instance of a `Store` instruction to each address in this block + last_stores: BTreeMap, } /// An `Expression` here is used to represent a canonical key @@ -129,6 +132,7 @@ struct Block { #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] enum Expression { Dereference(Box), + ArrayElement(Box), Other(ValueId), } @@ -175,7 +179,8 @@ impl PerFunctionContext { let mut predecessors = self.cfg.predecessors(block); if let Some(first_predecessor) = predecessors.next() { - let first = self.blocks.get(&first_predecessor).cloned().unwrap_or_default(); + let mut first = self.blocks.get(&first_predecessor).cloned().unwrap_or_default(); + first.last_stores.clear(); // Note that we have to start folding with the first block as the accumulator. // If we started with an empty block, an empty block union'd with any other block @@ -201,19 +206,18 @@ impl PerFunctionContext { mut references: Block, ) { let instructions = function.dfg[block].instructions().to_vec(); - let mut last_stores = BTreeMap::new(); for instruction in instructions { - self.analyze_instruction(function, &mut references, instruction, &mut last_stores); + self.analyze_instruction(function, &mut references, instruction); } - self.handle_terminator(function, block, &mut references, &mut last_stores); + self.handle_terminator(function, block, &mut references); // If there's only 1 block in the function total, we can remove any remaining last stores // as well. We can't do this if there are multiple blocks since subsequent blocks may // reference these stores. if self.post_order.as_slice().len() == 1 { - self.remove_stores_that_do_not_alias_parameters(function, &references, last_stores); + self.remove_stores_that_do_not_alias_parameters(function, &references); } self.blocks.insert(block, references); @@ -225,26 +229,23 @@ impl PerFunctionContext { &mut self, function: &Function, references: &Block, - last_stores: BTreeMap, ) { let reference_parameters = function .parameters() .iter() - .filter(|param| function.dfg.type_of_value(**param) == Type::Reference) + .filter(|param| function.dfg.value_is_reference(**param)) .collect::>(); - for (allocation, instruction) in last_stores { - if let Some(expression) = references.expressions.get(&allocation) { + for (allocation, instruction) in &references.last_stores { + if let Some(expression) = references.expressions.get(allocation) { if let Some(aliases) = references.aliases.get(expression) { let allocation_aliases_parameter = aliases.iter().any(|alias| reference_parameters.contains(alias)); if !aliases.is_empty() && !allocation_aliases_parameter { - self.instructions_to_remove.insert(instruction); + self.instructions_to_remove.insert(*instruction); } } - } else { - self.instructions_to_remove.insert(instruction); } } } @@ -254,7 +255,6 @@ impl PerFunctionContext { function: &mut Function, references: &mut Block, instruction: InstructionId, - last_stores: &mut BTreeMap, ) { match &function.dfg[instruction] { Instruction::Load { address } => { @@ -270,24 +270,26 @@ impl PerFunctionContext { self.instructions_to_remove.insert(instruction); } else { - last_stores.remove(&address); + references.mark_value_used(address, function); } } Instruction::Store { address, value } => { let address = function.dfg.resolve(*address); let value = function.dfg.resolve(*value); + self.check_array_aliasing(function, references, value); + // If there was another store to this instruction without any (unremoved) loads or // function calls in-between, we can remove the previous store. - if let Some(last_store) = last_stores.get(&address) { + if let Some(last_store) = references.last_stores.get(&address) { self.instructions_to_remove.insert(*last_store); } - references.set_known_value(address, value, last_stores); - last_stores.insert(address, instruction); + references.set_known_value(address, value); + references.last_stores.insert(address, instruction); } Instruction::Call { arguments, .. } => { - self.mark_all_unknown(arguments, function, references, last_stores); + self.mark_all_unknown(arguments, function, references); } Instruction::Allocate => { // Register the new reference @@ -298,22 +300,85 @@ impl PerFunctionContext { aliases.insert(result); references.aliases.insert(Expression::Other(result), aliases); } + Instruction::ArrayGet { array, .. } => { + let result = function.dfg.instruction_results(instruction)[0]; + references.mark_value_used(*array, function); + + if function.dfg.value_is_reference(result) { + let array = function.dfg.resolve(*array); + let expression = Expression::ArrayElement(Box::new(Expression::Other(array))); + + if let Some(aliases) = references.aliases.get_mut(&expression) { + aliases.insert(result); + } + } + } + Instruction::ArraySet { array, value, .. } => { + references.mark_value_used(*array, function); + + if function.dfg.value_is_reference(*value) { + let result = function.dfg.instruction_results(instruction)[0]; + let array = function.dfg.resolve(*array); + + let expression = Expression::ArrayElement(Box::new(Expression::Other(array))); + + if let Some(aliases) = references.aliases.get_mut(&expression) { + aliases.insert(result); + } else if let Some((elements, _)) = function.dfg.get_array_constant(array) { + // TODO: This should be a unification of each alias set + // If any are empty, the whole should be as well. + for reference in elements { + self.try_add_alias(references, reference, array); + } + } + + references.expressions.insert(result, expression); + } + } _ => (), } } - fn mark_all_unknown( - &self, - values: &[ValueId], - function: &Function, - references: &mut Block, - last_stores: &mut BTreeMap, - ) { + fn check_array_aliasing(&self, function: &Function, references: &mut Block, array: ValueId) { + if let Some((elements, typ)) = function.dfg.get_array_constant(array) { + if Self::contains_references(&typ) { + // TODO: Check if type directly holds references or holds arrays that hold references + let expr = Expression::ArrayElement(Box::new(Expression::Other(array))); + references.expressions.insert(array, expr.clone()); + let aliases = references.aliases.entry(expr).or_default(); + + for element in elements { + aliases.insert(element); + } + } + } + } + + fn contains_references(typ: &Type) -> bool { + match typ { + Type::Numeric(_) => false, + Type::Function => false, + Type::Reference => true, + Type::Array(elements, _) | Type::Slice(elements) => { + elements.iter().any(Self::contains_references) + } + } + } + + fn try_add_alias(&self, references: &mut Block, reference: ValueId, alias: ValueId) { + if let Some(expression) = references.expressions.get(&reference) { + if let Some(aliases) = references.aliases.get_mut(expression) { + aliases.insert(alias); + } + } + } + + fn mark_all_unknown(&self, values: &[ValueId], function: &Function, references: &mut Block) { for value in values { - if function.dfg.type_of_value(*value) == Type::Reference { + if function.dfg.value_is_reference(*value) { let value = function.dfg.resolve(*value); - references.set_unknown(value, last_stores); - last_stores.remove(&value); + references.set_unknown(value); + references.mark_value_used(value, function); } } } @@ -335,7 +400,6 @@ impl PerFunctionContext { function: &mut Function, block: BasicBlockId, references: &mut Block, - last_stores: &mut BTreeMap, ) { match function.dfg[block].unwrap_terminator() { TerminatorInstruction::JmpIf { .. } => (), // Nothing to do @@ -345,7 +409,7 @@ impl PerFunctionContext { // Add an alias for each reference parameter for (parameter, argument) in destination_parameters.iter().zip(arguments) { - if function.dfg.type_of_value(*parameter) == Type::Reference { + if function.dfg.value_is_reference(*parameter) { let argument = function.dfg.resolve(*argument); if let Some(expression) = references.expressions.get(&argument) { @@ -361,7 +425,7 @@ impl PerFunctionContext { // Removing all `last_stores` for each returned reference is more important here // than setting them all to ReferenceValue::Unknown since no other block should // have a block with a Return terminator as a predecessor anyway. - self.mark_all_unknown(return_values, function, references, last_stores); + self.mark_all_unknown(return_values, function, references); } } } @@ -387,57 +451,42 @@ impl Block { } /// If the given address is known, set its value to `ReferenceValue::Known(value)`. - fn set_known_value( - &mut self, - address: ValueId, - value: ValueId, - last_stores: &mut BTreeMap, - ) { - self.set_value(address, ReferenceValue::Known(value), last_stores); + fn set_known_value(&mut self, address: ValueId, value: ValueId) { + self.set_value(address, ReferenceValue::Known(value)); } - fn set_unknown( - &mut self, - address: ValueId, - last_stores: &mut BTreeMap, - ) { - self.set_value(address, ReferenceValue::Unknown, last_stores); + fn set_unknown(&mut self, address: ValueId) { + self.set_value(address, ReferenceValue::Unknown); } - fn set_value( - &mut self, - address: ValueId, - value: ReferenceValue, - last_stores: &mut BTreeMap, - ) { - if let Some(expression) = self.expressions.get(&address) { - if let Some(aliases) = self.aliases.get(expression) { - if aliases.is_empty() { - // uh-oh, we don't know at all what this reference refers to, could be anything. - // Now we have to invalidate every reference we know of - self.invalidate_all_references(last_stores); - } else if aliases.len() == 1 { - let alias = aliases.first().expect("There should be exactly 1 alias"); - self.references.insert(*alias, value); - } else { - // More than one alias. We're not sure which it refers to so we have to - // conservatively invalidate all references it may refer to. - for alias in aliases.iter() { - if let Some(reference_value) = self.references.get_mut(alias) { - *reference_value = ReferenceValue::Unknown; - } - } + fn set_value(&mut self, address: ValueId, value: ReferenceValue) { + let expression = self.expressions.entry(address).or_insert(Expression::Other(address)); + let aliases = self.aliases.entry(expression.clone()).or_default(); + + if aliases.is_empty() { + // uh-oh, we don't know at all what this reference refers to, could be anything. + // Now we have to invalidate every reference we know of + self.invalidate_all_references(); + } else if aliases.len() == 1 { + let alias = aliases.first().expect("There should be exactly 1 alias"); + self.references.insert(*alias, value); + } else { + // More than one alias. We're not sure which it refers to so we have to + // conservatively invalidate all references it may refer to. + for alias in aliases.iter() { + if let Some(reference_value) = self.references.get_mut(alias) { + *reference_value = ReferenceValue::Unknown; } } } } - fn invalidate_all_references(&mut self, last_stores: &mut BTreeMap) { + fn invalidate_all_references(&mut self) { for reference_value in self.references.values_mut() { *reference_value = ReferenceValue::Unknown; } - last_stores.clear(); + self.last_stores.clear(); } fn unify(mut self, other: &Self) -> Self { @@ -477,7 +526,7 @@ impl Block { /// Remember that `result` is the result of dereferencing `address`. This is important to /// track aliasing when references are stored within other references. fn remember_dereference(&mut self, function: &Function, address: ValueId, result: ValueId) { - if function.dfg.type_of_value(result) == Type::Reference { + if function.dfg.value_is_reference(result) { if let Some(known_address) = self.get_known_value(address) { self.expressions.insert(result, Expression::Other(known_address)); } else { @@ -489,6 +538,56 @@ impl Block { } } } + + /// Iterate through each known alias of the given address and apply the function `f` to each. + fn for_each_alias_of( + &mut self, + address: ValueId, + mut f: impl FnMut(&mut Self, ValueId) -> T, + ) { + if let Some(expr) = self.expressions.get(&address) { + if let Some(aliases) = self.aliases.get(expr).cloned() { + for alias in aliases { + f(self, alias); + } + } + } + } + + fn keep_last_stores_for(&mut self, address: ValueId, function: &Function) { + let address = function.dfg.resolve(address); + self.keep_last_store(address, function); + self.for_each_alias_of(address, |t, alias| t.keep_last_store(alias, function)); + } + + fn keep_last_store(&mut self, address: ValueId, function: &Function) { + let address = function.dfg.resolve(address); + + if let Some(instruction) = self.last_stores.remove(&address) { + // Whenever we decide we want to keep a store instruction, we also need + // to go through its stored value and mark that used as well. + match &function.dfg[instruction] { + Instruction::Store { value, .. } => { + self.mark_value_used(*value, function); + } + other => { + unreachable!("last_store held an id of a non-store instruction: {other:?}") + } + } + } + } + + fn mark_value_used(&mut self, value: ValueId, function: &Function) { + self.keep_last_stores_for(value, function); + + // We must do a recursive check for arrays since they're the only Values which may contain + // other ValueIds. + if let Some((array, _)) = function.dfg.get_array_constant(value) { + for value in array { + self.mark_value_used(value, function); + } + } + } } impl ReferenceValue {