From 1f9c258e400a55bf62db53211e918cc296726624 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 22 Aug 2023 19:14:51 +0000 Subject: [PATCH 01/46] initial work for get dynamic indexing for slices working --- .../execution_success/slices/src/main.nr | 118 +++++++++++------- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 13 +- .../src/ssa/ir/instruction/call.rs | 2 +- crates/noirc_evaluator/src/ssa/ir/printer.rs | 28 +++-- .../src/ssa/opt/flatten_cfg.rs | 25 +++- .../src/ssa/ssa_builder/mod.rs | 3 +- 7 files changed, 128 insertions(+), 63 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 8fbf0a19fc5..ddb97e61e55 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -1,53 +1,79 @@ use dep::std::slice; use dep::std; + fn main(x : Field, y : pub Field) { - let mut slice = [0; 2]; - assert(slice[0] == 0); - assert(slice[0] != 1); - slice[0] = x; - assert(slice[0] == x); - - let slice_plus_10 = slice.push_back(y); - assert(slice_plus_10[2] == 10); - assert(slice_plus_10[2] != 8); - assert(slice_plus_10.len() == 3); - - let mut new_slice = []; - for i in 0..5 { - new_slice = new_slice.push_back(i); + // let mut new_slice = []; + // for i in 0..5 { + // new_slice = new_slice.push_back(i + 10); + // } + // assert(new_slice.len() == 5); + let mut slice = [104, 101, 108, 108, 111]; + regression_dynamic_slice_index(slice, x - 1, x - 4); + + + // let mut slice = [0; 2]; + // assert(slice[0] == 0); + // assert(slice[0] != 1); + // slice[0] = x; + // assert(slice[0] == x); + + // let slice_plus_10 = slice.push_back(y); + // assert(slice_plus_10[2] == 10); + // assert(slice_plus_10[2] != 8); + // assert(slice_plus_10.len() == 3); + + // let mut new_slice = []; + // for i in 0..5 { + // new_slice = new_slice.push_back(i); + // } + // assert(new_slice.len() == 5); + + // new_slice = new_slice.push_front(20); + // assert(new_slice[0] == 20); + // assert(new_slice.len() == 6); + + // let (popped_slice, last_elem) = new_slice.pop_back(); + // assert(last_elem == 4); + // assert(popped_slice.len() == 5); + + // let (first_elem, rest_of_slice) = popped_slice.pop_front(); + // assert(first_elem == 20); + // assert(rest_of_slice.len() == 4); + + // new_slice = rest_of_slice.insert(2, 100); + // assert(new_slice[2] == 100); + // assert(new_slice[4] == 3); + // assert(new_slice.len() == 5); + + // let (remove_slice, removed_elem) = new_slice.remove(3); + // assert(removed_elem == 2); + // assert(remove_slice[3] == 3); + // assert(remove_slice.len() == 4); + + // let append = [1, 2].append([3, 4, 5]); + // assert(append.len() == 5); + // assert(append[0] == 1); + // assert(append[4] == 5); + + // regression_2083(); + // // The parameters to this function must come from witness values (inputs to main) + // regression_merge_slices(x, y); +} + +fn regression_dynamic_slice_index(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 111); + assert(slice[y] == 101); + slice[y] = 0; + assert(slice[x] == 111); + assert(slice[1] == 0); + if x as u32 < 10 { + // slice[x] = slice[x] - 2; + // assert(slice[x] == 2); + } else { + // slice[x] = 0; + // assert(slice[x] == 0); } - assert(new_slice.len() == 5); - - new_slice = new_slice.push_front(20); - assert(new_slice[0] == 20); - assert(new_slice.len() == 6); - - let (popped_slice, last_elem) = new_slice.pop_back(); - assert(last_elem == 4); - assert(popped_slice.len() == 5); - - let (first_elem, rest_of_slice) = popped_slice.pop_front(); - assert(first_elem == 20); - assert(rest_of_slice.len() == 4); - - new_slice = rest_of_slice.insert(2, 100); - assert(new_slice[2] == 100); - assert(new_slice[4] == 3); - assert(new_slice.len() == 5); - - let (remove_slice, removed_elem) = new_slice.remove(3); - assert(removed_elem == 2); - assert(remove_slice[3] == 3); - assert(remove_slice.len() == 4); - - let append = [1, 2].append([3, 4, 5]); - assert(append.len() == 5); - assert(append[0] == 1); - assert(append[4] == 5); - - regression_2083(); - // The parameters to this function must come from witness values (inputs to main) - regression_merge_slices(x, y); + // assert(slice[4] == 109); } // Ensure that slices of struct/tuple values work. diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index f1c865a4b8f..40bb5034d37 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -407,7 +407,7 @@ impl Context { last_array_uses, )?; } - Instruction::ArraySet { array, index, value } => { + Instruction::ArraySet { array, index, value, length } => { self.handle_array_operation( instruction_id, *array, diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction.rs b/crates/noirc_evaluator/src/ssa/ir/instruction.rs index 6b68b0f85a4..58e2e2bb58c 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction.rs @@ -170,7 +170,9 @@ pub(crate) enum Instruction { /// Creates a new array with the new value at the given index. All other elements are identical /// to those in the given array. This will not modify the original array. - ArraySet { array: ValueId, index: ValueId, value: ValueId }, + /// + /// An optional length can be provided to enabling handling of dynamic slice indices + ArraySet { array: ValueId, index: ValueId, value: ValueId, length: Option }, } impl Instruction { @@ -269,8 +271,8 @@ impl Instruction { Instruction::ArrayGet { array, index } => { Instruction::ArrayGet { array: f(*array), index: f(*index) } } - Instruction::ArraySet { array, index, value } => { - Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value) } + Instruction::ArraySet { array, index, value, length } => { + Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value), length: length.map(|l| f(l)) } } } } @@ -304,10 +306,11 @@ impl Instruction { f(*array); f(*index); } - Instruction::ArraySet { array, index, value } => { + Instruction::ArraySet { array, index, value, length } => { f(*array); f(*index); f(*value); + length.map(|l| f(l)); } Instruction::EnableSideEffects { condition } => { f(*condition); @@ -365,7 +368,7 @@ impl Instruction { } None } - Instruction::ArraySet { array, index, value } => { + Instruction::ArraySet { array, index, value, .. } => { let array = dfg.get_array_constant(*array); let index = dfg.get_numeric_constant(*index); if let (Some((array, element_type)), Some(index)) = (array, index) { diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index 713bc8b0997..bd442f31a55 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -75,7 +75,7 @@ pub(super) fn simplify_call( if let Some(length) = dfg.try_get_array_length(arguments[0]) { let length = FieldElement::from(length as u128); SimplifyResult::SimplifiedTo(dfg.make_constant(length, Type::field())) - } else if matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) { + } else if arguments.len() > 1 && matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) { SimplifyResult::SimplifiedTo(arguments[0]) } else { SimplifyResult::None diff --git a/crates/noirc_evaluator/src/ssa/ir/printer.rs b/crates/noirc_evaluator/src/ssa/ir/printer.rs index e8cea151ad1..936bc1fa201 100644 --- a/crates/noirc_evaluator/src/ssa/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa/ir/printer.rs @@ -162,14 +162,26 @@ pub(crate) fn display_instruction( Instruction::ArrayGet { array, index } => { writeln!(f, "array_get {}, index {}", show(*array), show(*index)) } - Instruction::ArraySet { array, index, value } => { - writeln!( - f, - "array_set {}, index {}, value {}", - show(*array), - show(*index), - show(*value) - ) + Instruction::ArraySet { array, index, value, length } => { + if let Some(length) = length { + writeln!( + f, + "array_set {}, index {}, value {}, length {}", + show(*array), + show(*index), + show(*value), + show(*length), + ) + } else { + writeln!( + f, + "array_set {}, index {}, value {}", + show(*array), + show(*index), + show(*value) + ) + } + } } } diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 7eb266aaf75..8901ab176ad 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -143,7 +143,7 @@ use crate::ssa::{ dfg::{CallStack, InsertInstructionResult}, function::Function, function_inserter::FunctionInserter, - instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, + instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction, Intrinsic}, types::Type, value::{Value, ValueId}, }, @@ -386,12 +386,15 @@ impl<'f> Context<'f> { ) -> ValueId { match self.inserter.function.dfg.type_of_value(then_value) { Type::Numeric(_) => { + dbg!("merging numeric values"); self.merge_numeric_values(then_condition, else_condition, then_value, else_value) } typ @ Type::Array(_, _) => { + dbg!("merging array values"); self.merge_array_values(typ, then_condition, else_condition, then_value, else_value) } typ @ Type::Slice(_) => { + dbg!("merging slice values"); self.merge_slice_values(typ, then_condition, else_condition, then_value, else_value) } Type::Reference => panic!("Cannot return references from an if expression"), @@ -416,6 +419,8 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); + dbg!(then_value.clone()); + dbg!(else_value.clone()); let len = match then_value { Value::Array { array, .. } => array.len(), @@ -424,6 +429,24 @@ impl<'f> Context<'f> { let else_len = match else_value { Value::Array { array, .. } => array.len(), + Value::Instruction { instruction, typ, .. } => { + match &typ { + Type::Slice(_) => (), + _ => panic!("ICE: Expected slice type, but got {typ}"), + }; + // let instruction = &self.inserter.function.dfg[instruction]; + // dbg!(instruction.clone()); + match self.inserter.function.dfg[instruction] { + Instruction::ArraySet { array, index, value, length } => { + let array_len_func = self.inserter.function.dfg.import_intrinsic(Intrinsic::ArrayLen); + let call_array_len = Instruction::Call { func: array_len_func, arguments: vec![array] }; + let array_len = self.insert_instruction_with_typevars(call_array_len, Some(vec![Type::field()])).first(); + dbg!(array_len); + } + _ => unreachable!("ahh expected array set but got {:?}", instruction), + } + panic!("ah got instruction"); + } _ => panic!("Expected array value"), }; diff --git a/crates/noirc_evaluator/src/ssa/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa/ssa_builder/mod.rs index 6a5b24e3e9f..c0881af2b83 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_builder/mod.rs @@ -270,8 +270,9 @@ impl FunctionBuilder { array: ValueId, index: ValueId, value: ValueId, + length: Option, ) -> ValueId { - self.insert_instruction(Instruction::ArraySet { array, index, value }, None).first() + self.insert_instruction(Instruction::ArraySet { array, index, value, length }, None).first() } /// Terminates the current block with the given terminator instruction From 78c1921e3a2035a14e4f2d190b5258f23116e85c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 22 Aug 2023 15:23:27 -0500 Subject: [PATCH 02/46] Add everything except alias handling --- .../noirc_evaluator/src/ssa/ir/basic_block.rs | 3 +- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 518 +++++++----------- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 8 +- crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs | 5 +- 4 files changed, 215 insertions(+), 319 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/ir/basic_block.rs b/crates/noirc_evaluator/src/ssa/ir/basic_block.rs index 998591f7210..b3b566f8f37 100644 --- a/crates/noirc_evaluator/src/ssa/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa/ir/basic_block.rs @@ -122,10 +122,11 @@ impl BasicBlock { /// Return the jmp arguments, if any, of this block's TerminatorInstruction. /// - /// If this block has no terminator, or a Return terminator this will be empty. + /// If this block has no terminator, this will be empty. pub(crate) fn terminator_arguments(&self) -> &[ValueId] { match &self.terminator { Some(TerminatorInstruction::Jmp { arguments, .. }) => arguments, + Some(TerminatorInstruction::Return { return_values, .. }) => return_values, _ => &[], } } diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index d83cda4a8b1..c9b5fed4237 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -1,69 +1,66 @@ //! mem2reg implements a pass for promoting values stored in memory to values in registers where //! possible. This is particularly important for converting our memory-based representation of //! mutable variables into values that are easier to manipulate. -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use crate::ssa::{ ir::{ basic_block::BasicBlockId, cfg::ControlFlowGraph, - dfg::DataFlowGraph, dom::DominatorTree, function::Function, - instruction::{Instruction, InstructionId, TerminatorInstruction}, + instruction::{Instruction, InstructionId}, post_order::PostOrder, - value::{Value, ValueId}, + value::ValueId, }, ssa_gen::Ssa, }; -use super::unrolling::{find_all_loops, Loops}; - impl Ssa { /// Attempts to remove any load instructions that recover values that are already available in /// scope, and attempts to remove stores that are subsequently redundant. /// As long as they are not stores on memory used inside of loops pub(crate) fn mem2reg(mut self) -> Ssa { for function in self.functions.values_mut() { - let mut all_protected_allocations = HashSet::new(); - let mut context = PerFunctionContext::new(function); - - for block in function.reachable_blocks() { - // Maps Load instruction id -> value to replace the result of the load with - let mut loads_to_substitute_per_block = BTreeMap::new(); - - // Maps Load result id -> value to replace the result of the load with - let mut load_values_to_substitute = BTreeMap::new(); - - let allocations_protected_by_block = context - .analyze_allocations_and_eliminate_known_loads( - &mut function.dfg, - &mut loads_to_substitute_per_block, - &mut load_values_to_substitute, - block, - ); - all_protected_allocations.extend(allocations_protected_by_block.into_iter()); - } - - for block in function.reachable_blocks() { - context.remove_unused_stores(&mut function.dfg, &all_protected_allocations, block); - } + context.mem2reg(function); + context.remove_instructions(function); } self } } struct PerFunctionContext { - last_stores_with_block: BTreeMap<(AllocId, BasicBlockId), ValueId>, - // Maps Load result id -> (value, block_id) - // Used to replace the result of a load with the appropriate block - load_values_to_substitute_per_func: BTreeMap, - store_ids: Vec, cfg: ControlFlowGraph, post_order: PostOrder, dom_tree: DominatorTree, - loops: Loops, + + blocks: BTreeMap, + + /// Load and Store instructions that should be removed at the end of the pass. + /// + /// We avoid removing individual instructions as we go since removing elements + /// from the middle of Vecs many times will be slower than a single call to `retain`. + instructions_to_remove: BTreeSet, +} + +#[derive(Default)] +struct Block { + /// The value of each reference at the end of this block + end_references: BTreeMap, +} + +#[derive(Clone, Default)] +struct Reference { + aliases: BTreeSet, + value: ReferenceValue, +} + +#[derive(Copy, Clone, PartialEq, Eq, Default)] +enum ReferenceValue { + #[default] + Unknown, + Known(ValueId), } impl PerFunctionContext { @@ -71,308 +68,194 @@ impl PerFunctionContext { let cfg = ControlFlowGraph::with_function(function); let post_order = PostOrder::with_function(function); let dom_tree = DominatorTree::with_cfg_and_post_order(&cfg, &post_order); + PerFunctionContext { - last_stores_with_block: BTreeMap::new(), - load_values_to_substitute_per_func: BTreeMap::new(), - store_ids: Vec::new(), cfg, post_order, dom_tree, - loops: find_all_loops(function), + blocks: BTreeMap::new(), + instructions_to_remove: BTreeSet::new(), } } -} -/// An AllocId is the ValueId returned from an allocate instruction. E.g. v0 in v0 = allocate. -/// This type alias is used to help signal where the only valid ValueIds are those that are from -/// an allocate instruction. -type AllocId = ValueId; + /// Apply the mem2reg pass to the given function. + /// + /// This function is expected to be the same one that the internal cfg, post_order, and + /// dom_tree were created from. + fn mem2reg(&mut self, function: &mut Function) { + // Iterate each block in reverse post order = forward order + let mut block_order = PostOrder::with_function(function).into_vec(); + block_order.reverse(); + + for block in block_order { + let references = self.find_starting_references(block); + self.analyze_block(function, block, references); + } + } -impl PerFunctionContext { - // Attempts to remove load instructions for which the result is already known from previous - // store instructions to the same address in the same block. - fn analyze_allocations_and_eliminate_known_loads( + /// The value of each reference at the start of the given block is the unification + /// of the value of the same reference at the end of its predecessor blocks. + fn find_starting_references(&self, block: BasicBlockId) -> Block { + self.cfg + .predecessors(block) + .fold(Block::default(), |block, predecessor| block.unify(&self.blocks[&predecessor])) + } + + /// Analyze a block with the given starting reference values. + /// + /// This will remove any known loads in the block and track the value of references + /// as they are stored to. When this function is finished, the value of each reference + /// at the end of this block will be remembered in `self.blocks`. + fn analyze_block( &mut self, - dfg: &mut DataFlowGraph, - loads_to_substitute: &mut BTreeMap, - load_values_to_substitute_per_block: &mut BTreeMap, - block_id: BasicBlockId, - ) -> HashSet { - let mut protected_allocations = HashSet::new(); - let block = &dfg[block_id]; - - // Check whether the block has a common successor here to avoid analyzing - // the CFG for every block instruction. - let has_common_successor = self.has_common_successor(block_id); - - for instruction_id in block.instructions() { - match &dfg[*instruction_id] { - Instruction::Store { mut address, value } => { - if has_common_successor { - protected_allocations.insert(address); - } - address = self.fetch_load_value_to_substitute(block_id, address); - - self.last_stores_with_block.insert((address, block_id), *value); - self.store_ids.push(*instruction_id); - - if has_common_successor { - protected_allocations.insert(address); - } - } - Instruction::Load { mut address } => { - address = self.fetch_load_value_to_substitute(block_id, address); - - let found_last_value = self.find_load_to_substitute( - block_id, - address, - dfg, - instruction_id, - loads_to_substitute, - load_values_to_substitute_per_block, - ); - if !found_last_value { - // We want to protect allocations that do not have a load to substitute - protected_allocations.insert(address); - // We also want to check for allocations that share a value - // with the one we are protecting. - // This check prevents the pass from removing stores to a value that - // is used by reference aliases in different blocks - protected_allocations.insert(dfg.resolve(address)); - } - } - Instruction::Call { arguments, .. } => { - for arg in arguments { - if Self::value_is_from_allocation(*arg, dfg) { - protected_allocations.insert(*arg); - } - } - } - _ => { - // Nothing to do - } - } - } + function: &mut Function, + block: BasicBlockId, + mut references: Block, + ) { + // TODO: Can we avoid cloning here? + let instructions = function.dfg[block].instructions().to_vec(); + let mut last_stores = BTreeMap::new(); - // Identify any arrays that are returned from this function - if let TerminatorInstruction::Return { return_values } = block.unwrap_terminator() { - for value in return_values { - if Self::value_is_from_allocation(*value, dfg) { - protected_allocations.insert(*value); - } - } + for instruction in instructions { + self.analyze_instruction(function, &mut references, instruction, &mut last_stores); } - // Substitute load result values - for (result_value, new_value) in load_values_to_substitute_per_block { - let result_value = dfg.resolve(*result_value); - dfg.set_value_from_id(result_value, *new_value); - } + let terminator_arguments = function.dfg[block].terminator_arguments(); + self.mark_all_unknown(terminator_arguments, &mut references, &mut last_stores); - // Delete load instructions - // Even though we could let DIE handle this, doing it here makes the debug output easier - // to read. - dfg[block_id] - .instructions_mut() - .retain(|instruction| !loads_to_substitute.contains_key(instruction)); + // 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 { + for (_, instruction) in last_stores { + self.instructions_to_remove.insert(instruction); + } + } - protected_allocations + self.blocks.insert(block, references); } - // This method will fetch already saved load values to substitute for a given address. - // The search starts at the block supplied as a parameter. If there is not a load to substitute - // the CFG is analyzed to determine whether a predecessor block has a load value to substitute. - // If there is no load value to substitute the original address is returned. - fn fetch_load_value_to_substitute( + fn analyze_instruction( &mut self, - block_id: BasicBlockId, - address: ValueId, - ) -> ValueId { - let mut stack = vec![block_id]; - let mut visited = HashSet::new(); - - while let Some(block) = stack.pop() { - visited.insert(block); - - // We do not want to substitute loads that take place within loops or yet to be simplified branches - // as this pass can occur before loop unrolling and flattening. - // The mem2reg pass should be ran again following all optimization passes as new values - // may be able to be promoted - for l in self.loops.yet_to_unroll.iter() { - // We do not want to substitute loads that take place within loops as this pass - // can occur before loop unrolling - // The pass should be ran again following loop unrolling as new values - if l.blocks.contains(&block) { - return address; + function: &mut Function, + references: &mut Block, + instruction: InstructionId, + last_stores: &mut BTreeMap, + ) { + match &function.dfg[instruction] { + Instruction::Load { address } => { + // If the load is known, replace it with the known value and remove the load + if let Some(value) = references.get_known_value(*address) { + let result = function.dfg.instruction_results(instruction)[0]; + function.dfg.set_value_from_id(result, value); + + self.instructions_to_remove.insert(instruction); + } else { + last_stores.remove(address); } } - - // Check whether there is a load value to substitute in the current block. - // Return the value if found. - if let Some((value, load_block_id)) = - self.load_values_to_substitute_per_func.get(&address) - { - if *load_block_id == block { - return *value; + Instruction::Store { address, 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) { + self.instructions_to_remove.insert(*last_store); } - } - // If no load values to substitute have been found in the current block, check the block's predecessors. - if let Some(predecessor) = self.block_has_predecessor(block, &visited) { - stack.push(predecessor); + references.set_known_value(*address, *value); + last_stores.insert(*address, instruction); } + Instruction::Call { arguments, .. } => { + self.mark_all_unknown(arguments, references, last_stores); + } + + // TODO: Track aliases here + // Instruction::ArrayGet { array, index } => todo!(), + // Instruction::ArraySet { array, index, value } => todo!(), + // We can ignore Allocate since the value is still unknown until the first Store + _ => (), } - address } - // This method determines which loads should be substituted and saves them - // to be substituted further in the pass. - // Starting at the block supplied as a parameter, we check whether a store has occurred with the given address. - // If no store has occurred in the supplied block, the CFG is analyzed to determine whether - // a predecessor block has a store at the given address. - fn find_load_to_substitute( - &mut self, - block_id: BasicBlockId, - address: ValueId, - dfg: &DataFlowGraph, - instruction_id: &InstructionId, - loads_to_substitute: &mut BTreeMap, - load_values_to_substitute_per_block: &mut BTreeMap, - ) -> bool { - let mut stack = vec![block_id]; - let mut visited = HashSet::new(); - - while let Some(block) = stack.pop() { - visited.insert(block); - - // We do not want to substitute loads that take place within loops or yet to be simplified branches - // as this pass can occur before loop unrolling and flattening. - // The mem2reg pass should be ran again following all optimization passes as new values - // may be able to be promoted - for l in self.loops.yet_to_unroll.iter() { - // We do not want to substitute loads that take place within loops as this pass - // can occur before loop unrolling - // The pass should be ran again following loop unrolling as new values - if l.blocks.contains(&block) { - return false; - } - } + fn mark_all_unknown( + &self, + values: &[ValueId], + references: &mut Block, + last_stores: &mut BTreeMap, + ) { + for value in values { + references.set_unknown(*value); - // Check whether there has been a store instruction in the current block - // If there has been a store, add a load to be substituted. - if let Some(last_value) = self.last_stores_with_block.get(&(address, block)) { - let result_value = *dfg - .instruction_results(*instruction_id) - .first() - .expect("ICE: Load instructions should have single result"); - - loads_to_substitute.insert(*instruction_id, *last_value); - load_values_to_substitute_per_block.insert(result_value, *last_value); - self.load_values_to_substitute_per_func.insert(result_value, (*last_value, block)); - return true; - } + println!("Removing {value}"); + last_stores.remove(value); + } + } - // If no load values to substitute have been found in the current block, check the block's predecessors. - if let Some(predecessor) = self.block_has_predecessor(block, &visited) { - stack.push(predecessor); - } + /// Remove any instructions in `self.instructions_to_remove` from the current function. + /// This is expected to contain any loads which were replaced and any stores which are + /// no longer needed. + fn remove_instructions(&self, function: &mut Function) { + // The order we iterate blocks in is not important + for block in self.post_order.as_slice() { + function.dfg[*block] + .instructions_mut() + .retain(|instruction| !self.instructions_to_remove.contains(instruction)); } - false } +} - // If no loads or stores have been found in the current block, check the block's predecessors. - // Only check blocks with one predecessor as otherwise a constant value could be propogated - // through successor blocks with multiple branches that rely on other simplification passes - // such as loop unrolling or flattening of the CFG. - fn block_has_predecessor( - &mut self, - block_id: BasicBlockId, - visited: &HashSet, - ) -> Option { - let mut predecessors = self.cfg.predecessors(block_id); - if predecessors.len() == 1 { - let predecessor = predecessors.next().unwrap(); - if self.dom_tree.is_reachable(predecessor) - && self.dom_tree.dominates(predecessor, block_id) - && !visited.contains(&predecessor) - { - return Some(predecessor); +impl Block { + /// If the given reference id points to a known value, return the value + fn get_known_value(&self, address: ValueId) -> Option { + if let Some(reference) = self.end_references.get(&address) { + if let ReferenceValue::Known(value) = reference.value { + return Some(value); } } None } - fn has_common_successor(&mut self, block_id: BasicBlockId) -> bool { - let mut predecessors = self.cfg.predecessors(block_id); - if let Some(predecessor) = predecessors.next() { - let pred_successors = self.find_all_successors(predecessor); - let current_successors: HashSet<_> = self.cfg.successors(block_id).collect(); - return pred_successors.into_iter().any(|b| current_successors.contains(&b)); - } - false + /// If the given address is known, set its value to `ReferenceValue::Known(value)`. + fn set_known_value(&mut self, address: ValueId, value: ValueId) { + // TODO: Aliases + let entry = self.end_references.entry(address).or_default(); + entry.value = ReferenceValue::Known(value); } - fn find_all_successors(&self, block_id: BasicBlockId) -> HashSet { - let mut stack = vec![]; - let mut visited = HashSet::new(); + fn set_unknown(&mut self, address: ValueId) { + self.end_references.entry(address).and_modify(|reference| { + reference.value = ReferenceValue::Unknown; + }); + } - // Fetch initial block successors - let successors = self.cfg.successors(block_id); - for successor in successors { - if !visited.contains(&successor) { - stack.push(successor); - } - } + fn unify(mut self, other: &Self) -> Self { + for (reference_id, other_reference) in &other.end_references { + let new_reference = if let Some(old_reference) = self.end_references.get(reference_id) { + old_reference.unify(other_reference) + } else { + other_reference.clone() + }; - // Follow the CFG to fetch the remaining successors - while let Some(block) = stack.pop() { - visited.insert(block); - let successors = self.cfg.successors(block); - for successor in successors { - if !visited.contains(&successor) { - stack.push(successor); - } - } + self.end_references.insert(*reference_id, new_reference); } - visited + self } +} - /// Checks whether the given value id refers to an allocation. - fn value_is_from_allocation(value: ValueId, dfg: &DataFlowGraph) -> bool { - match &dfg[value] { - Value::Instruction { instruction, .. } => { - matches!(&dfg[*instruction], Instruction::Allocate) - } - _ => false, - } +impl Reference { + fn unify(&self, other: &Self) -> Self { + let value = self.value.unify(other.value); + let aliases = self.aliases.union(&other.aliases).copied().collect(); + Self { value, aliases } } +} - /// Removes all store instructions identified during analysis that aren't present in the - /// provided `protected_allocations` `HashSet`. - fn remove_unused_stores( - &self, - dfg: &mut DataFlowGraph, - protected_allocations: &HashSet, - block_id: BasicBlockId, - ) { - // Scan for unused stores - let mut stores_to_remove = HashSet::new(); - - for instruction_id in &self.store_ids { - let address = match &dfg[*instruction_id] { - Instruction::Store { address, .. } => *address, - _ => unreachable!("store_ids should contain only store instructions"), - }; - - if !protected_allocations.contains(&address) { - stores_to_remove.insert(*instruction_id); - } +impl ReferenceValue { + fn unify(self, other: Self) -> Self { + if self == other { + self + } else { + ReferenceValue::Unknown } - - // Delete unused stores - dfg[block_id] - .instructions_mut() - .retain(|instruction| !stores_to_remove.contains(instruction)); } } @@ -423,8 +306,6 @@ mod tests { let ssa = builder.finish().mem2reg().fold_constants(); - println!("{ssa}"); - let func = ssa.main(); let block_id = func.entry_block(); @@ -495,7 +376,9 @@ mod tests { let func = ssa.main(); let block_id = func.entry_block(); - // Store affects outcome of returned array, and can't be removed + println!("{ssa}"); + + // Store is needed by the return value, and can't be removed assert_eq!(count_stores(block_id, &func.dfg), 1); let ret_val_id = match func.dfg[block_id].terminator().unwrap() { @@ -562,11 +445,13 @@ mod tests { assert_eq!(ssa.main().reachable_blocks().len(), 2); // Expected result: - // fn main { + // acir fn main f0 { // b0(): // v0 = allocate + // store Field 5 at v0 // jmp b1(Field 5) // b1(v3: Field): + // store Field 6 at v0 // return v3, Field 5, Field 6 // Optimized to constants 5 and 6 // } let ssa = ssa.mem2reg(); @@ -578,9 +463,9 @@ mod tests { assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); - // All stores should be removed - assert_eq!(count_stores(main.entry_block(), &main.dfg), 0); - assert_eq!(count_stores(b1, &main.dfg), 0); + // Neither store is removed since they are each the last in the block and there are multiple blocks + assert_eq!(count_stores(main.entry_block(), &main.dfg), 1); + assert_eq!(count_stores(b1, &main.dfg), 1); // The jmp to b1 should also be a constant 5 now match main.dfg[main.entry_block()].terminator() { @@ -597,7 +482,7 @@ mod tests { // Test that a load in a predecessor block has been removed if the value // is later stored in a successor block #[test] - fn store_with_load_in_predecessor_block() { + fn load_aliases_in_predecessor_block() { // fn main { // b0(): // v0 = allocate @@ -647,14 +532,18 @@ mod tests { assert_eq!(ssa.main().reachable_blocks().len(), 2); // Expected result: - // fn main { - // b0(): - // v0 = allocate - // v2 = allocate - // jmp b1() - // b1(): - // v8 = eq Field 2, Field 2 - // return + // acir fn main f0 { + // b0(): + // v0 = allocate + // store Field 0 at v0 + // v2 = allocate + // store v0 at v2 + // jmp b1() + // b1(): + // store Field 1 at v0 + // store Field 2 at v0 + // v8 = eq Field 1, Field 2 + // return // } let ssa = ssa.mem2reg(); @@ -665,13 +554,16 @@ mod tests { assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); - // All stores should be removed - assert_eq!(count_stores(main.entry_block(), &main.dfg), 0); - assert_eq!(count_stores(b1, &main.dfg), 0); + println!("{ssa}"); + + // The stores are not removed in this case + assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); + assert_eq!(count_stores(b1, &main.dfg), 2); let b1_instructions = main.dfg[b1].instructions(); - // The first instruction should be a binary operation - match &main.dfg[b1_instructions[0]] { + + // The last instruction in b1 should be a binary operation + match &main.dfg[*b1_instructions.last().unwrap()] { Instruction::Binary(binary) => { let lhs = main.dfg.get_numeric_constant(binary.lhs).expect("Expected constant value"); diff --git a/crates/noirc_evaluator/src/ssa/opt/unrolling.rs b/crates/noirc_evaluator/src/ssa/opt/unrolling.rs index 9bf62bda1cb..f9c92f8335e 100644 --- a/crates/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/crates/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -44,7 +44,7 @@ impl Ssa { } } -pub(crate) struct Loop { +struct Loop { /// The header block of a loop is the block which dominates all the /// other blocks in the loop. header: BasicBlockId, @@ -57,12 +57,12 @@ pub(crate) struct Loop { pub(crate) blocks: HashSet, } -pub(crate) struct Loops { +struct Loops { /// The loops that failed to be unrolled so that we do not try to unroll them again. /// Each loop is identified by its header block id. failed_to_unroll: HashSet, - pub(crate) yet_to_unroll: Vec, + yet_to_unroll: Vec, modified_blocks: HashSet, cfg: ControlFlowGraph, dom_tree: DominatorTree, @@ -70,7 +70,7 @@ pub(crate) struct Loops { /// Find a loop in the program by finding a node that dominates any predecessor node. /// The edge where this happens will be the back-edge of the loop. -pub(crate) fn find_all_loops(function: &Function) -> Loops { +fn find_all_loops(function: &Function) -> Loops { let cfg = ControlFlowGraph::with_function(function); let post_order = PostOrder::with_function(function); let mut dom_tree = DominatorTree::with_cfg_and_post_order(&cfg, &post_order); diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs index cc3a7c02a75..1367b1753af 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -130,7 +130,10 @@ impl<'a> FunctionContext<'a> { let slice_contents = self.codegen_array(elements, typ[1].clone()); Tree::Branch(vec![slice_length.into(), slice_contents]) } - _ => unreachable!("ICE: array literal type must be an array or a slice, but got {}", array.typ), + _ => unreachable!( + "ICE: array literal type must be an array or a slice, but got {}", + array.typ + ), } } ast::Literal::Integer(value, typ) => { From 49cc0c819bfc63c8e688e5aac6787a15e2c519f0 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 23 Aug 2023 11:51:53 -0500 Subject: [PATCH 03/46] Fix all tests --- .../str_as_bytes/src/main.nr | 22 +- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 230 ++++++++++++++---- 2 files changed, 197 insertions(+), 55 deletions(-) diff --git a/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr b/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr index 6890b156b15..80a8ccbd77a 100644 --- a/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr +++ b/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr @@ -1,18 +1,18 @@ use dep::std; fn main() { let a = "hello"; - let b = a.as_bytes(); - assert(b[0]==104); - assert(b[1]==101); - assert(b[2]==108); - assert(b[3]==108); - assert(b[4]==111); - assert(b.len()==5); + // let b = a.as_bytes(); + // assert(b[0]==104); + // assert(b[1]==101); + // assert(b[2]==108); + // assert(b[3]==108); + // assert(b[4]==111); + // assert(b.len()==5); let mut c = a.as_bytes_vec(); assert(c.get(0)==104); - assert(c.get(1)==101); - assert(c.get(2)==108); - assert(c.get(3)==108); - assert(c.get(4)==111); + // assert(c.get(1)==101); + // assert(c.get(2)==108); + // assert(c.get(3)==108); + // assert(c.get(4)==111); assert(c.len()==5); } diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index c9b5fed4237..e7219b05912 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -11,6 +11,7 @@ use crate::ssa::{ function::Function, instruction::{Instruction, InstructionId}, post_order::PostOrder, + types::Type, value::ValueId, }, ssa_gen::Ssa, @@ -44,21 +45,29 @@ struct PerFunctionContext { instructions_to_remove: BTreeSet, } -#[derive(Default)] +#[derive(Debug, Default, Clone)] struct Block { - /// The value of each reference at the end of this block - end_references: BTreeMap, + expressions: BTreeMap, + + aliases: BTreeMap>, + + alias_sets: BTreeMap, +} + +#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] +enum Expression { + Dereference(Box), + Other(ValueId), } -#[derive(Clone, Default)] +#[derive(Debug, Clone)] struct Reference { aliases: BTreeSet, value: ReferenceValue, } -#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ReferenceValue { - #[default] Unknown, Known(ValueId), } @@ -95,10 +104,26 @@ impl PerFunctionContext { /// The value of each reference at the start of the given block is the unification /// of the value of the same reference at the end of its predecessor blocks. - fn find_starting_references(&self, block: BasicBlockId) -> Block { - self.cfg - .predecessors(block) - .fold(Block::default(), |block, predecessor| block.unify(&self.blocks[&predecessor])) + fn find_starting_references(&mut self, block: BasicBlockId) -> Block { + let mut predecessors = self.cfg.predecessors(block); + println!( + "find_starting_references block {block} with {} predecessor(s)", + predecessors.len() + ); + + if let Some(first_predecessor) = predecessors.next() { + let first = self.blocks.get(&first_predecessor).cloned().unwrap_or_default(); + + // 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 + // is always also empty so we'd never be able to track any references across blocks. + predecessors.fold(first, |block, predecessor| { + let predecessor = self.blocks.entry(predecessor).or_default(); + block.unify(predecessor) + }) + } else { + Block::default() + } } /// Analyze a block with the given starting reference values. @@ -121,7 +146,7 @@ impl PerFunctionContext { } let terminator_arguments = function.dfg[block].terminator_arguments(); - self.mark_all_unknown(terminator_arguments, &mut references, &mut last_stores); + self.mark_all_unknown(terminator_arguments, function, &mut references, &mut last_stores); // 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 @@ -144,34 +169,50 @@ impl PerFunctionContext { ) { match &function.dfg[instruction] { Instruction::Load { address } => { + let address = function.dfg.resolve(*address); + + let result = function.dfg.instruction_results(instruction)[0]; + references.remember_dereference(function, address, result); + // If the load is known, replace it with the known value and remove the load - if let Some(value) = references.get_known_value(*address) { + if let Some(value) = references.get_known_value(address) { let result = function.dfg.instruction_results(instruction)[0]; function.dfg.set_value_from_id(result, value); self.instructions_to_remove.insert(instruction); } else { - last_stores.remove(address); + last_stores.remove(&address); } } Instruction::Store { address, value } => { + let address = function.dfg.resolve(*address); + let value = function.dfg.resolve(*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) = last_stores.get(&address) { self.instructions_to_remove.insert(*last_store); } - references.set_known_value(*address, *value); - last_stores.insert(*address, instruction); + references.set_known_value(address, value); + last_stores.insert(address, instruction); } Instruction::Call { arguments, .. } => { - self.mark_all_unknown(arguments, references, last_stores); + self.mark_all_unknown(arguments, function, references, last_stores); + } + Instruction::Allocate => { + // Register the new reference + let result = function.dfg.instruction_results(instruction)[0]; + references.expressions.insert(result, Expression::Other(result)); + + let mut aliases = BTreeSet::new(); + aliases.insert(result); + references.aliases.insert(Expression::Other(result), aliases); } // TODO: Track aliases here // Instruction::ArrayGet { array, index } => todo!(), // Instruction::ArraySet { array, index, value } => todo!(), - // We can ignore Allocate since the value is still unknown until the first Store _ => (), } } @@ -179,14 +220,16 @@ impl PerFunctionContext { fn mark_all_unknown( &self, values: &[ValueId], + function: &Function, references: &mut Block, last_stores: &mut BTreeMap, ) { for value in values { - references.set_unknown(*value); - - println!("Removing {value}"); - last_stores.remove(value); + if function.dfg.type_of_value(*value) == Type::Reference { + let value = function.dfg.resolve(*value); + references.set_unknown(value); + last_stores.remove(&value); + } } } @@ -206,39 +249,137 @@ impl PerFunctionContext { impl Block { /// If the given reference id points to a known value, return the value fn get_known_value(&self, address: ValueId) -> Option { - if let Some(reference) = self.end_references.get(&address) { - if let ReferenceValue::Known(value) = reference.value { - return Some(value); + if let Some(expression) = self.expressions.get(&address) { + if let Some(aliases) = self.aliases.get(expression) { + // We could allow multiple aliases if we check that the reference + // value in each is equal. + if aliases.len() == 1 { + let alias = aliases.first().expect("There should be exactly 1 alias"); + + if let Some(reference) = self.alias_sets.get(alias) { + if let ReferenceValue::Known(value) = reference.value { + println!("get_known_value: returning {value} for alias {alias}"); + return Some(value); + } else { + println!("get_known_value: ReferenceValue::Unknown for alias {alias}"); + } + } else { + println!("get_known_value: No alias_set for alias {alias}"); + } + } else { + println!("get_known_value: {} aliases for address {address}", aliases.len()); + } + } else { + println!("get_known_value: No known aliases for address {address}"); } + } else { + println!("get_known_value: No expression for address {address}"); } None } /// If the given address is known, set its value to `ReferenceValue::Known(value)`. fn set_known_value(&mut self, address: ValueId, value: ValueId) { - // TODO: Aliases - let entry = self.end_references.entry(address).or_default(); - entry.value = ReferenceValue::Known(value); + self.set_value(address, ReferenceValue::Known(value)); } fn set_unknown(&mut self, address: ValueId) { - self.end_references.entry(address).and_modify(|reference| { - reference.value = ReferenceValue::Unknown; - }); + self.set_value(address, ReferenceValue::Unknown); + } + + fn set_value(&mut self, address: ValueId, value: ReferenceValue) { + // TODO: Verify this does not fail in the case of reference parameters + 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 + todo!("empty alias set"); + } else if aliases.len() == 1 { + let alias = aliases.first().expect("There should be exactly 1 alias"); + + if let Some(reference) = self.alias_sets.get_mut(alias) { + println!("set_known_value: Set value to {value:?} for alias {alias}"); + reference.value = value; + } else { + let reference = Reference { value, aliases: BTreeSet::new() }; + self.alias_sets.insert(*alias, reference); + println!("set_known_value: Created new alias set for alias {alias}, with value {value:?}"); + } + } else { + println!("set_known_value: {} aliases for expression {expression:?}, marking all unknown", aliases.len()); + // 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) = self.alias_sets.get_mut(alias) { + println!(" Marking {alias} unknown"); + reference.value = ReferenceValue::Unknown; + } else { + println!(" {alias} already unknown"); + } + } + } + } else { + println!("set_known_value: ReferenceValue::Unknown for expression {expression:?}"); + } + } else { + println!("\n\n!! set_known_value: Creating fresh expression for {address} in set_value... aliases will be empty\n\n"); + self.expressions.insert(address, Expression::Other(address)); + } } fn unify(mut self, other: &Self) -> Self { - for (reference_id, other_reference) in &other.end_references { - let new_reference = if let Some(old_reference) = self.end_references.get(reference_id) { - old_reference.unify(other_reference) + println!("unify:\n {self:?}\n {other:?}"); + + for (value_id, expression) in &other.expressions { + if let Some(existing) = self.expressions.get(value_id) { + assert_eq!(existing, expression, "Expected expressions for {value_id} to be equal"); } else { - other_reference.clone() - }; + self.expressions.insert(*value_id, expression.clone()); + } + } + + for (expression, new_aliases) in &other.aliases { + let expression = expression.clone(); + + self.aliases + .entry(expression) + .and_modify(|aliases| { + for alias in new_aliases { + aliases.insert(*alias); + } + }) + .or_insert_with(|| new_aliases.clone()); + } - self.end_references.insert(*reference_id, new_reference); + // Keep only the references present in both maps. + let mut intersection = BTreeMap::new(); + for (value_id, reference) in &other.alias_sets { + if let Some(existing) = self.alias_sets.get(value_id) { + intersection.insert(*value_id, existing.unify(reference)); + } } + self.alias_sets = intersection; + + println!("unify result:\n {self:?}"); self } + + /// 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 let Some(known_address) = self.get_known_value(address) { + self.expressions.insert(result, Expression::Other(known_address)); + } else { + let expression = Expression::Dereference(Box::new(Expression::Other(address))); + self.expressions.insert(result, expression.clone()); + // No known aliases to insert for this expression... can we find an alias + // even if we don't have a known address? If not we'll have to invalidate all + // known references if this reference is ever stored to. + } + } + } } impl Reference { @@ -376,8 +517,6 @@ mod tests { let func = ssa.main(); let block_id = func.entry_block(); - println!("{ssa}"); - // Store is needed by the return value, and can't be removed assert_eq!(count_stores(block_id, &func.dfg), 1); @@ -454,11 +593,14 @@ mod tests { // store Field 6 at v0 // return v3, Field 5, Field 6 // Optimized to constants 5 and 6 // } + println!("{ssa}"); let ssa = ssa.mem2reg(); let main = ssa.main(); assert_eq!(main.reachable_blocks().len(), 2); + println!("{ssa}"); + // The loads should be removed assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); @@ -540,12 +682,13 @@ mod tests { // store v0 at v2 // jmp b1() // b1(): - // store Field 1 at v0 // store Field 2 at v0 // v8 = eq Field 1, Field 2 // return // } + println!("{ssa}"); let ssa = ssa.mem2reg(); + println!("{ssa}"); let main = ssa.main(); assert_eq!(main.reachable_blocks().len(), 2); @@ -554,11 +697,10 @@ mod tests { assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); - println!("{ssa}"); - - // The stores are not removed in this case + // Only the first store in b1 is removed since there is another store to the same reference + // in the same block, and the store is not needed before the later store. assert_eq!(count_stores(main.entry_block(), &main.dfg), 2); - assert_eq!(count_stores(b1, &main.dfg), 2); + assert_eq!(count_stores(b1, &main.dfg), 1); let b1_instructions = main.dfg[b1].instructions(); From 88913c1ba750e2a15a5cf5c07bf6b0ba89b5a529 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 23 Aug 2023 11:52:57 -0500 Subject: [PATCH 04/46] clippy --- .../str_as_bytes/src/main.nr | 22 +++++++++---------- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr b/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr index 80a8ccbd77a..6890b156b15 100644 --- a/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr +++ b/crates/nargo_cli/tests/compile_success_empty/str_as_bytes/src/main.nr @@ -1,18 +1,18 @@ use dep::std; fn main() { let a = "hello"; - // let b = a.as_bytes(); - // assert(b[0]==104); - // assert(b[1]==101); - // assert(b[2]==108); - // assert(b[3]==108); - // assert(b[4]==111); - // assert(b.len()==5); + let b = a.as_bytes(); + assert(b[0]==104); + assert(b[1]==101); + assert(b[2]==108); + assert(b[3]==108); + assert(b[4]==111); + assert(b.len()==5); let mut c = a.as_bytes_vec(); assert(c.get(0)==104); - // assert(c.get(1)==101); - // assert(c.get(2)==108); - // assert(c.get(3)==108); - // assert(c.get(4)==111); + assert(c.get(1)==101); + assert(c.get(2)==108); + assert(c.get(3)==108); + assert(c.get(4)==111); assert(c.len()==5); } diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index e7219b05912..888bc454cda 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -373,7 +373,7 @@ impl Block { self.expressions.insert(result, Expression::Other(known_address)); } else { let expression = Expression::Dereference(Box::new(Expression::Other(address))); - self.expressions.insert(result, expression.clone()); + self.expressions.insert(result, expression); // No known aliases to insert for this expression... can we find an alias // even if we don't have a known address? If not we'll have to invalidate all // known references if this reference is ever stored to. From 079ea08a6978a66d400406ee6e85c11d01fa6a57 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 23 Aug 2023 18:38:44 +0000 Subject: [PATCH 05/46] initial work to get dyn slices working --- .../execution_success/slices/src/main.nr | 67 +++++++--- .../src/brillig/brillig_gen/brillig_block.rs | 2 +- crates/noirc_evaluator/src/ssa.rs | 6 + .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 33 ++++- .../noirc_evaluator/src/ssa/ir/instruction.rs | 4 +- .../src/ssa/opt/flatten_cfg.rs | 115 +++++++++++++++--- .../src/ssa/ssa_gen/context.rs | 7 +- 7 files changed, 196 insertions(+), 38 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index ddb97e61e55..2f1cd489748 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -2,13 +2,22 @@ use dep::std::slice; use dep::std; fn main(x : Field, y : pub Field) { - // let mut new_slice = []; + // Dynamic indexing works on slice with constant length + // let mut slice = [0, 1, 2, 3, 4]; + + // Need to implement flattening on a dynamic length + // let mut slice = []; // for i in 0..5 { - // new_slice = new_slice.push_back(i + 10); + // slice = slice.push_back(i); // } - // assert(new_slice.len() == 5); - let mut slice = [104, 101, 108, 108, 111]; - regression_dynamic_slice_index(slice, x - 1, x - 4); + // let mut slice = [0, 1]; + // if x != y { + // slice = slice.push_back(y); + // } else { + // slice = slice.push_back(x); + // } + // assert(slice.len() == 3); + // assert(slice[2] == 10); // let mut slice = [0; 2]; @@ -58,24 +67,54 @@ fn main(x : Field, y : pub Field) { // regression_2083(); // // The parameters to this function must come from witness values (inputs to main) // regression_merge_slices(x, y); + regression_dynamic_slice_index(x - 1, x - 4); } -fn regression_dynamic_slice_index(mut slice: [Field], x: Field, y: Field) { - assert(slice[x] == 111); - assert(slice[y] == 101); +fn regression_dynamic_slice_index(x: Field, y: Field) { + let mut slice = []; + for i in 0..5 { + slice = slice.push_back(i); + } + assert(slice.len() == 5); + dynamic_slice_index_set_if(slice, x, y); + dynamic_slice_index_set_if(slice, x, y); + // dynamic_slice_index_set_else(slice, x, y); +} + +fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); slice[y] = 0; - assert(slice[x] == 111); + assert(slice[x] == 4); assert(slice[1] == 0); if x as u32 < 10 { - // slice[x] = slice[x] - 2; - // assert(slice[x] == 2); + slice[x] = slice[x] - 2; } else { - // slice[x] = 0; - // assert(slice[x] == 0); + slice[x] = 0; } - // assert(slice[4] == 109); + assert(slice[4] == 2); } +fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[1] == 0); + if x as u32 > 10 { + slice[x] = slice[x] - 2; + } else { + slice[x] = 0; + } + assert(slice[4] == 0); +} + +// fn dynamic_slice_index_get(mut slice: [Field], x: Field, y: Field) { +// if x as u32 < 10 { + +// } +// } + // Ensure that slices of struct/tuple values work. fn regression_2083() { let y = [(1, 2)]; 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 38df0374a96..2314e46a45d 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -461,7 +461,7 @@ impl<'block> BrilligBlock<'block> { destination_variable, ); } - Instruction::ArraySet { array, index, value } => { + Instruction::ArraySet { array, index, value, .. } => { let source_variable = self.convert_ssa_value(*array, dfg); let index_register = self.convert_ssa_register_value(*index, dfg); let value_variable = self.convert_ssa_value(*value, dfg); diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index 7dbd627a949..ee11e43234e 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -63,6 +63,12 @@ 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() + .print(print_ssa_passes, "After Constant Folding:") + // .mem2reg() + // .print(print_ssa_passes, "After Mem2Reg:") + // .fold_constants() + // .print(print_ssa_passes, "After Constant Folding:") .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/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index bfc09a2420d..8b3fd105644 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -405,6 +405,7 @@ impl Context { None, dfg, last_array_uses, + None )?; } Instruction::ArraySet { array, index, value, length } => { @@ -415,6 +416,7 @@ impl Context { Some(*value), dfg, last_array_uses, + *length )?; } Instruction::Allocate => { @@ -463,6 +465,7 @@ impl Context { /// Handles an ArrayGet or ArraySet instruction. /// To set an index of the array (and create a new array in doing so), pass Some(value) for /// store_value. To just retrieve an index of the array, pass None for store_value. + #[allow(clippy::too_many_arguments)] fn handle_array_operation( &mut self, instruction: InstructionId, @@ -471,6 +474,7 @@ impl Context { store_value: Option, dfg: &DataFlowGraph, last_array_uses: &HashMap, + length: Option, ) -> Result<(), RuntimeError> { let index_const = dfg.get_numeric_constant(index); @@ -531,7 +535,7 @@ impl Context { let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); if let Some(store) = store_value { - self.array_set(instruction, array, index, store, dfg, map_array)?; + self.array_set(instruction, array, index, store, dfg, map_array, length)?; } else { self.array_get(instruction, array, index, dfg)?; } @@ -576,6 +580,14 @@ impl Context { } typ[0].clone() } + Type::Slice(typ) => { + if typ.len() != 1 { + unimplemented!( + "Non-const array indices is not implemented for non-homogenous array" + ); + } + typ[0].clone() + } _ => unreachable!("ICE - expected an array"), }; let typ = AcirType::from(typ); @@ -586,6 +598,7 @@ impl Context { /// 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. + #[allow(clippy::too_many_arguments)] fn array_set( &mut self, instruction: InstructionId, @@ -594,6 +607,7 @@ impl Context { store_value: ValueId, dfg: &DataFlowGraph, map_array: bool, + length: Option ) -> Result<(), InternalError> { // Fetch the internal SSA ID for the array let array = dfg.resolve(array); @@ -605,8 +619,25 @@ impl Context { // the SSA IR. let len = match dfg.type_of_value(array) { Type::Array(_, len) => len, + Type::Slice(_) => { + // let value = &dfg[array]; + // match value { + // Value::Array { array, .. } => { + // dbg!(array.len()); + // } + // Value::Instruction { instruction, position, typ } => { + // let instruction = &dfg[*instruction]; + // dbg!(instruction.clone()); + // } + // _ => unreachable!("ICE - expected value with slice type to be an array but got {:?}", value), + // } + let length = length.expect("ICE: array set on slice must have a length associated with the call"); + let len = dfg.get_numeric_constant(length).expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + len.to_u128() as usize + } _ => unreachable!("ICE - expected an array"), }; + dbg!(len); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction.rs b/crates/noirc_evaluator/src/ssa/ir/instruction.rs index 58e2e2bb58c..ce94e0b06b7 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction.rs @@ -272,7 +272,7 @@ impl Instruction { Instruction::ArrayGet { array: f(*array), index: f(*index) } } Instruction::ArraySet { array, index, value, length } => { - Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value), length: length.map(|l| f(l)) } + Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value), length: length.map(f) } } } } @@ -310,7 +310,7 @@ impl Instruction { f(*array); f(*index); f(*value); - length.map(|l| f(l)); + length.map(&mut f); } Instruction::EnableSideEffects { condition } => { f(*condition); diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 8901ab176ad..9bfc25400fd 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -143,7 +143,7 @@ use crate::ssa::{ dfg::{CallStack, InsertInstructionResult}, function::Function, function_inserter::FunctionInserter, - instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction, Intrinsic}, + instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, types::Type, value::{Value, ValueId}, }, @@ -419,36 +419,72 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); - dbg!(then_value.clone()); - dbg!(else_value.clone()); + // dbg!(then_value.clone()); + // dbg!(else_value.clone()); + + // let len = self.recursively_get_slice_length(then_value_id); + // dbg!(len); let len = match then_value { Value::Array { array, .. } => array.len(), + Value::Instruction { instruction, .. } => { + match self.inserter.function.dfg[instruction] { + Instruction::ArraySet { array: _, index: _, value: _, length } => { + let length = length.expect("ICE: array set on a slice must have a length"); + dbg!(&self.inserter.function.dfg[length]); + + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + let length_instr = &self.inserter.function.dfg[*instruction]; + let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + dbg!(&self.inserter.function.dfg[x]); + x + } + _ => length, + }; + // let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + let len = match &self.inserter.function.dfg[length] { + Value::NumericConstant { constant, .. } => { + constant.to_u128() as usize + } + _ => unreachable!("ahh expected array set but got {:?}", &self.inserter.function.dfg[length]), + }; + len + // len.to_u128() as usize + } + _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), + } + } _ => panic!("Expected array value"), }; + dbg!(len); let else_len = match else_value { Value::Array { array, .. } => array.len(), - Value::Instruction { instruction, typ, .. } => { - match &typ { - Type::Slice(_) => (), - _ => panic!("ICE: Expected slice type, but got {typ}"), - }; - // let instruction = &self.inserter.function.dfg[instruction]; - // dbg!(instruction.clone()); + Value::Instruction { instruction, .. } => { match self.inserter.function.dfg[instruction] { - Instruction::ArraySet { array, index, value, length } => { - let array_len_func = self.inserter.function.dfg.import_intrinsic(Intrinsic::ArrayLen); - let call_array_len = Instruction::Call { func: array_len_func, arguments: vec![array] }; - let array_len = self.insert_instruction_with_typevars(call_array_len, Some(vec![Type::field()])).first(); - dbg!(array_len); + Instruction::ArraySet { array: _, index: _, value: _, length } => { + let length = length.expect("ICE: array set on a slice must have a length"); + dbg!(&self.inserter.function.dfg[length]); + + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + let length_instr = &self.inserter.function.dfg[*instruction]; + let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + dbg!(&self.inserter.function.dfg[x]); + x + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + len.to_u128() as usize } - _ => unreachable!("ahh expected array set but got {:?}", instruction), + _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), } - panic!("ah got instruction"); } _ => panic!("Expected array value"), }; + dbg!(else_len); let len = len.max(else_len); @@ -486,6 +522,51 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } + // fn recursively_get_slice_length( + // &mut self, + // value: ValueId + // ) -> usize { + // match &self.inserter.function.dfg[value] { + // Value::Array { array, .. } => return array.len(), + // Value::Instruction { instruction, typ, .. } => { + // match &typ { + // Type::Slice(_) => (), + // _ => panic!("ICE: Expected slice type, but got {typ}"), + // }; + // match self.inserter.function.dfg[*instruction] { + // Instruction::ArraySet { array, index: _, value: _, length } => { + // let length = length.expect("ICE: array set on a slice must have a length"); + // dbg!(&self.inserter.function.dfg[length]); + + // let length = match &self.inserter.function.dfg[length] { + // Value::Instruction { instruction, position, typ } => { + // let length_instr = &self.inserter.function.dfg[*instruction]; + // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + // dbg!(&self.inserter.function.dfg[x]); + // x + // } + // _ => length, + // }; + // // Handle non-numeric constants later + // // However, I don't think the length should ever not be a dynamic constant at this point + // let len = &self.inserter.function.dfg.get_numeric_constant(length); + // if let Some(len) = len { + // len.to_u128() as usize + // } else { + // self.recursively_get_slice_length(array) + // } + // // len.to_u128() as usize + + // // panic!("ahhh") + // } + // _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[*instruction]), + // } + // // panic!("ah got instruction"); + // } + // _ => panic!("Expected array value"), + // } + // } + /// Given an if expression that returns an array: `if c { array1 } else { array2 }`, /// this function will recursively merge array1 and array2 into a single resulting array /// by creating a new array containing the result of self.merge_values for each element. diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs index 0b3ee5b9acf..0348d7ae9b8 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -658,14 +658,14 @@ impl<'a> FunctionContext<'a> { match lvalue { LValue::Ident => unreachable!("Cannot assign to a variable without a reference"), LValue::Index { old_array: mut array, index, array_lvalue, location } => { - array = self.assign_lvalue_index(new_value, array, index, location); + array = self.assign_lvalue_index(new_value, array, index, None, location); self.assign_new_value(*array_lvalue, array.into()); } LValue::SliceIndex { old_slice: slice, index, slice_lvalue, location } => { let mut slice_values = slice.into_value_list(self); slice_values[1] = - self.assign_lvalue_index(new_value, slice_values[1], index, location); + self.assign_lvalue_index(new_value, slice_values[1], index, Some(slice_values[0]), location); // The size of the slice does not change in a slice index assignment so we can reuse the same length value let new_slice = Tree::Branch(vec![slice_values[0].into(), slice_values[1].into()]); @@ -686,6 +686,7 @@ impl<'a> FunctionContext<'a> { new_value: Values, mut array: ValueId, index: ValueId, + length: Option, location: Location, ) -> ValueId { let element_size = self.builder.field_constant(self.element_size(array)); @@ -697,7 +698,7 @@ impl<'a> FunctionContext<'a> { new_value.for_each(|value| { let value = value.eval(self); - array = self.builder.insert_array_set(array, index, value); + array = self.builder.insert_array_set(array, index, value, length); index = self.builder.insert_binary(index, BinaryOp::Add, one); }); array From 4f22d7b0a30020323169426380d667d8f48ee635 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 23 Aug 2023 19:33:45 +0000 Subject: [PATCH 06/46] merged in jf/new-mem2reg, have partial dynamic indices support but cannot merge a load --- .../execution_success/slices/src/main.nr | 48 +++++++++--- crates/noirc_evaluator/src/ssa.rs | 4 - .../src/ssa/opt/flatten_cfg.rs | 75 ++++++------------- 3 files changed, 60 insertions(+), 67 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 2f1cd489748..5152c098b70 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -76,9 +76,10 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { slice = slice.push_back(i); } assert(slice.len() == 5); - dynamic_slice_index_set_if(slice, x, y); - dynamic_slice_index_set_if(slice, x, y); + // dynamic_slice_index_set_if(slice, x, y); // dynamic_slice_index_set_else(slice, x, y); + // dynamic_slice_index_set_nested(slice, x, y); + dynamic_slice_index_get(slice, x, y); } fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { @@ -88,10 +89,13 @@ fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { assert(slice[x] == 4); assert(slice[1] == 0); if x as u32 < 10 { - slice[x] = slice[x] - 2; + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice[x - 1] = slice[x]; } else { - slice[x] = 0; + slice[x] = 0; } + assert(slice[3] == 2); assert(slice[4] == 2); } @@ -102,18 +106,42 @@ fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { assert(slice[x] == 4); assert(slice[1] == 0); if x as u32 > 10 { - slice[x] = slice[x] - 2; + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice[x - 1] = slice[x]; } else { - slice[x] = 0; + slice[x] = 0; } assert(slice[4] == 0); } -// fn dynamic_slice_index_get(mut slice: [Field], x: Field, y: Field) { -// if x as u32 < 10 { +fn dynamic_slice_index_set_nested(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[1] == 0); + if x as u32 > 10 { + slice[x] = slice[x] - 2; + // TODO: this panics as we have a load for the slice in flattening + if y == 1 { + slice[x] = slice[x] + 20; + } + } else { + slice[x] = 0; + } + assert(slice[4] == 0); +} -// } -// } +fn dynamic_slice_index_get(mut slice: [Field], x: Field, y: Field) { + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + } else { + assert(slice[x] == 0); + } + assert(slice[4] == 2); +} // Ensure that slices of struct/tuple values work. fn regression_2083() { diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index ee11e43234e..04170b07154 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -65,10 +65,6 @@ pub(crate) fn optimize_into_acir( .print(print_ssa_passes, "After Mem2Reg:") .fold_constants() .print(print_ssa_passes, "After Constant Folding:") - // .mem2reg() - // .print(print_ssa_passes, "After Mem2Reg:") - // .fold_constants() - // .print(print_ssa_passes, "After Constant Folding:") .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/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 9bfc25400fd..104f1552123 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -419,19 +419,17 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); - // dbg!(then_value.clone()); - // dbg!(else_value.clone()); - - // let len = self.recursively_get_slice_length(then_value_id); - // dbg!(len); + dbg!(then_value.clone()); + dbg!(else_value.clone()); let len = match then_value { Value::Array { array, .. } => array.len(), Value::Instruction { instruction, .. } => { match self.inserter.function.dfg[instruction] { Instruction::ArraySet { array: _, index: _, value: _, length } => { + dbg!("got array set then"); + let length = length.expect("ICE: array set on a slice must have a length"); - dbg!(&self.inserter.function.dfg[length]); let length = match &self.inserter.function.dfg[length] { Value::Instruction { instruction, .. } => { @@ -442,7 +440,6 @@ impl<'f> Context<'f> { } _ => length, }; - // let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); let len = match &self.inserter.function.dfg[length] { Value::NumericConstant { constant, .. } => { constant.to_u128() as usize @@ -450,7 +447,6 @@ impl<'f> Context<'f> { _ => unreachable!("ahh expected array set but got {:?}", &self.inserter.function.dfg[length]), }; len - // len.to_u128() as usize } _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), } @@ -464,6 +460,7 @@ impl<'f> Context<'f> { Value::Instruction { instruction, .. } => { match self.inserter.function.dfg[instruction] { Instruction::ArraySet { array: _, index: _, value: _, length } => { + dbg!("got array set else"); let length = length.expect("ICE: array set on a slice must have a length"); dbg!(&self.inserter.function.dfg[length]); @@ -479,6 +476,23 @@ impl<'f> Context<'f> { let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); len.to_u128() as usize } + Instruction::Load { address } => { + println!("LOAD address: {address}"); + let resolved_address = self.inserter.function.dfg.resolve(address); + println!("resolved address: {resolved_address}"); + dbg!(&self.inserter.function.dfg[address]); + match &self.inserter.function.dfg[address] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + let res = self.inserter.function.dfg.instruction_results(*instruction).first().expect("expected a result"); + dbg!(&self.inserter.function.dfg[*res]); + } + _ => panic!("ahh expected instr"), + } + let res = self.inserter.function.dfg.instruction_results(instruction).first().expect("expected a result"); + dbg!(&self.inserter.function.dfg[*res]); + panic!("ahhh got load") + } _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), } } @@ -522,51 +536,6 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } - // fn recursively_get_slice_length( - // &mut self, - // value: ValueId - // ) -> usize { - // match &self.inserter.function.dfg[value] { - // Value::Array { array, .. } => return array.len(), - // Value::Instruction { instruction, typ, .. } => { - // match &typ { - // Type::Slice(_) => (), - // _ => panic!("ICE: Expected slice type, but got {typ}"), - // }; - // match self.inserter.function.dfg[*instruction] { - // Instruction::ArraySet { array, index: _, value: _, length } => { - // let length = length.expect("ICE: array set on a slice must have a length"); - // dbg!(&self.inserter.function.dfg[length]); - - // let length = match &self.inserter.function.dfg[length] { - // Value::Instruction { instruction, position, typ } => { - // let length_instr = &self.inserter.function.dfg[*instruction]; - // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - // dbg!(&self.inserter.function.dfg[x]); - // x - // } - // _ => length, - // }; - // // Handle non-numeric constants later - // // However, I don't think the length should ever not be a dynamic constant at this point - // let len = &self.inserter.function.dfg.get_numeric_constant(length); - // if let Some(len) = len { - // len.to_u128() as usize - // } else { - // self.recursively_get_slice_length(array) - // } - // // len.to_u128() as usize - - // // panic!("ahhh") - // } - // _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[*instruction]), - // } - // // panic!("ah got instruction"); - // } - // _ => panic!("Expected array value"), - // } - // } - /// Given an if expression that returns an array: `if c { array1 } else { array2 }`, /// this function will recursively merge array1 and array2 into a single resulting array /// by creating a new array containing the result of self.merge_values for each element. From fb77dfa11d0029e4648617371bdf9be6e77b6204 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 23 Aug 2023 19:35:05 +0000 Subject: [PATCH 07/46] debug comment --- crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 104f1552123..8b7c9abe96d 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -444,7 +444,7 @@ impl<'f> Context<'f> { Value::NumericConstant { constant, .. } => { constant.to_u128() as usize } - _ => unreachable!("ahh expected array set but got {:?}", &self.inserter.function.dfg[length]), + _ => unreachable!("ahh expected numeric constants but got {:?}", &self.inserter.function.dfg[length]), }; len } @@ -477,6 +477,7 @@ impl<'f> Context<'f> { len.to_u128() as usize } Instruction::Load { address } => { + // This match is all for debugging println!("LOAD address: {address}"); let resolved_address = self.inserter.function.dfg.resolve(address); println!("resolved address: {resolved_address}"); @@ -484,8 +485,6 @@ impl<'f> Context<'f> { match &self.inserter.function.dfg[address] { Value::Instruction { instruction, .. } => { dbg!(&self.inserter.function.dfg[*instruction]); - let res = self.inserter.function.dfg.instruction_results(*instruction).first().expect("expected a result"); - dbg!(&self.inserter.function.dfg[*res]); } _ => panic!("ahh expected instr"), } From 8804a3f0ee79ba7b8cca60cf74738beb150854da Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 23 Aug 2023 16:00:16 -0500 Subject: [PATCH 08/46] Mild cleanup --- crates/noirc_evaluator/src/ssa.rs | 2 + crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 201 ++++++++++-------- 2 files changed, 111 insertions(+), 92 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index 77399994e83..aaa18205af2 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -63,6 +63,8 @@ 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() + .simplify_cfg() .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/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index 888bc454cda..2051afc341a 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -9,7 +9,7 @@ use crate::ssa::{ cfg::ControlFlowGraph, dom::DominatorTree, function::Function, - instruction::{Instruction, InstructionId}, + instruction::{Instruction, InstructionId, TerminatorInstruction}, post_order::PostOrder, types::Type, value::ValueId, @@ -20,7 +20,6 @@ use crate::ssa::{ impl Ssa { /// Attempts to remove any load instructions that recover values that are already available in /// scope, and attempts to remove stores that are subsequently redundant. - /// As long as they are not stores on memory used inside of loops pub(crate) fn mem2reg(mut self) -> Ssa { for function in self.functions.values_mut() { let mut context = PerFunctionContext::new(function); @@ -47,25 +46,35 @@ struct PerFunctionContext { #[derive(Debug, Default, Clone)] struct Block { + /// Maps a ValueId to the Expression it represents. + /// Multiple ValueIds can map to the same Expression, e.g. + /// dereferences to the same allocation. expressions: BTreeMap, + /// Each expression is tracked as to how many aliases it + /// may have. If there is only 1, we can attempt to optimize + /// out any known loads to that alias. Note that "alias" here + /// includes the original reference as well. aliases: BTreeMap>, - alias_sets: BTreeMap, + /// Each allocate instruction result (and some reference block parameters) + /// will map to a Reference value which tracks whether the last value stored + /// to the reference is known. + references: BTreeMap, } +/// An `Expression` here is used to represent a canonical key +/// into the aliases map since otherwise two dereferences of the +/// same address will be given different ValueIds. +/// +/// TODO: This should be expanded to any other value that can +/// hold a reference, such as arrays. #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] enum Expression { Dereference(Box), Other(ValueId), } -#[derive(Debug, Clone)] -struct Reference { - aliases: BTreeSet, - value: ReferenceValue, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ReferenceValue { Unknown, @@ -106,10 +115,6 @@ impl PerFunctionContext { /// of the value of the same reference at the end of its predecessor blocks. fn find_starting_references(&mut self, block: BasicBlockId) -> Block { let mut predecessors = self.cfg.predecessors(block); - println!( - "find_starting_references block {block} with {} predecessor(s)", - predecessors.len() - ); if let Some(first_predecessor) = predecessors.next() { let first = self.blocks.get(&first_predecessor).cloned().unwrap_or_default(); @@ -145,8 +150,7 @@ impl PerFunctionContext { self.analyze_instruction(function, &mut references, instruction, &mut last_stores); } - let terminator_arguments = function.dfg[block].terminator_arguments(); - self.mark_all_unknown(terminator_arguments, function, &mut references, &mut last_stores); + self.handle_terminator(function, block, &mut references, &mut last_stores); // 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 @@ -194,7 +198,7 @@ impl PerFunctionContext { self.instructions_to_remove.insert(*last_store); } - references.set_known_value(address, value); + references.set_known_value(address, value, last_stores); last_stores.insert(address, instruction); } Instruction::Call { arguments, .. } => { @@ -227,7 +231,7 @@ impl PerFunctionContext { for value in values { if function.dfg.type_of_value(*value) == Type::Reference { let value = function.dfg.resolve(*value); - references.set_unknown(value); + references.set_unknown(value, last_stores); last_stores.remove(&value); } } @@ -244,6 +248,45 @@ impl PerFunctionContext { .retain(|instruction| !self.instructions_to_remove.contains(instruction)); } } + + fn handle_terminator( + &self, + function: &mut Function, + block: BasicBlockId, + references: &mut Block, + last_stores: &mut BTreeMap, + ) { + match function.dfg[block].unwrap_terminator() { + TerminatorInstruction::JmpIf { .. } => (), // Nothing to do + TerminatorInstruction::Jmp { destination, arguments, .. } => { + let destination_parameters = function.dfg[*destination].parameters(); + assert_eq!(destination_parameters.len(), arguments.len()); + + // 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 { + let argument = function.dfg.resolve(*argument); + + if let Some(expression) = references.expressions.get(&argument) { + if let Some(aliases) = references.aliases.get_mut(expression) { + // The argument reference is possibly aliased by this block parameter + aliases.insert(*parameter); + + // TODO: Should we also insert an expression/alias for the reverse, + // argument -> parameter? + } + } + } + } + } + TerminatorInstruction::Return { return_values } => { + // 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); + } + } + } } impl Block { @@ -256,81 +299,69 @@ impl Block { if aliases.len() == 1 { let alias = aliases.first().expect("There should be exactly 1 alias"); - if let Some(reference) = self.alias_sets.get(alias) { - if let ReferenceValue::Known(value) = reference.value { - println!("get_known_value: returning {value} for alias {alias}"); - return Some(value); - } else { - println!("get_known_value: ReferenceValue::Unknown for alias {alias}"); - } - } else { - println!("get_known_value: No alias_set for alias {alias}"); + if let Some(ReferenceValue::Known(value)) = self.references.get(alias) { + return Some(*value); } - } else { - println!("get_known_value: {} aliases for address {address}", aliases.len()); } - } else { - println!("get_known_value: No known aliases for address {address}"); } - } else { - println!("get_known_value: No expression for address {address}"); } None } /// If the given address is known, set its value to `ReferenceValue::Known(value)`. - fn set_known_value(&mut self, address: ValueId, value: ValueId) { - self.set_value(address, 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_unknown(&mut self, address: ValueId) { - self.set_value(address, ReferenceValue::Unknown); + fn set_unknown( + &mut self, + address: ValueId, + last_stores: &mut BTreeMap, + ) { + self.set_value(address, ReferenceValue::Unknown, last_stores); } - fn set_value(&mut self, address: ValueId, value: ReferenceValue) { - // TODO: Verify this does not fail in the case of reference parameters - 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 - todo!("empty alias set"); - } else if aliases.len() == 1 { - let alias = aliases.first().expect("There should be exactly 1 alias"); - - if let Some(reference) = self.alias_sets.get_mut(alias) { - println!("set_known_value: Set value to {value:?} for alias {alias}"); - reference.value = value; - } else { - let reference = Reference { value, aliases: BTreeSet::new() }; - self.alias_sets.insert(*alias, reference); - println!("set_known_value: Created new alias set for alias {alias}, with value {value:?}"); - } - } else { - println!("set_known_value: {} aliases for expression {expression:?}, marking all unknown", aliases.len()); - // 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) = self.alias_sets.get_mut(alias) { - println!(" Marking {alias} unknown"); - reference.value = ReferenceValue::Unknown; - } else { - println!(" {alias} already unknown"); - } - } + fn set_value( + &mut self, + address: ValueId, + value: ReferenceValue, + last_stores: &mut BTreeMap, + ) { + 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(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; } - } else { - println!("set_known_value: ReferenceValue::Unknown for expression {expression:?}"); } - } else { - println!("\n\n!! set_known_value: Creating fresh expression for {address} in set_value... aliases will be empty\n\n"); - self.expressions.insert(address, Expression::Other(address)); } } - fn unify(mut self, other: &Self) -> Self { - println!("unify:\n {self:?}\n {other:?}"); + fn invalidate_all_references(&mut self, last_stores: &mut BTreeMap) { + for reference_value in self.references.values_mut() { + *reference_value = ReferenceValue::Unknown; + } + + last_stores.clear(); + } + fn unify(mut self, other: &Self) -> Self { for (value_id, expression) in &other.expressions { if let Some(existing) = self.expressions.get(value_id) { assert_eq!(existing, expression, "Expected expressions for {value_id} to be equal"); @@ -354,14 +385,13 @@ impl Block { // Keep only the references present in both maps. let mut intersection = BTreeMap::new(); - for (value_id, reference) in &other.alias_sets { - if let Some(existing) = self.alias_sets.get(value_id) { - intersection.insert(*value_id, existing.unify(reference)); + for (value_id, reference) in &other.references { + if let Some(existing) = self.references.get(value_id) { + intersection.insert(*value_id, existing.unify(*reference)); } } - self.alias_sets = intersection; + self.references = intersection; - println!("unify result:\n {self:?}"); self } @@ -382,14 +412,6 @@ impl Block { } } -impl Reference { - fn unify(&self, other: &Self) -> Self { - let value = self.value.unify(other.value); - let aliases = self.aliases.union(&other.aliases).copied().collect(); - Self { value, aliases } - } -} - impl ReferenceValue { fn unify(self, other: Self) -> Self { if self == other { @@ -593,14 +615,11 @@ mod tests { // store Field 6 at v0 // return v3, Field 5, Field 6 // Optimized to constants 5 and 6 // } - println!("{ssa}"); let ssa = ssa.mem2reg(); let main = ssa.main(); assert_eq!(main.reachable_blocks().len(), 2); - println!("{ssa}"); - // The loads should be removed assert_eq!(count_loads(main.entry_block(), &main.dfg), 0); assert_eq!(count_loads(b1, &main.dfg), 0); @@ -686,9 +705,7 @@ mod tests { // v8 = eq Field 1, Field 2 // return // } - println!("{ssa}"); let ssa = ssa.mem2reg(); - println!("{ssa}"); let main = ssa.main(); assert_eq!(main.reachable_blocks().len(), 2); From cd29ec842361cda7fba0ab2534e84152a5610742 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Wed, 23 Aug 2023 16:11:13 -0500 Subject: [PATCH 09/46] Undo some unneeded changes --- crates/noirc_evaluator/src/ssa.rs | 2 -- crates/noirc_evaluator/src/ssa/ir/basic_block.rs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index aaa18205af2..77399994e83 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -63,8 +63,6 @@ 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() - .simplify_cfg() .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/basic_block.rs b/crates/noirc_evaluator/src/ssa/ir/basic_block.rs index b3b566f8f37..998591f7210 100644 --- a/crates/noirc_evaluator/src/ssa/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa/ir/basic_block.rs @@ -122,11 +122,10 @@ impl BasicBlock { /// Return the jmp arguments, if any, of this block's TerminatorInstruction. /// - /// If this block has no terminator, this will be empty. + /// If this block has no terminator, or a Return terminator this will be empty. pub(crate) fn terminator_arguments(&self) -> &[ValueId] { match &self.terminator { Some(TerminatorInstruction::Jmp { arguments, .. }) => arguments, - Some(TerminatorInstruction::Return { return_values, .. }) => return_values, _ => &[], } } From f3f34aa4f295c9233f6c75a0c751de688abb44d9 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 24 Aug 2023 12:10:36 -0500 Subject: [PATCH 10/46] Partially revert cleanup commit which broke some tests --- .../execution_success/references/src/main.nr | 2 +- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 37 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/references/src/main.nr b/crates/nargo_cli/tests/execution_success/references/src/main.nr index ec23f2f3a38..70de5cada3f 100644 --- a/crates/nargo_cli/tests/execution_success/references/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/references/src/main.nr @@ -229,4 +229,4 @@ fn regression_2218_loop(x: Field, y: Field) { assert(*q1 == 20); } assert(*q1 == 2); -} \ No newline at end of file +} diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index 2051afc341a..c0141fe0f8d 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -332,22 +332,27 @@ impl Block { value: ReferenceValue, last_stores: &mut BTreeMap, ) { - 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(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; + 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 + println!("Invalidating all references for address {address}"); + self.invalidate_all_references(last_stores); + } else if aliases.len() == 1 { + let alias = aliases.first().expect("There should be exactly 1 alias"); + println!("set_known_value: Setting {} value to {:?}", alias, value); + self.references.insert(*alias, value); + } else { + println!("set_known_value: {} aliases for expression {expression:?}, marking all unknown", aliases.len()); + // 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() { + println!(" Marking {alias} unknown"); + if let Some(reference_value) = self.references.get_mut(alias) { + *reference_value = ReferenceValue::Unknown; + } + } } } } From 0887db5139d3ab05e648a67937e627ecba073875 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 24 Aug 2023 12:55:36 -0500 Subject: [PATCH 11/46] Add large doc comment --- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index c0141fe0f8d..4bc4fbc5d59 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -1,6 +1,66 @@ -//! mem2reg implements a pass for promoting values stored in memory to values in registers where -//! possible. This is particularly important for converting our memory-based representation of -//! mutable variables into values that are easier to manipulate. +//! The goal of the mem2reg SSA optimization pass is to replace any `Load` instructions to known +//! addresses with the value stored at that address, if it is also known. This pass will also remove +//! any `Store` instructions within a block that are no longer needed because no more loads occur in +//! between the Store in question and the next Store. +//! +//! The pass works as follows: +//! - Each block in each function is iterated in forward-order. +//! - The starting value of each reference in the block is the unification of the same references +//! at the end of each direct predecessor block to the current block. +//! - At each step, the value of each reference is either Known(ValueId) or Unknown. +//! - Two reference values unify to each other if they are exactly equal, or to Unknown otherwise. +//! - If a block has no predecessors, the starting value of each reference is Unknown. +//! - Throughout this pass, aliases of each reference are also tracked. +//! - References typically have 1 alias - themselves. +//! - A reference with multiple aliases means we will not be able to optimize out loads if the +//! reference is stored to. Note that this means we can still optimize out loads if these +//! aliased references are never stored to, or the store occurs after a load. +//! - A reference with 0 aliases means we were unable to find which reference this reference +//! refers to. If such a reference is stored to, we must conservatively invalidate every +//! reference in the current block. +//! +//! From there, to figure out the value of each reference at the end of block, iterate each instruction: +//! - On `Instruction::Allocate`: +//! - Register a new reference was made with itself as its only alias +//! - On `Instruction::Load { address }`: +//! - If `address` is known to only have a single alias (including itself) and if the value of +//! that alias is known, replace the value of the load with the known value. +//! - Furthermore, if the result of the load is a reference, mark that reference as an alias +//! of the reference it dereferences to (if known). +//! - If which reference it dereferences to is not known, this load result has no aliases. +//! - On `Instruction::Store { address, value }`: +//! - If the address of the store is known: +//! - If the address has exactly 1 alias: +//! - Set the value of the address to `Known(value)`. +//! - If the address has more than 1 alias: +//! - Set the value of every possible alias to `Unknown`. +//! - If the address has 0 aliases: +//! - Conservatively mark every alias in the block to `Unknown`. +//! - If the address of the store is not known: +//! - Conservatively mark every alias in the block to `Unknown`. +//! - Additionally, if there were no Loads to any alias of the address between this Store and +//! the previous Store to the same address, the previous store can be removed. +//! - On `Instruction::Call { arguments }`: +//! - If any argument of the call is a reference, set the value of each alias of that +//! reference to `Unknown` +//! - Any builtin functions that may return aliases if their input also contains a +//! reference should be tracked. Examples: `slice_push_back`, `slice_insert`, `slice_remove`, etc. +//! +//! On a terminator instruction: +//! - If the terminator is a `Jmp`: +//! - For each reference argument of the jmp, mark the corresponding block parameter it is passed +//! to as an alias for the jmp argument. +//! +//! Finally, if this is the only block in the function, we can remove any Stores that were not +//! referenced by the terminator instruction. +//! +//! Repeating this algorithm for each block in the function in program order should result in +//! optimizing out most known loads. However, identifying all aliases correctly has been proven +//! undecidable in general (Landi, 1992). So this pass will not always optimize out all loads +//! that could theoretically be optimized out. This pass can be performed at any time in the +//! SSA optimization pipeline, although it will be more successful the simpler the program's CFG is. +//! This pass is currently performed several times to enable other passes - most notably being +//! performed before loop unrolling to try to allow for mutable variables used for for loop indices. use std::collections::{BTreeMap, BTreeSet}; use crate::ssa::{ @@ -75,6 +135,7 @@ enum Expression { Other(ValueId), } +/// Every reference's value is either Known and can be optimized away, or Unknown. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ReferenceValue { Unknown, From 33feb07991629c9ef127ecf7d516b6164f42255c Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 24 Aug 2023 13:14:15 -0500 Subject: [PATCH 12/46] Add test and remove printlns --- .../references_aliasing/Nargo.toml | 7 +++++++ .../references_aliasing/Prover.toml | 0 .../references_aliasing/src/main.nr | 10 ++++++++++ .../target/references.bytecode | 1 + .../references_aliasing/target/witness.tr | Bin 0 -> 788 bytes crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 4 ---- 6 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 crates/nargo_cli/tests/execution_success/references_aliasing/Nargo.toml create mode 100644 crates/nargo_cli/tests/execution_success/references_aliasing/Prover.toml create mode 100644 crates/nargo_cli/tests/execution_success/references_aliasing/src/main.nr create mode 100644 crates/nargo_cli/tests/execution_success/references_aliasing/target/references.bytecode create mode 100644 crates/nargo_cli/tests/execution_success/references_aliasing/target/witness.tr 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 @@ +H4sIAAAAAAAA/+1c8ZONVRh+WCKykYqIiFRE37f37u5dkY2IEEJEtLv2bkJKQkpCSkJKIjLGNE3TmJqpmZqpmZrRH9L/Uu907szZzfjlPO+Z98yc88u+w8yz3/O853ue93zf3fsXgL/x/zXY/ex0P4uwVQ7ysCpFW7Vab2+pl5Wyu2jp6Km1FtXWnrZaWStba629LbVKpV6r1to7ejrai46yWqmXfa0dlT4HNpiAVe/7bzX9izHoJvwHkfkP5mEV/vU2efWQAb3z//82BU4Y8HsG6th8k3+j/nKNJjUp4A4Bb/Nr8R7C71HhQZrXtLEsG99QpGd8Q6FjfLd5dTa+QMyhSkINg23jE97D+D1SNT62po1l2fiGIz3jGw4d47vdq7PxBWIOd4KycUfAtvEJ7xH8HqkaH1vTxrJsfCORnvGNhI7x3eHV2fgCMUc6Qdm4o2Db+IT3KH6PVI2PrWljWTa+ZtCMrxbL+JqhY3x3enU2vkDMZicoG3c0bBuf8B7N71EyhjIGLEOp12MZyhjoGMpdXp0NJRBzjBOUjTsWtg1FeI/l9+iWhhKqA9Ok74bOTcHmzNxH9yTCmdnneyNxLsJWL7PP43jX1SIYbO+Rnoy7CW4o76vQ8bEmKv+yytzf44l9uQrWkBXvcRWRf78h6z6vzkNWIOZ4JygbdwJsD1nCewK/R6qPq9iaNhZ7SGCeLifS9CsrsYyPd839je9+r87GF4g50QnKxp0E28YnvCfxe6T5uKqVqelkIudYhsK8Zv96H/DqbCiBmJOdoGzcKbBtKMJ7Cr9HqpPUFMSZpIqwRX1OP5WAFfsIORU6xvegV2fjC8Sc6gRl406DbeMT3tP4PUrGUKaDZSjxXvxNh46hPOTV2VACMac7Qdm4M2DbUIT3DH6PVJ/1ME36YejcFGzOzH30SCKcmX1+NBLnImz1Mvs8k3ddKi/+pCczwX/xdw06PsZ+8cfc37OIfbkG2pDVG2vIIvLvN2Q95tV5yArEnOUEZePOhu0hS3jP5vdI9XEVW9PGsny6nAOW8cX7nPoc6Bjf416djS8Qc44TlI1bwLbxCe+C3yNFQylbmJqWRM6xDIV5zf71tnh1NpRAzNIJysatwLahCO8Kv0eqkxRb08ayPElVkd4kVYWO8bV6dTa+QMyqE5SN2wbbxie82/g9SsZQ2pGeobRDx1BqXp0NJRCz3QnKxu2AbUMR3h38HiVjKHORnqHMhY6hPOHV2VACMec6Qdm482DbUIT3PH6PkjGU+UjPUOZDx1Ce9OpsKIGY852gbNwFsG0ownsBv0fJGEon0jOUTugYylNenQ0lELPTCcrGXQjbhiI4C/k9SsZQFiE9Q1kEHUN52quzoQRiLnKCsnEXw7ahCO/F/B4lYyhLwDKUeH+NsQQ6hvKMV2dDCcRc4gRl4y6FbUMR3kv5PUrGUJYhPUNZBh1Dedars6EEYi5zgrJxl8O2oQjv5fweqf55F9OkV/A4t9yKcxG2qCa6EumZ6EromOhzXp1NNBBzpROUjbsKtk1UeK/i90jlWsXsV4D/N3XfQCc8mkjX2fiWEGYgryb2hahfMkG0BukF0RroBNHzXp2DKBBzjROUjbsWtoNIeK/l90jlWiUwV4MfRN8ijSBiDjXriH0h6pdMEK1HekG0HjpB9IJX5yAKxFzvBGXjboDtIBLeG/g9UrlWCcx14AfRd0gjiJhDzUZiX4j63TKIQjkzX0K/CNt+Jvf0RoV75Xukca8wfXcTsS9E/VS+yUn29SaFfXM90r4pglZZZXrEZmJfrhPvjViDL5F/v8H3Ja/Og28g5mYnKBt3C2wPvsJ7C79Hqt8/wNa0sSyf+LeCZnzRPuq6FTrG97JXZ+MLxNzqBGXjdsG28QnvLn6PkjGUbqRnKN3QMZQer86GEojZ7QRl426DbUMR3tv4PVL9ZFoXEasXOjcFmzNzH9UjcS7CFjU4+sAKjnjvnvqgExyveHUOjkDMPicoG3c7bAeH8N7O75FqcDBN9FWkERzMfbSDx7nfuyLLz4F3Evus9RzdcvDuAit4453YdkEneF/z6hy8gZi7nKBs3N2wHbzCeze/R8mc2F5HGsHL3EdvROJchC1qcOwBKzjindj2QCc43vTqHByBmHucoGzcvbAdHMJ7L79HqsHBNNG3kEZwMPfRPh5nlU8syYl8J/ifWPoB3P3N5i1PD3Yo8P4ROvd1E/k69xO1JPa61NLP8qB1AKxBK94J/QB0Bq23vToPWoGYB5ygbNyDsD1oCe+D/B4lc0J/Bzo3BZszcx+9G4lzEbaowXEIrOCId0I/BJ3geM+rc3AEYh5ygrJxD8N2cAjvw/weqQYH00TfRxrBwdxHR3icVU7o8gRmP/gn1Z/A3d9s3vK0aJ8C75+hc1+zT+hHiVoSe11q6Wd50DoG1qAV74R+DDqD1gdenQetQMxjTlA27nHYHrSE93F+j5I5oX8InZuCzZm5jz6KxLkIW9TgOAFWcMQ7oZ+ATnB87NU5OAIxTzhB2bgnYTs4hPdJfo9Ug4Npop8gjeBg7qNTPM4qJ3R5AnMU/JPqL+DubzZveVp0RIH3r9C5r9kn9NNELYm9LrX0szxonQFr0Ip3Qj8DnUHrU6/Og1Yg5hknKBv3LGwPWsL7LL9HyZzQP4POTcHmzNxHn0fiXIQtanCcAys44p3Qz0EnOL7w6hwcgZjnnKBs3POwHRzC+zy/R6rBwTTRL5FGcDD30QUeZ5UTujyBOQ3+SfU3cPc3m7c8LTqlwPt36NzX7BP6RaKWxF6XWvpZHqi/gu28lgy4qHCv/AHbHiF5dUGB959IwyMuEbUk9rpk68feN3I/X1LYNzcQZ98UQausdhE5Xyb25QbhumJ/1zWRf78D7ddenQ+0gZiXnaBs3CuwfaAV3lf4PVL9rmumpoMGXKO//gGokXOuaucAAA== 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 0000000000000000000000000000000000000000..22a1c7fe1098dfe81652877dc26d99dc3aae67ec GIT binary patch literal 788 zcmV+v1MB=BiwFP!00002|E-o)&l5oyg>iRxciq{U9YH_b-JPB84DRmk?ixY}A%p;d z1QJLf;iu7laMAnk*|te9nkVO+_dV13T<_mgzx=8B|2?VOlQ!U3Nkfp;h+~Pypspq$ zt10NQ8K|o{$Z7$4YzgXW1+rR$9@|v0WTKS{CJjQ0JV~9mS)wwpt*|1qS&>>PgH_fz z?@Z!^l0n4G$q;LwyloXLbtbnZof;E_R$eFGr&fjXubniBiiOLBNGbp+qz}2w)2LO+ z#8kQRTebu9YR`F0I#jVlR55C+o$%VHp(unavRv7a>$H@3KXx_(IY`!qd?YZF#9p!{c9|E|4P8T1jtH3mIUXhz&U2%95tAi z0Xj0p_(5%xe{x*J?1Y zHQ+sZEvRc9nAducwE@g)BX|ec1l|EQgL&nkM-O@|fgTHRCj!Wd;90l@JPWsi*>3~$ z+76yyJ3!V>utU4R4($f#xCfl$UXZm9?9hI&LkGa@4}y6e0{8wf$T|Xc=qT8sW8fT* zgL6CqvQB~>It6y}^6cIW}vp@(4hkHEYhgM0r3WIY8vJ_B_<2j}<# zWW5CQdIje78ua)EWW5Dh??Bdjko5ta<3~`}Cs5ZHP}f&b*EdktcTm?4P}fgT*Dp}l SZ&24Cko6Z+MM~zdGXMaV18<%H literal 0 HcmV?d00001 diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index 4bc4fbc5d59..19e752d8b6a 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -398,18 +398,14 @@ impl Block { 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 - println!("Invalidating all references for address {address}"); self.invalidate_all_references(last_stores); } else if aliases.len() == 1 { let alias = aliases.first().expect("There should be exactly 1 alias"); - println!("set_known_value: Setting {} value to {:?}", alias, value); self.references.insert(*alias, value); } else { - println!("set_known_value: {} aliases for expression {expression:?}, marking all unknown", aliases.len()); // 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() { - println!(" Marking {alias} unknown"); if let Some(reference_value) = self.references.get_mut(alias) { *reference_value = ReferenceValue::Unknown; } From 2574c33f9b8dcc8e96840eba5b21a48c4d1c6ee3 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 24 Aug 2023 14:33:24 -0500 Subject: [PATCH 13/46] Remove todos --- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index 19e752d8b6a..bd1015245bf 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -126,9 +126,6 @@ struct Block { /// An `Expression` here is used to represent a canonical key /// into the aliases map since otherwise two dereferences of the /// same address will be given different ValueIds. -/// -/// TODO: This should be expanded to any other value that can -/// hold a reference, such as arrays. #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] enum Expression { Dereference(Box), @@ -203,7 +200,6 @@ impl PerFunctionContext { block: BasicBlockId, mut references: Block, ) { - // TODO: Can we avoid cloning here? let instructions = function.dfg[block].instructions().to_vec(); let mut last_stores = BTreeMap::new(); @@ -274,10 +270,6 @@ impl PerFunctionContext { aliases.insert(result); references.aliases.insert(Expression::Other(result), aliases); } - - // TODO: Track aliases here - // Instruction::ArrayGet { array, index } => todo!(), - // Instruction::ArraySet { array, index, value } => todo!(), _ => (), } } @@ -332,9 +324,6 @@ impl PerFunctionContext { if let Some(aliases) = references.aliases.get_mut(expression) { // The argument reference is possibly aliased by this block parameter aliases.insert(*parameter); - - // TODO: Should we also insert an expression/alias for the reverse, - // argument -> parameter? } } } From 96b946978925a332482e8903975ce4cf6234e322 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 24 Aug 2023 20:11:17 +0000 Subject: [PATCH 14/46] working dynamic slice indices w/ slice push back, need to still cleanup and add other dnyamic slice intrinsics --- Cargo.toml | 3 + .../execution_success/slices/src/main.nr | 108 ++++++- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 227 +++++++++---- .../src/ssa/opt/flatten_cfg.rs | 302 ++++++++++++------ 4 files changed, 470 insertions(+), 170 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76ec9edfa0d..3447569d2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,6 @@ url = "2.2.0" wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] } wasm-bindgen-test = "0.3.33" base64 = "0.21.2" + +# [patch.crates-io] +# acvm = { path = "/mnt/user-data/maxim/acvm/acvm" } diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 5152c098b70..931eeec9b57 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -76,10 +76,16 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { slice = slice.push_back(i); } assert(slice.len() == 5); - // dynamic_slice_index_set_if(slice, x, y); - // dynamic_slice_index_set_else(slice, x, y); - // dynamic_slice_index_set_nested(slice, x, y); - dynamic_slice_index_get(slice, x, y); + + dynamic_slice_index_set_if(slice, x, y); + dynamic_slice_index_set_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + dynamic_slice_index_if(slice, x, y); + dynamic_slice_index_else(slice, x, y); + + // dynamic_slice_merge_if(slice, x, y); + // dynamic_slice_merge_else(slice, x, y); } fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { @@ -115,32 +121,108 @@ fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { assert(slice[4] == 0); } -fn dynamic_slice_index_set_nested(mut slice: [Field], x: Field, y: Field) { +// This tests the case of missing a store instruction in the else branch +// of merging slices +fn dynamic_slice_index_if(mut slice: [Field], x: Field, y: Field) { + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + } else { + assert(slice[x] == 0); + } + assert(slice[4] == 2); +} + +// This tests the case of missing a store instruction in the then branch +// of merging slices +fn dynamic_slice_index_else(mut slice: [Field], x: Field, y: Field) { + if x as u32 > 10 { + assert(slice[x] == 0); + } else { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + } + assert(slice[4] == 2); +} + +// TODO: This is still broken. Need to verify whether attaching +// predicates to memory ops in ACIR solves the issue (PR #2400), but I am pretty +// sure that it is the cause. +fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice = slice.push_back(10); + } else { + assert(slice[x] == 0); + } + + assert(slice[4] == 2); + assert(slice[slice.len() - 1] == 10); +} + +// TODO: This is still broken. Need to verify whether attaching +// predicates to memory ops in ACIR solves the issue (PR #2400), but I am pretty +// sure that it is the cause. +fn dynamic_slice_merge_else(mut slice: [Field], x: Field, y: Field) { + if x as u32 > 10 { + assert(slice[x] == 0); + } else { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice = slice.push_back(10); + } + + assert(slice[4] == 2); + assert(slice[slice.len() - 1] == 10); +} + +fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { assert(slice[x] == 4); assert(slice[y] == 1); slice[y] = 0; assert(slice[x] == 4); assert(slice[1] == 0); - if x as u32 > 10 { + if x as u32 < 10 { slice[x] = slice[x] - 2; - // TODO: this panics as we have a load for the slice in flattening - if y == 1 { + if y != 1 { slice[x] = slice[x] + 20; + } else { + if x == 5 { + // We should not hit this case + assert(slice[x] == 22); + } else { + slice[x] = 10; + } + assert(slice[4] == 10); } } else { slice[x] = 0; } - assert(slice[4] == 0); + assert(slice[4] == 10); } -fn dynamic_slice_index_get(mut slice: [Field], x: Field, y: Field) { +fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 2); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[2] == 0); if x as u32 < 10 { - assert(slice[x] == 4); slice[x] = slice[x] - 2; + // TODO: this panics as we have a load for the slice in flattening + if y == 1 { + slice[x] = slice[x] + 20; + } else { + if x == 4 { + slice[x] = 5; + } + assert(slice[4] == 5); + } } else { - assert(slice[x] == 0); + slice[x] = 0; } - assert(slice[4] == 2); + assert(slice[4] == 5); } // Ensure that slices of struct/tuple values work. diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 8b3fd105644..19cd870774c 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -28,6 +28,7 @@ use acvm::{ acir::{brillig::Opcode, circuit::opcodes::BlockId, native_types::Expression}, FieldElement, }; +use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_frontend::Distinctness; @@ -322,6 +323,8 @@ impl Context { ) -> Result<(), RuntimeError> { let instruction = &dfg[instruction_id]; self.acir_context.set_call_stack(dfg.get_call_stack(instruction_id)); + // println!("instruction: {instruction_id}"); + // dbg!(instruction.clone()); match instruction { Instruction::Binary(binary) => { let result_acir_var = self.convert_ssa_binary(binary, dfg)?; @@ -337,6 +340,8 @@ impl Context { } Instruction::Call { func, arguments } => { let result_ids = dfg.instruction_results(instruction_id); + let resolved_result_ids = vecmap(result_ids, |id| dfg.resolve(*id)); + dbg!(resolved_result_ids.clone()); match &dfg[*func] { Value::Function(id) => { let func = &ssa.functions[id]; @@ -371,6 +376,8 @@ impl Context { // assert_eq!(result_ids.len(), outputs.len()); for (result, output) in result_ids.iter().zip(outputs) { + dbg!(result); + dbg!(output.clone()); self.ssa_values.insert(*result, output); } } @@ -478,62 +485,77 @@ impl Context { ) -> Result<(), RuntimeError> { let index_const = dfg.get_numeric_constant(index); - match self.convert_value(array, dfg) { - AcirValue::Var(acir_var, _) => { - return Err(RuntimeError::InternalError(InternalError::UnExpected { - expected: "an array value".to_string(), - found: format!("{acir_var:?}"), - call_stack: self.acir_context.get_call_stack(), - })) - } - AcirValue::Array(array) => { - if let Some(index_const) = index_const { - let array_size = array.len(); - 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 >= array_size { - // Ignore the error if side effects are disabled. - if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) - { - let call_stack = self.acir_context.get_call_stack(); - return Err(RuntimeError::IndexOutOfBounds { - index, - array_size, - call_stack, - }); - } - let result_type = - dfg.type_of_value(dfg.instruction_results(instruction)[0]); - let value = self.create_default_value(&result_type)?; - self.define_result(dfg, instruction, value); - return Ok(()); + match dfg.type_of_value(array) { + Type::Array(_, _) => { + match self.convert_value(array, dfg) { + AcirValue::Var(acir_var, _) => { + return Err(RuntimeError::InternalError(InternalError::UnExpected { + expected: "an array value".to_string(), + found: format!("{acir_var:?}"), + call_stack: self.acir_context.get_call_stack(), + })) } - - let value = match store_value { - Some(store_value) => { - let store_value = self.convert_value(store_value, dfg); - AcirValue::Array(array.update(index, store_value)) + AcirValue::Array(array) => { + dbg!("got here"); + if let Some(index_const) = index_const { + dbg!(index_const); + let array_size = array.len(); + 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 >= array_size { + // Ignore the error if side effects are disabled. + if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) + { + dbg!("array out of bounds"); + let call_stack = self.acir_context.get_call_stack(); + return Err(RuntimeError::IndexOutOfBounds { + index, + array_size, + call_stack, + }); + } + let result_type = + dfg.type_of_value(dfg.instruction_results(instruction)[0]); + dbg!("about to create default value"); + let value = self.create_default_value(&result_type)?; + self.define_result(dfg, instruction, value); + return Ok(()); + } + + let value = match store_value { + Some(store_value) => { + let store_value = self.convert_value(store_value, dfg); + AcirValue::Array(array.update(index, store_value)) + } + None => array[index].clone(), + }; + + self.define_result(dfg, instruction, value); + return Ok(()); } - None => array[index].clone(), - }; - - self.define_result(dfg, instruction, value); - return Ok(()); + } + AcirValue::DynamicArray(_) => (), } } - AcirValue::DynamicArray(_) => (), + Type::Slice(_) => { + // Do nothing we only want dynamic checks here + } + _ => unreachable!("ICE: expected array or slice type"), } + + dbg!("got here"); let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); + dbg!(store_value.is_some()); if let Some(store) = store_value { self.array_set(instruction, array, index, store, dfg, map_array, length)?; } else { @@ -553,6 +575,8 @@ impl Context { ) -> Result<(), RuntimeError> { let array = dfg.resolve(array); let block_id = self.block_id(&array); + dbg!(block_id.0); + dbg!(self.initialized_arrays.contains(&block_id)); if !self.initialized_arrays.contains(&block_id) { match &dfg[array] { Value::Array { array, .. } => { @@ -560,7 +584,23 @@ impl Context { array.iter().map(|i| self.convert_value(*i, dfg)).collect(); self.initialize_array(block_id, array.len(), Some(&values))?; } + Value::Instruction { instruction, .. } => { + let results = dfg.instruction_results(*instruction); + dbg!(results.clone()); + dbg!(self.convert_value(results[0], dfg)); + dbg!(self.convert_value(results[1], dfg)); + match self.convert_value(results[1], dfg) { + AcirValue::Array(values) => { + let values = vecmap(values, |v| v.clone()); + dbg!(values.len()); + self.initialize_array(block_id, values.len(), Some(&values))?; + } + // TODO: return internal error + _ => unreachable!("ICE: expected array as result"), + } + } _ => { + dbg!(&dfg[array]); return Err(RuntimeError::UnInitialized { name: "array".to_string(), call_stack: self.acir_context.get_call_stack(), @@ -568,7 +608,7 @@ impl Context { } } } - + dbg!(block_id.0); let index_var = self.convert_value(index, dfg).into_var()?; let read = self.acir_context.read_from_memory(block_id, &index_var)?; let typ = match dfg.type_of_value(array) { @@ -620,17 +660,6 @@ impl Context { let len = match dfg.type_of_value(array) { Type::Array(_, len) => len, Type::Slice(_) => { - // let value = &dfg[array]; - // match value { - // Value::Array { array, .. } => { - // dbg!(array.len()); - // } - // Value::Instruction { instruction, position, typ } => { - // let instruction = &dfg[*instruction]; - // dbg!(instruction.clone()); - // } - // _ => unreachable!("ICE - expected value with slice type to be an array but got {:?}", value), - // } let length = length.expect("ICE: array set on slice must have a length associated with the call"); let len = dfg.get_numeric_constant(length).expect("ICE: slice length should be fully tracked and constant by ACIR gen"); len.to_u128() as usize @@ -704,6 +733,7 @@ impl Context { values: Option<&[AcirValue]>, ) -> Result<(), InternalError> { self.acir_context.initialize_array(array, len, values)?; + dbg!(array.0); self.initialized_arrays.insert(array); Ok(()) } @@ -1063,17 +1093,92 @@ impl Context { Ok(Self::convert_vars_to_values(out_vars, dfg, result_ids)) } Intrinsic::ArrayLen => { + dbg!("got here"); let len = match self.convert_value(arguments[0], dfg) { AcirValue::Var(_, _) => unreachable!("Non-array passed to array.len() method"), AcirValue::Array(values) => (values.len() as u128).into(), AcirValue::DynamicArray(array) => (array.len as u128).into(), }; + dbg!(len); Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())]) } + Intrinsic::SlicePushBack => { + dbg!(arguments.clone()); + + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + let element = self.convert_value(arguments[2], dfg); + + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.add_var(slice_length, one)?; + + // Classic array way + dbg!("making new slice"); + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + new_slice.push_back(element); + dbg!("got new slice"); + + // TODO: this complicates things in arrayget, but is it perhaps more efficient?? + // let result_block_id = self.block_id(&arguments[1]); + // let len = match slice { + // AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { + // let new_len = len + 1; + // let mut init_values = try_vecmap(0..len, |i| { + // let index = AcirValue::Var( + // self.acir_context.add_constant(FieldElement::from(i as u128)), + // AcirType::NumericType(NumericType::NativeField), + // ); + // let var = index.into_var()?; + // let read = self.acir_context.read_from_memory(block_id, &var)?; + // Ok::(AcirValue::Var(read, AcirType::NumericType(NumericType::NativeField))) + // })?; + // init_values.push(element); + // self.initialize_array(result_block_id, new_len, Some(&init_values))?; + // new_len + // } + // _ => unreachable!("ICE: expected a dynamic array but got {:?}", slice), + // }; + // dbg!(self.initialized_arrays.contains(&result_block_id)); + // let new_slice = + // AcirValue::DynamicArray(AcirDynamicArray { block_id: result_block_id, len }); + // Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), new_slice]) + + Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), AcirValue::Array(new_slice.into())]) + } _ => todo!("expected a black box function"), } } + fn slice_intrinsic_input( + &mut self, + old_slice: &mut Vector, + input: AcirValue, + ) -> Result<(), RuntimeError> { + match input { + AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { + for i in 0..len { + // We generate witnesses corresponding to the array values + let index = AcirValue::Var( + self.acir_context.add_constant(FieldElement::from(i as u128)), + AcirType::NumericType(NumericType::NativeField), + ); + + let index_var = index.into_var()?; + let value_read_var = self.acir_context.read_from_memory(block_id, &index_var)?; + let value_read = AcirValue::Var( + value_read_var, + AcirType::NumericType(NumericType::NativeField), + ); + + old_slice.push_back(value_read); + } + } + _ => unreachable!("ICE: expected a dynamic array but got {:?}", input), + } + Ok(()) + } + /// Given an array value, return the numerical type of its element. /// Panics if the given value is not an array or has a non-numeric element type. fn array_element_type(dfg: &DataFlowGraph, value: ValueId) -> AcirType { diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 8b7c9abe96d..759ac4e4691 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -143,7 +143,7 @@ use crate::ssa::{ dfg::{CallStack, InsertInstructionResult}, function::Function, function_inserter::FunctionInserter, - instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, + instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction, Intrinsic}, types::Type, value::{Value, ValueId}, }, @@ -177,6 +177,12 @@ struct Context<'f> { /// Maps an address to the old and new value of the element at that address 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 preceding block + // outer_block_stores: HashMap<(ValueId, BasicBlockId), Store>, + 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 @@ -193,6 +199,7 @@ struct Context<'f> { conditions: Vec<(BasicBlockId, ValueId)>, } +#[derive(Debug, Clone)] struct Store { old_value: ValueId, new_value: ValueId, @@ -225,6 +232,7 @@ fn flatten_function_cfg(function: &mut Function) { local_allocations: HashSet::new(), branch_ends, conditions: Vec::new(), + outer_block_stores: HashMap::new(), }; context.flatten(); } @@ -233,7 +241,7 @@ impl<'f> Context<'f> { fn flatten(&mut self) { // Start with following the terminator of the entry block since we don't // need to flatten the entry block into itself. - self.handle_terminator(self.inserter.function.entry_block()); + self.handle_terminator(self.inserter.function.entry_block(), HashMap::new()); } /// Check the terminator of the given block and recursively inline any blocks reachable from @@ -246,7 +254,36 @@ 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 { + fn handle_terminator(&mut self, block: BasicBlockId, mut outer_block_stores: HashMap,) -> BasicBlockId { + // let mut outer_block_stores = HashMap::new(); + let instructions = self.inserter.function.dfg[block].instructions().to_vec(); + // let entry_block = self.inserter.function.entry_block(); + println!("inline_block destination: {block}"); + for instruction in instructions { + // self.push_instruction(instruction); + let (instruction, _) = self.inserter.map_instruction(instruction); + match instruction { + Instruction::Store { address, value } => { + println!("remember STORE address: {address}"); + let load = Instruction::Load { address }; + let load_type = Some(vec![self.inserter.function.dfg.type_of_value(value)]); + let old_value = self.insert_instruction_with_typevars(load, load_type).first(); + println!("remember old_value: {old_value}"); + println!("remember new_value: {value}"); + dbg!(&self.inserter.function.dfg[value]); + outer_block_stores.insert(address, Store { old_value, new_value: value }); + // self.outer_block_stores.insert((address, block), Store { old_value, new_value: value }); + self.outer_block_stores.insert(address, Store { old_value, new_value: value }); + + } + _ => { + // DO nothing + // println!("Not a store instruction") + } + } + } + dbg!(outer_block_stores.clone()); + match self.inserter.function.dfg[block].unwrap_terminator() { TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { let old_condition = *condition; @@ -255,8 +292,12 @@ impl<'f> Context<'f> { let then_condition = self.inserter.resolve(old_condition); let one = FieldElement::one(); + println!("block: {block}"); + println!("then_block: {then_block}"); + dbg!(self.store_values.clone()); let then_branch = - self.inline_branch(block, then_block, old_condition, then_condition, one); + self.inline_branch(block, then_block, old_condition, then_condition, one, outer_block_stores.clone()); + // dbg!(self.store_values.clone()); let else_condition = self.insert_instruction(Instruction::Not(then_condition), CallStack::new()); @@ -265,9 +306,11 @@ impl<'f> Context<'f> { // Make sure the else branch sees the previous values of each store // rather than any values created in the 'then' branch. self.undo_stores_in_then_branch(&then_branch); - + println!("else_block: {else_block}"); + // dbg!(self.store_values.clone()); let else_branch = - self.inline_branch(block, else_block, old_condition, else_condition, zero); + self.inline_branch(block, else_block, old_condition, else_condition, zero, outer_block_stores.clone()); + // dbg!(self.store_values.clone()); // We must remember to reset whether side effects are enabled when both branches // end, in addition to resetting the value of old_condition since it is set to @@ -282,7 +325,8 @@ impl<'f> Context<'f> { // until it is popped. This ensures we inline the full then and else branches // before continuing from the end of the conditional here where they can be merged properly. let end = self.branch_ends[&block]; - self.inline_branch_end(end, then_branch, else_branch) + println!("end: {end}"); + self.inline_branch_end(end, then_branch, else_branch, outer_block_stores) } TerminatorInstruction::Jmp { destination, arguments, call_stack: _ } => { if let Some((end_block, _)) = self.conditions.last() { @@ -292,7 +336,7 @@ impl<'f> Context<'f> { } let destination = *destination; let arguments = vecmap(arguments.clone(), |value| self.inserter.resolve(value)); - self.inline_block(destination, &arguments) + self.inline_block(destination, &arguments, outer_block_stores) } TerminatorInstruction::Return { return_values } => { let return_values = @@ -383,6 +427,7 @@ impl<'f> Context<'f> { else_condition: ValueId, then_value: ValueId, else_value: ValueId, + outer_block_stores: HashMap, ) -> ValueId { match self.inserter.function.dfg.type_of_value(then_value) { Type::Numeric(_) => { @@ -395,7 +440,7 @@ impl<'f> Context<'f> { } typ @ Type::Slice(_) => { dbg!("merging slice values"); - self.merge_slice_values(typ, then_condition, else_condition, then_value, else_value) + self.merge_slice_values(typ, then_condition, else_condition, then_value, else_value, outer_block_stores) } Type::Reference => panic!("Cannot return references from an if expression"), Type::Function => panic!("Cannot return functions from an if expression"), @@ -409,6 +454,7 @@ impl<'f> Context<'f> { else_condition: ValueId, then_value_id: ValueId, else_value_id: ValueId, + outer_block_stores: HashMap, ) -> ValueId { let mut merged = im::Vector::new(); @@ -419,84 +465,12 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); - dbg!(then_value.clone()); - dbg!(else_value.clone()); - - let len = match then_value { - Value::Array { array, .. } => array.len(), - Value::Instruction { instruction, .. } => { - match self.inserter.function.dfg[instruction] { - Instruction::ArraySet { array: _, index: _, value: _, length } => { - dbg!("got array set then"); - - let length = length.expect("ICE: array set on a slice must have a length"); + // dbg!(then_value.clone()); + // dbg!(else_value.clone()); - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - let length_instr = &self.inserter.function.dfg[*instruction]; - let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - dbg!(&self.inserter.function.dfg[x]); - x - } - _ => length, - }; - let len = match &self.inserter.function.dfg[length] { - Value::NumericConstant { constant, .. } => { - constant.to_u128() as usize - } - _ => unreachable!("ahh expected numeric constants but got {:?}", &self.inserter.function.dfg[length]), - }; - len - } - _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), - } - } - _ => panic!("Expected array value"), - }; + let len = self.get_slice_length(&then_value); + let else_len = self.get_slice_length(&else_value); dbg!(len); - - let else_len = match else_value { - Value::Array { array, .. } => array.len(), - Value::Instruction { instruction, .. } => { - match self.inserter.function.dfg[instruction] { - Instruction::ArraySet { array: _, index: _, value: _, length } => { - dbg!("got array set else"); - let length = length.expect("ICE: array set on a slice must have a length"); - dbg!(&self.inserter.function.dfg[length]); - - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - let length_instr = &self.inserter.function.dfg[*instruction]; - let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - dbg!(&self.inserter.function.dfg[x]); - x - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - len.to_u128() as usize - } - Instruction::Load { address } => { - // This match is all for debugging - println!("LOAD address: {address}"); - let resolved_address = self.inserter.function.dfg.resolve(address); - println!("resolved address: {resolved_address}"); - dbg!(&self.inserter.function.dfg[address]); - match &self.inserter.function.dfg[address] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - } - _ => panic!("ahh expected instr"), - } - let res = self.inserter.function.dfg.instruction_results(instruction).first().expect("expected a result"); - dbg!(&self.inserter.function.dfg[*res]); - panic!("ahhh got load") - } - _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[instruction]), - } - } - _ => panic!("Expected array value"), - }; dbg!(else_len); let len = len.max(else_len); @@ -528,6 +502,7 @@ impl<'f> Context<'f> { else_condition, then_element, else_element, + outer_block_stores.clone(), )); } } @@ -535,6 +510,114 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } + fn get_slice_length( + &mut self, + slice: &Value, + ) -> usize { + let len = match slice { + Value::Array { array, .. } => array.len(), + Value::Instruction { instruction, .. } => { + match &self.inserter.function.dfg[*instruction] { + Instruction::ArraySet { array: _, index: _, value: _, length } => { + dbg!("got array set else"); + let length = length.expect("ICE: array set on a slice must have a length"); + dbg!(&self.inserter.function.dfg[length]); + + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + let length_instr = &self.inserter.function.dfg[*instruction]; + let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + dbg!(&self.inserter.function.dfg[x]); + x + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + len.to_u128() as usize + } + Instruction::Load { address } => { + println!("LOAD address: {address}"); + dbg!(&self.inserter.function.dfg[*address]); + match &self.inserter.function.dfg[*address] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + } + _ => panic!("ahh expected instr"), + } + // dbg!(outer_block_stores.clone()); + dbg!(self.outer_block_stores.clone()); + + let context_store = self.outer_block_stores.get(&address).expect("ICE: load in merger should have store from outer block"); + + let len = match &self.inserter.function.dfg[context_store.new_value] { + Value::Array { array, .. } => array.len(), + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + dbg!("got array set else"); + match &self.inserter.function.dfg[*instruction] { + Instruction::ArraySet { array: _, index: _, value: _, length } => { + dbg!("got array set else"); + let length = length.expect("ICE: array set on a slice must have a length"); + dbg!(&self.inserter.function.dfg[length]); + + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + let length_instr = &self.inserter.function.dfg[*instruction]; + let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + dbg!(&self.inserter.function.dfg[x]); + x + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + len.to_u128() as usize + } + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + dbg!(len); + len.to_u128() as usize + 1 + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => panic!("ahhh got {:?}", &self.inserter.function.dfg[*instruction]), + } + } + _ => unreachable!("expected array but got {:?}", &self.inserter.function.dfg[context_store.new_value]), + }; + dbg!(len); + len + } + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + dbg!(len); + len.to_u128() as usize + 1 + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[*instruction]), + } + } + _ => panic!("Expected array value"), + }; + len + } + /// Given an if expression that returns an array: `if c { array1 } else { array2 }`, /// this function will recursively merge array1 and array2 into a single resulting array /// by creating a new array containing the result of self.merge_values for each element. @@ -573,6 +656,7 @@ impl<'f> Context<'f> { else_condition, then_element, else_element, + HashMap::new(), )); } } @@ -652,6 +736,7 @@ impl<'f> Context<'f> { old_condition: ValueId, new_condition: ValueId, condition_value: FieldElement, + outer_block_stores: HashMap, ) -> Branch { if destination == self.branch_ends[&jmpif_block] { // If the branch destination is the same as the end of the branch, this must be the @@ -682,13 +767,14 @@ impl<'f> Context<'f> { self.inserter.map_value(old_condition, known_value); } - let final_block = self.inline_block(destination, &[]); + let final_block = self.inline_block(destination, &[], outer_block_stores); self.conditions.pop(); + // dbg!(self.store_values.clone()); let stores_in_branch = std::mem::replace(&mut self.store_values, old_stores); let local_allocations = std::mem::replace(&mut self.local_allocations, old_allocations); - + // dbg!(stores_in_branch.clone()); Branch { condition: new_condition, last_block: final_block, @@ -712,6 +798,7 @@ impl<'f> Context<'f> { destination: BasicBlockId, then_branch: Branch, else_branch: Branch, + outer_block_stores: HashMap, ) -> BasicBlockId { assert_eq!(self.cfg.predecessors(destination).len(), 2); @@ -730,13 +817,17 @@ impl<'f> Context<'f> { // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { - self.merge_values(then_branch.condition, else_branch.condition, then_arg, else_arg) + dbg!(&self.inserter.function.dfg[then_arg]); + dbg!(&self.inserter.function.dfg[else_arg]); + self.merge_values(then_branch.condition, else_branch.condition, then_arg, else_arg, outer_block_stores.clone()) }); + println!("then_branch.last_block: {}", then_branch.last_block); + println!("else_branch.last_block: {}", else_branch.last_block); - self.merge_stores(then_branch, else_branch); - + self.merge_stores(then_branch, else_branch, outer_block_stores.clone()); + dbg!(destination); // insert merge instruction - self.inline_block(destination, &args) + self.inline_block(destination, &args, outer_block_stores) } /// Merge any store instructions found in each branch. @@ -744,27 +835,42 @@ impl<'f> Context<'f> { /// This function relies on the 'then' branch being merged before the 'else' branch of a jmpif /// instruction. If this ordering is changed, the ordering that store values are merged within /// this function also needs to be changed to reflect that. - fn merge_stores(&mut self, then_branch: Branch, else_branch: Branch) { + fn merge_stores( + &mut self, + then_branch: Branch, + else_branch: Branch, + outer_block_stores: HashMap, + ) { // Address -> (then_value, else_value, value_before_the_if) let mut new_map = BTreeMap::new(); + dbg!(then_branch.store_values.clone()); for (address, store) in then_branch.store_values { new_map.insert(address, (store.new_value, store.old_value, store.old_value)); } + dbg!(else_branch.store_values.clone()); for (address, store) in else_branch.store_values { if let Some(entry) = new_map.get_mut(&address) { + let n = store.new_value; + println!("store.new_value: {n}"); entry.1 = store.new_value; } else { new_map.insert(address, (store.old_value, store.new_value, store.old_value)); } } + // for (address, store) in outer_block_stores { + + // } + let then_condition = then_branch.condition; let else_condition = else_branch.condition; for (address, (then_case, else_case, old_value)) in new_map { - let value = self.merge_values(then_condition, else_condition, then_case, else_case); + // dbg!(&self.inserter.function.dfg[then_case]); + // dbg!(&self.inserter.function.dfg[else_case]); + let value = self.merge_values(then_condition, else_condition, then_case, else_case, outer_block_stores.clone()); self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); if let Some(store) = self.store_values.get_mut(&address) { @@ -780,9 +886,12 @@ impl<'f> Context<'f> { if let Some(store_value) = self.store_values.get_mut(&address) { store_value.new_value = new_value; } else { + // println!("remember STORE address: {address}"); let load = Instruction::Load { address }; 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(); + // println!("remember old_value: {old_value}"); + // println!("remember new_value: {new_value}"); self.store_values.insert(address, Store { old_value, new_value }); } @@ -797,18 +906,18 @@ impl<'f> Context<'f> { /// Expects that the `arguments` given are already translated via self.inserter.resolve. /// If they are not, it is possible some values which no longer exist, such as block /// parameters, will be kept in the program. - fn inline_block(&mut self, destination: BasicBlockId, arguments: &[ValueId]) -> BasicBlockId { + fn inline_block(&mut self, destination: BasicBlockId, arguments: &[ValueId], outer_block_stores: HashMap,) -> BasicBlockId { self.inserter.remember_block_params(destination, arguments); // If this is not a separate variable, clippy gets confused and says the to_vec is // unnecessary, when removing it actually causes an aliasing/mutability error. let instructions = self.inserter.function.dfg[destination].instructions().to_vec(); - + println!("inline_block destination: {destination}"); for instruction in instructions { self.push_instruction(instruction); } - self.handle_terminator(destination) + self.handle_terminator(destination, outer_block_stores) } /// Push the given instruction to the end of the entry block of the current function. @@ -853,6 +962,7 @@ impl<'f> Context<'f> { Instruction::Constrain(eq) } Instruction::Store { address, value } => { + println!("STORE: {address}"); self.remember_store(address, value); Instruction::Store { address, value } } From cd8cf415d8be8de38697e5913e7d40d6aefcee4b Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 03:03:12 +0000 Subject: [PATCH 15/46] adding slice insert to get_slice_length in flattening, need to rework method --- .../execution_success/slices/src/main.nr | 35 ++++---- crates/noirc_evaluator/src/ssa.rs | 2 + .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 14 +-- .../src/ssa/ir/instruction/call.rs | 3 + .../src/ssa/opt/flatten_cfg.rs | 89 +++++++++++++++++-- 5 files changed, 114 insertions(+), 29 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 931eeec9b57..7052af04810 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -77,14 +77,14 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { } assert(slice.len() == 5); - dynamic_slice_index_set_if(slice, x, y); - dynamic_slice_index_set_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - dynamic_slice_index_if(slice, x, y); - dynamic_slice_index_else(slice, x, y); - - // dynamic_slice_merge_if(slice, x, y); + // dynamic_slice_index_set_if(slice, x, y); + // dynamic_slice_index_set_else(slice, x, y); + // dynamic_slice_index_set_nested_if_else_else(slice, x, y); + // dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + // dynamic_slice_index_if(slice, x, y); + // dynamic_slice_index_else(slice, x, y); + + dynamic_slice_merge_if(slice, x, y); // dynamic_slice_merge_else(slice, x, y); } @@ -145,25 +145,26 @@ fn dynamic_slice_index_else(mut slice: [Field], x: Field, y: Field) { assert(slice[4] == 2); } -// TODO: This is still broken. Need to verify whether attaching -// predicates to memory ops in ACIR solves the issue (PR #2400), but I am pretty -// sure that it is the cause. + fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { if x as u32 < 10 { assert(slice[x] == 4); slice[x] = slice[x] - 2; slice = slice.push_back(10); + assert(slice[slice.len() - 1] == 10); + + slice = slice.insert(x, 20); } else { assert(slice[x] == 0); + // slice = slice.push_back(20); } assert(slice[4] == 2); - assert(slice[slice.len() - 1] == 10); + // TODO: This is still broken. Need to verify whether attaching + // predicates to memory ops in ACIR solves the issue (PR #2400) + // assert(slice[slice.len() - 1] == 10); } -// TODO: This is still broken. Need to verify whether attaching -// predicates to memory ops in ACIR solves the issue (PR #2400), but I am pretty -// sure that it is the cause. fn dynamic_slice_merge_else(mut slice: [Field], x: Field, y: Field) { if x as u32 > 10 { assert(slice[x] == 0); @@ -174,7 +175,9 @@ fn dynamic_slice_merge_else(mut slice: [Field], x: Field, y: Field) { } assert(slice[4] == 2); - assert(slice[slice.len() - 1] == 10); + // TODO: This is still broken. Need to verify whether attaching + // predicates to memory ops in ACIR solves the issue (PR #2400) + // assert(slice[slice.len() - 1] == 10); } fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index 04170b07154..d9ef6cff5d5 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -65,6 +65,8 @@ pub(crate) fn optimize_into_acir( .print(print_ssa_passes, "After Mem2Reg:") .fold_constants() .print(print_ssa_passes, "After Constant Folding:") + .fold_constants() + .print(print_ssa_passes, "After Constant Folding:") .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/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 923628838be..cc01fd62f09 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -553,10 +553,10 @@ impl Context { _ => unreachable!("ICE: expected array or slice type"), } - dbg!("got here"); + // dbg!("got here"); let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); - dbg!(store_value.is_some()); + // dbg!(store_value.is_some()); if let Some(store) = store_value { self.array_set(instruction, array, index, store, dfg, map_array, length)?; } else { @@ -576,8 +576,8 @@ impl Context { ) -> Result<(), RuntimeError> { let array = dfg.resolve(array); let block_id = self.block_id(&array); - dbg!(block_id.0); - dbg!(self.initialized_arrays.contains(&block_id)); + // dbg!(block_id.0); + // dbg!(self.initialized_arrays.contains(&block_id)); if !self.initialized_arrays.contains(&block_id) { match &dfg[array] { Value::Array { array, .. } => { @@ -609,7 +609,7 @@ impl Context { } } } - dbg!(block_id.0); + // dbg!(block_id.0); let index_var = self.convert_value(index, dfg).into_var()?; let read = self.acir_context.read_from_memory(block_id, &index_var)?; let typ = match dfg.type_of_value(array) { @@ -667,7 +667,7 @@ impl Context { } _ => unreachable!("ICE - expected an array"), }; - dbg!(len); + // dbg!(len); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA @@ -734,7 +734,7 @@ impl Context { values: Option<&[AcirValue]>, ) -> Result<(), InternalError> { self.acir_context.initialize_array(array, len, values)?; - dbg!(array.0); + // dbg!(array.0); self.initialized_arrays.insert(array); Ok(()) } diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index bd442f31a55..d439ce660e8 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -92,6 +92,9 @@ pub(super) fn simplify_call( let new_slice = dfg.make_array(slice, element_type); SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]) + } else if let Some(slice_length) = dfg.get_numeric_constant(arguments[0]) { + // SimplifyResult::SimplifiedToMultiple() + SimplifyResult::None } else { SimplifyResult::None } diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 759ac4e4691..2e88bf30df2 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -282,7 +282,7 @@ impl<'f> Context<'f> { } } } - dbg!(outer_block_stores.clone()); + // dbg!(outer_block_stores.clone()); match self.inserter.function.dfg[block].unwrap_terminator() { TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { @@ -465,10 +465,11 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); - // dbg!(then_value.clone()); - // dbg!(else_value.clone()); - + + dbg!(then_value.clone()); let len = self.get_slice_length(&then_value); + + dbg!(else_value.clone()); let else_len = self.get_slice_length(&else_value); dbg!(len); dbg!(else_len); @@ -545,10 +546,10 @@ impl<'f> Context<'f> { _ => panic!("ahh expected instr"), } // dbg!(outer_block_stores.clone()); - dbg!(self.outer_block_stores.clone()); + // dbg!(self.outer_block_stores.clone()); let context_store = self.outer_block_stores.get(&address).expect("ICE: load in merger should have store from outer block"); - + dbg!(context_store.clone()); let len = match &self.inserter.function.dfg[context_store.new_value] { Value::Array { array, .. } => array.len(), Value::Instruction { instruction, .. } => { @@ -581,6 +582,44 @@ impl<'f> Context<'f> { dbg!(len); len.to_u128() as usize + 1 } + Intrinsic::SliceInsert => { + // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // dbg!(len); + // len.to_u128() as usize + 1 + let length = arguments[0]; + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + match &self.inserter.function.dfg[*instruction] { + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // dbg!(len); + // len.to_u128() as usize + 1 + arguments[0] + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => todo!(), + } + // let length_instr = &self.inserter.function.dfg[*instruction]; + // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + // dbg!(&self.inserter.function.dfg[x]); + // x + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + dbg!(len); + len.to_u128() as usize + 1 + } _ => todo!("have to check other intrinsics"), } } @@ -604,6 +643,44 @@ impl<'f> Context<'f> { dbg!(len); len.to_u128() as usize + 1 } + Intrinsic::SliceInsert => { + // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // dbg!(len); + // len.to_u128() as usize + 1 + let length = arguments[0]; + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + match &self.inserter.function.dfg[*instruction] { + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // dbg!(len); + // len.to_u128() as usize + 1 + arguments[0] + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => todo!(), + } + // let length_instr = &self.inserter.function.dfg[*instruction]; + // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + // dbg!(&self.inserter.function.dfg[x]); + // x + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + dbg!(len); + len.to_u128() as usize + 1 + } _ => todo!("have to check other intrinsics"), } } From 876bde8f3c9bbe60bd24db154f69817de4b38894 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 15:27:25 +0000 Subject: [PATCH 16/46] remove comment from slice push back --- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index cc01fd62f09..1b7e5087ab9 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1104,8 +1104,6 @@ impl Context { Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())]) } Intrinsic::SlicePushBack => { - dbg!(arguments.clone()); - let slice_length = self.convert_value(arguments[0], dfg).into_var()?; let slice = self.convert_value(arguments[1], dfg); let element = self.convert_value(arguments[2], dfg); @@ -1113,37 +1111,9 @@ impl Context { let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.add_var(slice_length, one)?; - // Classic array way - dbg!("making new slice"); let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; new_slice.push_back(element); - dbg!("got new slice"); - - // TODO: this complicates things in arrayget, but is it perhaps more efficient?? - // let result_block_id = self.block_id(&arguments[1]); - // let len = match slice { - // AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { - // let new_len = len + 1; - // let mut init_values = try_vecmap(0..len, |i| { - // let index = AcirValue::Var( - // self.acir_context.add_constant(FieldElement::from(i as u128)), - // AcirType::NumericType(NumericType::NativeField), - // ); - // let var = index.into_var()?; - // let read = self.acir_context.read_from_memory(block_id, &var)?; - // Ok::(AcirValue::Var(read, AcirType::NumericType(NumericType::NativeField))) - // })?; - // init_values.push(element); - // self.initialize_array(result_block_id, new_len, Some(&init_values))?; - // new_len - // } - // _ => unreachable!("ICE: expected a dynamic array but got {:?}", slice), - // }; - // dbg!(self.initialized_arrays.contains(&result_block_id)); - // let new_slice = - // AcirValue::DynamicArray(AcirDynamicArray { block_id: result_block_id, len }); - // Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), new_slice]) Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), AcirValue::Array(new_slice.into())]) } From d3dbffa60faa2a20726e4fae69176f68d26d0053 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 20:27:29 +0000 Subject: [PATCH 17/46] working dynamic slices except for insert and remove --- .../execution_success/slices/src/main.nr | 45 ++- crates/noirc_evaluator/src/errors.rs | 14 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 10 + .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 176 +++++++-- .../noirc_evaluator/src/ssa/ir/instruction.rs | 11 +- .../src/ssa/ir/instruction/call.rs | 4 +- crates/noirc_evaluator/src/ssa/ir/printer.rs | 1 - .../src/ssa/opt/flatten_cfg.rs | 346 +++++++++++++++--- crates/noirc_evaluator/src/ssa/opt/mem2reg.rs | 4 +- .../src/ssa/ssa_gen/context.rs | 9 +- .../src/monomorphization/mod.rs | 4 +- 11 files changed, 523 insertions(+), 101 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 7052af04810..663ee25bd0b 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -150,18 +150,57 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { if x as u32 < 10 { assert(slice[x] == 4); slice[x] = slice[x] - 2; + + slice = slice.push_back(10); + // Having an array set here checks whether we appropriately + // handle a slice length that is not yet resolving to a constant + // during flattening + slice[x] = 10; + assert(slice[slice.len() - 1] == 10); + assert(slice.len() == 6); + + slice = slice.push_back(10); + // TODO: test this currently broken + slice[x] = 20; + slice[x] = slice[x] + 10; + assert(slice[slice.len() - 1] == 10); + assert(slice.len() == 7); + slice = slice.push_back(10); assert(slice[slice.len() - 1] == 10); + assert(slice.len() == 8); + + slice = slice.push_front(11); + assert(slice[0] == 11); + assert(slice.len() == 9); + assert(slice[5] == 30); + + slice = slice.push_front(12); + assert(slice[0] == 12); + assert(slice.len() == 10); + assert(slice[6] == 30); - slice = slice.insert(x, 20); + let (popped_slice, last_elem) = slice.pop_back(); + assert(last_elem == 10); + assert(popped_slice.len() == 9); + + let (first_elem, rest_of_slice) = popped_slice.pop_front(); + assert(first_elem == 12); + assert(rest_of_slice.len() == 8); + + // TODO: SliceInsert is not implemented in ACIR gen + // slice = slice.insert(x, 20); + // let (remove_slice, removed_elem) = new_slice.remove(3); } else { assert(slice[x] == 0); - // slice = slice.push_back(20); + slice = slice.push_back(20); } + assert(slice.len() == 10); - assert(slice[4] == 2); // TODO: This is still broken. Need to verify whether attaching // predicates to memory ops in ACIR solves the issue (PR #2400) + // We are able to print the slice.len() and return the correct value but + // we still get an index out of bounds error // assert(slice[slice.len() - 1] == 10); } diff --git a/crates/noirc_evaluator/src/errors.rs b/crates/noirc_evaluator/src/errors.rs index 4be94148b71..32d9a6e13a4 100644 --- a/crates/noirc_evaluator/src/errors.rs +++ b/crates/noirc_evaluator/src/errors.rs @@ -94,7 +94,16 @@ impl From for FileDiagnostic { impl RuntimeError { fn into_diagnostic(self) -> Diagnostic { match self { - RuntimeError::InternalError(_) => { + RuntimeError::InternalError(error) => { + match error { + InternalError::General { message, call_stack } => { + dbg!(message); + panic!("ahhh"); + } + _ => { + dbg!(error.clone()); + } + } Diagnostic::simple_error( "Internal Consistency Evaluators Errors: \n This is likely a bug. Consider Opening an issue at https://github.com/noir-lang/noir/issues".to_owned(), @@ -104,7 +113,8 @@ impl RuntimeError { } _ => { let message = self.to_string(); - let location = self.call_stack().back().expect("Expected RuntimeError to have a location"); + let location = + self.call_stack().back().expect("Expected RuntimeError to have a location"); Diagnostic::simple_error(message, String::new(), location.span) } diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index fe31608db8d..c77cbf0f8c0 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -246,6 +246,16 @@ impl AcirContext { } } + // Return Some(constant) if the given AcirVar refers to a constant value, + // otherwise return None + pub(crate) fn get_constant(&self, var: &AcirVar) -> Option { + dbg!(self.vars[var].clone()); + match self.vars[var] { + AcirVarData::Const(field) => Some(field), + _ => None, + } + } + /// Adds a new Variable to context whose value will /// be constrained to be the negation of `var`. /// diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 1b7e5087ab9..192dafcb4c5 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -379,6 +379,18 @@ impl Context { for (result, output) in result_ids.iter().zip(outputs) { dbg!(result); dbg!(output.clone()); + match output.clone() { + AcirValue::Array(values) => { + let block_id = self.block_id(result); + dbg!(block_id.0); + let values = vecmap(values, |v| v.clone()); + + self.initialize_array(block_id, values.len(), Some(&values))?; + } + _ => { + // Do nothing + } + } self.ssa_values.insert(*result, output); } } @@ -413,7 +425,7 @@ impl Context { None, dfg, last_array_uses, - None + None, )?; } Instruction::ArraySet { array, index, value, length } => { @@ -424,7 +436,7 @@ impl Context { Some(*value), dfg, last_array_uses, - *length + *length, )?; } Instruction::Allocate => { @@ -514,7 +526,9 @@ impl Context { }; if index >= array_size { // Ignore the error if side effects are disabled. - if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) + if self + .acir_context + .is_constant_one(&self.current_side_effects_enabled_var) { dbg!("array out of bounds"); let call_stack = self.acir_context.get_call_stack(); @@ -531,7 +545,7 @@ impl Context { self.define_result(dfg, instruction, value); return Ok(()); } - + let value = match store_value { Some(store_value) => { let store_value = self.convert_value(store_value, dfg); @@ -539,7 +553,7 @@ impl Context { } None => array[index].clone(), }; - + self.define_result(dfg, instruction, value); return Ok(()); } @@ -576,8 +590,8 @@ impl Context { ) -> Result<(), RuntimeError> { let array = dfg.resolve(array); let block_id = self.block_id(&array); - // dbg!(block_id.0); - // dbg!(self.initialized_arrays.contains(&block_id)); + dbg!(block_id.0); + dbg!(self.initialized_arrays.contains(&block_id)); if !self.initialized_arrays.contains(&block_id) { match &dfg[array] { Value::Array { array, .. } => { @@ -585,27 +599,27 @@ impl Context { array.iter().map(|i| self.convert_value(*i, dfg)).collect(); self.initialize_array(block_id, array.len(), Some(&values))?; } - Value::Instruction { instruction, .. } => { - let results = dfg.instruction_results(*instruction); - dbg!(results.clone()); - dbg!(self.convert_value(results[0], dfg)); - dbg!(self.convert_value(results[1], dfg)); - match self.convert_value(results[1], dfg) { - AcirValue::Array(values) => { - let values = vecmap(values, |v| v.clone()); - dbg!(values.len()); - self.initialize_array(block_id, values.len(), Some(&values))?; - } - // TODO: return internal error - _ => unreachable!("ICE: expected array as result"), - } - } + // Value::Instruction { instruction, .. } => { + // let results = dfg.instruction_results(*instruction); + // dbg!(results.clone()); + // dbg!(self.convert_value(results[0], dfg)); + // dbg!(self.convert_value(results[1], dfg)); + // match self.convert_value(results[1], dfg) { + // AcirValue::Array(values) => { + // let values = vecmap(values, |v| v.clone()); + // dbg!(values.len()); + // self.initialize_array(block_id, values.len(), Some(&values))?; + // } + // // TODO: return internal error + // _ => unreachable!("ICE: expected array as result"), + // } + // } _ => { dbg!(&dfg[array]); return Err(RuntimeError::UnInitialized { name: "array".to_string(), call_stack: self.acir_context.get_call_stack(), - }) + }); } } } @@ -648,26 +662,33 @@ impl Context { store_value: ValueId, dfg: &DataFlowGraph, map_array: bool, - length: Option + length: Option, ) -> Result<(), InternalError> { // Fetch the internal SSA ID for the array let array = dfg.resolve(array); // Use the SSA ID to get or create its block ID let block_id = self.block_id(&array); - + dbg!(block_id.0); // Every array has a length in its type, so we fetch that from // the SSA IR. let len = match dfg.type_of_value(array) { Type::Array(_, len) => len, Type::Slice(_) => { - let length = length.expect("ICE: array set on slice must have a length associated with the call"); - let len = dfg.get_numeric_constant(length).expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + let length = length + .expect("ICE: array set on slice must have a length associated with the call"); + let length_acir_value = self.convert_value(length, dfg); + dbg!(length_acir_value.clone()); + let x = self.acir_context.get_constant(&length_acir_value.into_var()?); + dbg!(x); + let len = + x.expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + // let len = dfg.get_numeric_constant(length).expect("ICE: slice length should be fully tracked and constant by ACIR gen"); len.to_u128() as usize } _ => unreachable!("ICE - expected an array"), }; - // dbg!(len); + dbg!(len); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA @@ -687,6 +708,7 @@ impl Context { } } } + dbg!(already_initialized); // Since array_set creates a new array, we create a new block ID for this // array, unless map_array is true. In that case, we operate directly on block_id @@ -713,9 +735,10 @@ impl Context { })?; self.initialize_array(result_block_id, len, Some(&init_values))?; } - + dbg!("about to fetch index and value"); // Write the new value into the new array at the specified index let index_var = self.convert_value(index, dfg).into_var()?; + dbg!(index_var); let value_var = self.convert_value(store_value, dfg).into_var()?; self.acir_context.write_to_memory(result_block_id, &index_var, &value_var)?; @@ -1106,6 +1129,7 @@ impl Context { Intrinsic::SlicePushBack => { let slice_length = self.convert_value(arguments[0], dfg).into_var()?; let slice = self.convert_value(arguments[1], dfg); + // TODO: make sure that we have handled nested struct inputs let element = self.convert_value(arguments[2], dfg); let one = self.acir_context.add_constant(FieldElement::one()); @@ -1115,7 +1139,88 @@ impl Context { self.slice_intrinsic_input(&mut new_slice, slice)?; new_slice.push_back(element); - Ok(vec![AcirValue::Var(new_slice_length, AcirType::field()), AcirValue::Array(new_slice.into())]) + Ok(vec![ + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice.into()), + ]) + } + Intrinsic::SlicePushFront => { + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + // TODO: make sure that we have handled nested struct inputs + let element = self.convert_value(arguments[2], dfg); + + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.add_var(slice_length, one)?; + + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + new_slice.push_front(element); + + Ok(vec![ + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice.into()), + ]) + } + Intrinsic::SlicePopBack => { + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.sub_var(slice_length, one)?; + + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + let elem = new_slice + .pop_back() + .expect("There are no elements in this slice to be removed"); + + Ok(vec![ + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice.into()), + elem, + ]) + } + Intrinsic::SlicePopFront => { + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.sub_var(slice_length, one)?; + + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + let elem = new_slice + .pop_front() + .expect("There are no elements in this slice to be removed"); + + Ok(vec![ + elem, + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice.into()), + ]) + } + Intrinsic::SliceInsert => { + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + let index = self.convert_value(arguments[2], dfg).into_var()?; + let element = self.convert_value(arguments[3], dfg); + + let index = self.acir_context.get_constant(&index); + dbg!(index); + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + + // Slice insert is a little less obvious on how to implement due to the case + // of having a dynamic index + // The slice insert logic will need a more involved codegen + todo!("need to write acir gen for SliceInsert") + } + Intrinsic::SliceRemove => { + // Slice remove is a little less obvious on how to implement due to the case + // of having a dynamic index + // The slice remove logic will need a more involved codegen + todo!("need to write acir gen for SliceInsert") } _ => todo!("expected a black box function"), } @@ -1127,6 +1232,14 @@ impl Context { input: AcirValue, ) -> Result<(), RuntimeError> { match input { + AcirValue::Var(_, _) => { + old_slice.push_back(input); + } + AcirValue::Array(vars) => { + for var in vars { + self.slice_intrinsic_input(old_slice, var)?; + } + } AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { for i in 0..len { // We generate witnesses corresponding to the array values @@ -1136,7 +1249,8 @@ impl Context { ); let index_var = index.into_var()?; - let value_read_var = self.acir_context.read_from_memory(block_id, &index_var)?; + let value_read_var = + self.acir_context.read_from_memory(block_id, &index_var)?; let value_read = AcirValue::Var( value_read_var, AcirType::NumericType(NumericType::NativeField), diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction.rs b/crates/noirc_evaluator/src/ssa/ir/instruction.rs index ce94e0b06b7..45f0cbce38b 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction.rs @@ -170,7 +170,7 @@ pub(crate) enum Instruction { /// Creates a new array with the new value at the given index. All other elements are identical /// to those in the given array. This will not modify the original array. - /// + /// /// An optional length can be provided to enabling handling of dynamic slice indices ArraySet { array: ValueId, index: ValueId, value: ValueId, length: Option }, } @@ -271,9 +271,12 @@ impl Instruction { Instruction::ArrayGet { array, index } => { Instruction::ArrayGet { array: f(*array), index: f(*index) } } - Instruction::ArraySet { array, index, value, length } => { - Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value), length: length.map(f) } - } + Instruction::ArraySet { array, index, value, length } => Instruction::ArraySet { + array: f(*array), + index: f(*index), + value: f(*value), + length: length.map(f), + }, } } diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index d439ce660e8..6f04e12bdaf 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -75,7 +75,9 @@ pub(super) fn simplify_call( if let Some(length) = dfg.try_get_array_length(arguments[0]) { let length = FieldElement::from(length as u128); SimplifyResult::SimplifiedTo(dfg.make_constant(length, Type::field())) - } else if arguments.len() > 1 && matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) { + } else if arguments.len() > 1 + && matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) + { SimplifyResult::SimplifiedTo(arguments[0]) } else { SimplifyResult::None diff --git a/crates/noirc_evaluator/src/ssa/ir/printer.rs b/crates/noirc_evaluator/src/ssa/ir/printer.rs index 936bc1fa201..ff103ec925f 100644 --- a/crates/noirc_evaluator/src/ssa/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa/ir/printer.rs @@ -181,7 +181,6 @@ pub(crate) fn display_instruction( show(*value) ) } - } } } diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 2e88bf30df2..70933f22e52 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -143,7 +143,7 @@ use crate::ssa::{ dfg::{CallStack, InsertInstructionResult}, function::Function, function_inserter::FunctionInserter, - instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction, Intrinsic}, + instruction::{BinaryOp, Instruction, InstructionId, Intrinsic, TerminatorInstruction}, types::Type, value::{Value, ValueId}, }, @@ -254,7 +254,11 @@ 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, mut outer_block_stores: HashMap,) -> BasicBlockId { + fn handle_terminator( + &mut self, + block: BasicBlockId, + mut outer_block_stores: HashMap, + ) -> BasicBlockId { // let mut outer_block_stores = HashMap::new(); let instructions = self.inserter.function.dfg[block].instructions().to_vec(); // let entry_block = self.inserter.function.entry_block(); @@ -270,11 +274,10 @@ impl<'f> Context<'f> { let old_value = self.insert_instruction_with_typevars(load, load_type).first(); println!("remember old_value: {old_value}"); println!("remember new_value: {value}"); - dbg!(&self.inserter.function.dfg[value]); + // dbg!(&self.inserter.function.dfg[value]); outer_block_stores.insert(address, Store { old_value, new_value: value }); // self.outer_block_stores.insert((address, block), Store { old_value, new_value: value }); self.outer_block_stores.insert(address, Store { old_value, new_value: value }); - } _ => { // DO nothing @@ -295,8 +298,14 @@ impl<'f> Context<'f> { println!("block: {block}"); println!("then_block: {then_block}"); dbg!(self.store_values.clone()); - let then_branch = - self.inline_branch(block, then_block, old_condition, then_condition, one, outer_block_stores.clone()); + let then_branch = self.inline_branch( + block, + then_block, + old_condition, + then_condition, + one, + outer_block_stores.clone(), + ); // dbg!(self.store_values.clone()); let else_condition = @@ -308,8 +317,14 @@ impl<'f> Context<'f> { self.undo_stores_in_then_branch(&then_branch); println!("else_block: {else_block}"); // dbg!(self.store_values.clone()); - let else_branch = - self.inline_branch(block, else_block, old_condition, else_condition, zero, outer_block_stores.clone()); + let else_branch = self.inline_branch( + block, + else_block, + old_condition, + else_condition, + zero, + outer_block_stores.clone(), + ); // dbg!(self.store_values.clone()); // We must remember to reset whether side effects are enabled when both branches @@ -440,7 +455,14 @@ impl<'f> Context<'f> { } typ @ Type::Slice(_) => { dbg!("merging slice values"); - self.merge_slice_values(typ, then_condition, else_condition, then_value, else_value, outer_block_stores) + self.merge_slice_values( + typ, + then_condition, + else_condition, + then_value, + else_value, + outer_block_stores, + ) } Type::Reference => panic!("Cannot return references from an if expression"), Type::Function => panic!("Cannot return functions from an if expression"), @@ -465,12 +487,15 @@ impl<'f> Context<'f> { let then_value = self.inserter.function.dfg[then_value_id].clone(); let else_value = self.inserter.function.dfg[else_value_id].clone(); - + dbg!(then_value.clone()); - let len = self.get_slice_length(&then_value); + // let len = self.get_slice_length(&then_value); + let len = self.get_slice_length_from_value(then_value_id); dbg!(else_value.clone()); - let else_len = self.get_slice_length(&else_value); + // let else_len = self.get_slice_length(&else_value); + let else_len = self.get_slice_length_from_value(else_value_id); + dbg!(len); dbg!(else_len); @@ -511,10 +536,7 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } - fn get_slice_length( - &mut self, - slice: &Value, - ) -> usize { + fn get_slice_length(&mut self, slice: &Value) -> usize { let len = match slice { Value::Array { array, .. } => array.len(), Value::Instruction { instruction, .. } => { @@ -527,15 +549,26 @@ impl<'f> Context<'f> { let length = match &self.inserter.function.dfg[length] { Value::Instruction { instruction, .. } => { let length_instr = &self.inserter.function.dfg[*instruction]; - let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + let x = self + .insert_instruction_with_typevars( + length_instr.clone(), + Some(vec![Type::field()]), + ) + .first(); dbg!(&self.inserter.function.dfg[x]); x } _ => length, }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + let len = &self + .inserter + .function + .dfg + .get_numeric_constant(length) + .expect("ICE: length should be numeric constant at this point"); len.to_u128() as usize } + Instruction::Load { address } => { println!("LOAD address: {address}"); dbg!(&self.inserter.function.dfg[*address]); @@ -548,7 +581,10 @@ impl<'f> Context<'f> { // dbg!(outer_block_stores.clone()); // dbg!(self.outer_block_stores.clone()); - let context_store = self.outer_block_stores.get(&address).expect("ICE: load in merger should have store from outer block"); + let context_store = self + .outer_block_stores + .get(&address) + .expect("ICE: load in merger should have store from outer block"); dbg!(context_store.clone()); let len = match &self.inserter.function.dfg[context_store.new_value] { Value::Array { array, .. } => array.len(), @@ -556,15 +592,27 @@ impl<'f> Context<'f> { dbg!(&self.inserter.function.dfg[*instruction]); dbg!("got array set else"); match &self.inserter.function.dfg[*instruction] { - Instruction::ArraySet { array: _, index: _, value: _, length } => { + Instruction::ArraySet { + array: _, + index: _, + value: _, + length, + } => { dbg!("got array set else"); - let length = length.expect("ICE: array set on a slice must have a length"); + let length = length + .expect("ICE: array set on a slice must have a length"); dbg!(&self.inserter.function.dfg[length]); - + let length = match &self.inserter.function.dfg[length] { Value::Instruction { instruction, .. } => { - let length_instr = &self.inserter.function.dfg[*instruction]; - let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + let length_instr = + &self.inserter.function.dfg[*instruction]; + let x = self + .insert_instruction_with_typevars( + length_instr.clone(), + Some(vec![Type::field()]), + ) + .first(); dbg!(&self.inserter.function.dfg[x]); x } @@ -578,27 +626,77 @@ impl<'f> Context<'f> { Value::Intrinsic(intrinsic) => { match intrinsic { Intrinsic::SlicePushBack => { - let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + // dbg!(len); + // len.to_u128() as usize + 1 + let length = arguments[0]; + let length = match &self + .inserter + .function + .dfg[length] + { + Value::Instruction { + instruction, + .. + } => { + dbg!( + &self.inserter.function.dfg + [*instruction] + ); + match &self.inserter.function.dfg + [*instruction] + { + Instruction::Call { + func, + arguments, + } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + arguments[0] + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => todo!(), + } + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); dbg!(len); len.to_u128() as usize + 1 } Intrinsic::SliceInsert => { - // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); - // dbg!(len); - // len.to_u128() as usize + 1 let length = arguments[0]; - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - match &self.inserter.function.dfg[*instruction] { - Instruction::Call { func, arguments } => { + let length = match &self + .inserter + .function + .dfg[length] + { + Value::Instruction { + instruction, + .. + } => { + dbg!( + &self.inserter.function.dfg + [*instruction] + ); + match &self.inserter.function.dfg + [*instruction] + { + Instruction::Call { + func, + arguments, + } => { match &self.inserter.function.dfg[*func] { Value::Intrinsic(intrinsic) => { match intrinsic { Intrinsic::SlicePushBack => { - // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); - // dbg!(len); - // len.to_u128() as usize + 1 arguments[0] } _ => todo!("have to check other intrinsics"), @@ -609,10 +707,6 @@ impl<'f> Context<'f> { } _ => todo!(), } - // let length_instr = &self.inserter.function.dfg[*instruction]; - // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - // dbg!(&self.inserter.function.dfg[x]); - // x } _ => length, }; @@ -626,27 +720,82 @@ impl<'f> Context<'f> { _ => todo!(), } } - _ => panic!("ahhh got {:?}", &self.inserter.function.dfg[*instruction]), + _ => panic!( + "ahhh got {:?}", + &self.inserter.function.dfg[*instruction] + ), } } - _ => unreachable!("expected array but got {:?}", &self.inserter.function.dfg[context_store.new_value]), + _ => unreachable!( + "expected array but got {:?}", + &self.inserter.function.dfg[context_store.new_value] + ), }; dbg!(len); len } + Instruction::Call { func, arguments } => { match &self.inserter.function.dfg[*func] { Value::Intrinsic(intrinsic) => { match intrinsic { Intrinsic::SlicePushBack => { - let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); + let length = arguments[0]; + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + match &self.inserter.function.dfg[*instruction] { + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushBack => { + arguments[0] + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => todo!(), + } + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); + dbg!(len); + len.to_u128() as usize + 1 + } + Intrinsic::SlicePushFront => { + let length = arguments[0]; + let length = match &self.inserter.function.dfg[length] { + Value::Instruction { instruction, .. } => { + dbg!(&self.inserter.function.dfg[*instruction]); + match &self.inserter.function.dfg[*instruction] { + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => { + match intrinsic { + Intrinsic::SlicePushFront => { + arguments[0] + } + _ => todo!("have to check other intrinsics"), + } + } + _ => todo!(), + } + } + _ => todo!(), + } + } + _ => length, + }; + let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); dbg!(len); len.to_u128() as usize + 1 } Intrinsic::SliceInsert => { - // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); - // dbg!(len); - // len.to_u128() as usize + 1 let length = arguments[0]; let length = match &self.inserter.function.dfg[length] { Value::Instruction { instruction, .. } => { @@ -656,6 +805,8 @@ impl<'f> Context<'f> { match &self.inserter.function.dfg[*func] { Value::Intrinsic(intrinsic) => { match intrinsic { + // TODO: we need to make this generializable to multiple slice intrinsics being intertwined + // we probably Intrinsic::SlicePushBack => { // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); // dbg!(len); @@ -687,7 +838,11 @@ impl<'f> Context<'f> { _ => todo!(), } } - _ => unreachable!("ahh expected array set but got {:?}", self.inserter.function.dfg[*instruction]), + + _ => unreachable!( + "ahh expected array set but got {:?}", + self.inserter.function.dfg[*instruction] + ), } } _ => panic!("Expected array value"), @@ -695,6 +850,74 @@ impl<'f> Context<'f> { len } + fn get_slice_length_from_value(&mut self, value_id: ValueId) -> usize { + let len = match &self.inserter.function.dfg[value_id] { + Value::Array { array, .. } => array.len(), + Value::NumericConstant { constant, .. } => constant.to_u128() as usize, + Value::Instruction { instruction, .. } => { + match &self.inserter.function.dfg[*instruction] { + Instruction::ArraySet { array: _, index: _, value: _, length } => { + let length = length.expect("ICE: array set on a slice must have a length"); + // TODO: for some reason I had to fetch the insert the length instruction previously but now everything is + // is working without. Need to test more + // We are chilling it looks like with this case. + // It was occuring when we had an array set immediately following the first slice intrinsic + // But with the recursive call it looks to all work + // let length = match &self.inserter.function.dfg[length] { + // Value::Instruction { instruction, .. } => { + // let length_instr = &self.inserter.function.dfg[*instruction]; + // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); + // dbg!(&self.inserter.function.dfg[x]); + // x + // } + // _ => length, + // }; + let len = self.get_slice_length_from_value(length); + dbg!(len); + len + } + Instruction::Load { address } => { + println!("LOAD address: {address}"); + let context_store = self + .outer_block_stores + .get(&address) + .expect("ICE: load in merger should have store from outer block"); + dbg!(context_store.clone()); + // let new_value = &self.inserter.function.dfg[context_store.new_value]; + let len = self.get_slice_length_from_value(context_store.new_value); + dbg!(len); + len + } + Instruction::Call { func, arguments } => { + match &self.inserter.function.dfg[*func] { + Value::Intrinsic(intrinsic) => match intrinsic { + Intrinsic::SlicePushBack + | Intrinsic::SlicePushFront + | Intrinsic::SlicePopBack + | Intrinsic::SlicePopFront + | Intrinsic::SliceInsert + | Intrinsic::SliceRemove => { + let length = arguments[0]; + let len = self.get_slice_length_from_value(length); + dbg!(len); + len + } + _ => todo!("have to check other intrinsics"), + }, + _ => todo!(), + } + } + _ => unreachable!( + "ahh expected array set but got {:?}", + self.inserter.function.dfg[*instruction] + ), + } + } + _ => panic!("Expected array value, but got {:?}", value_id), + }; + len + } + /// Given an if expression that returns an array: `if c { array1 } else { array2 }`, /// this function will recursively merge array1 and array2 into a single resulting array /// by creating a new array containing the result of self.merge_values for each element. @@ -896,7 +1119,13 @@ impl<'f> Context<'f> { let args = vecmap(args, |(then_arg, else_arg)| { dbg!(&self.inserter.function.dfg[then_arg]); dbg!(&self.inserter.function.dfg[else_arg]); - self.merge_values(then_branch.condition, else_branch.condition, then_arg, else_arg, outer_block_stores.clone()) + self.merge_values( + then_branch.condition, + else_branch.condition, + then_arg, + else_arg, + outer_block_stores.clone(), + ) }); println!("then_branch.last_block: {}", then_branch.last_block); println!("else_branch.last_block: {}", else_branch.last_block); @@ -913,9 +1142,9 @@ impl<'f> Context<'f> { /// instruction. If this ordering is changed, the ordering that store values are merged within /// this function also needs to be changed to reflect that. fn merge_stores( - &mut self, - then_branch: Branch, - else_branch: Branch, + &mut self, + then_branch: Branch, + else_branch: Branch, outer_block_stores: HashMap, ) { // Address -> (then_value, else_value, value_before_the_if) @@ -947,7 +1176,13 @@ impl<'f> Context<'f> { for (address, (then_case, else_case, old_value)) in new_map { // dbg!(&self.inserter.function.dfg[then_case]); // dbg!(&self.inserter.function.dfg[else_case]); - let value = self.merge_values(then_condition, else_condition, then_case, else_case, outer_block_stores.clone()); + let value = self.merge_values( + then_condition, + else_condition, + then_case, + else_case, + outer_block_stores.clone(), + ); self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); if let Some(store) = self.store_values.get_mut(&address) { @@ -983,7 +1218,12 @@ impl<'f> Context<'f> { /// Expects that the `arguments` given are already translated via self.inserter.resolve. /// If they are not, it is possible some values which no longer exist, such as block /// parameters, will be kept in the program. - fn inline_block(&mut self, destination: BasicBlockId, arguments: &[ValueId], outer_block_stores: HashMap,) -> BasicBlockId { + fn inline_block( + &mut self, + destination: BasicBlockId, + arguments: &[ValueId], + outer_block_stores: HashMap, + ) -> BasicBlockId { self.inserter.remember_block_params(destination, arguments); // If this is not a separate variable, clippy gets confused and says the to_vec is diff --git a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs index bd1015245bf..20cbbcc44da 100644 --- a/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -55,12 +55,12 @@ //! referenced by the terminator instruction. //! //! Repeating this algorithm for each block in the function in program order should result in -//! optimizing out most known loads. However, identifying all aliases correctly has been proven +//! optimizing out most known loads. However, identifying all aliases correctly has been proven //! undecidable in general (Landi, 1992). So this pass will not always optimize out all loads //! that could theoretically be optimized out. This pass can be performed at any time in the //! SSA optimization pipeline, although it will be more successful the simpler the program's CFG is. //! This pass is currently performed several times to enable other passes - most notably being -//! performed before loop unrolling to try to allow for mutable variables used for for loop indices. +//! performed before loop unrolling to try to allow for mutable variables used for for loop indices. use std::collections::{BTreeMap, BTreeSet}; use crate::ssa::{ diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs index 0348d7ae9b8..87140e2c865 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -664,8 +664,13 @@ impl<'a> FunctionContext<'a> { LValue::SliceIndex { old_slice: slice, index, slice_lvalue, location } => { let mut slice_values = slice.into_value_list(self); - slice_values[1] = - self.assign_lvalue_index(new_value, slice_values[1], index, Some(slice_values[0]), location); + slice_values[1] = self.assign_lvalue_index( + new_value, + slice_values[1], + index, + Some(slice_values[0]), + location, + ); // The size of the slice does not change in a slice index assignment so we can reuse the same length value let new_slice = Tree::Branch(vec![slice_values[0].into(), slice_values[1].into()]); diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 2ef980176d3..fb441a1bc17 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -882,8 +882,8 @@ impl<'interner> Monomorphizer<'interner> { } } let printable_type: PrintableType = typ.into(); - let abi_as_string = - serde_json::to_string(&printable_type).expect("ICE: expected PrintableType to serialize"); + let abi_as_string = serde_json::to_string(&printable_type) + .expect("ICE: expected PrintableType to serialize"); arguments.push(ast::Expression::Literal(ast::Literal::Str(abi_as_string))); } From c44321b766c4fd0cac13734ad933bf3809edc5a0 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 20:29:24 +0000 Subject: [PATCH 18/46] remove old get_slice_length method --- .../execution_success/slices/src/main.nr | 14 +- .../src/ssa/opt/flatten_cfg.rs | 344 +----------------- 2 files changed, 10 insertions(+), 348 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 663ee25bd0b..c175b6f5b77 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -77,15 +77,15 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { } assert(slice.len() == 5); - // dynamic_slice_index_set_if(slice, x, y); - // dynamic_slice_index_set_else(slice, x, y); - // dynamic_slice_index_set_nested_if_else_else(slice, x, y); - // dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - // dynamic_slice_index_if(slice, x, y); - // dynamic_slice_index_else(slice, x, y); + dynamic_slice_index_set_if(slice, x, y); + dynamic_slice_index_set_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + dynamic_slice_index_if(slice, x, y); + dynamic_slice_index_else(slice, x, y); dynamic_slice_merge_if(slice, x, y); - // dynamic_slice_merge_else(slice, x, y); + dynamic_slice_merge_else(slice, x, y); } fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 70933f22e52..7bc8aaf671f 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -485,21 +485,12 @@ impl<'f> Context<'f> { _ => panic!("Expected slice type"), }; - let then_value = self.inserter.function.dfg[then_value_id].clone(); - let else_value = self.inserter.function.dfg[else_value_id].clone(); - - dbg!(then_value.clone()); - // let len = self.get_slice_length(&then_value); - let len = self.get_slice_length_from_value(then_value_id); - - dbg!(else_value.clone()); - // let else_len = self.get_slice_length(&else_value); + let then_len = self.get_slice_length_from_value(then_value_id); let else_len = self.get_slice_length_from_value(else_value_id); - - dbg!(len); + dbg!(then_len); dbg!(else_len); - let len = len.max(else_len); + let len = then_len.max(else_len); for i in 0..len { for (element_index, element_type) in element_types.iter().enumerate() { @@ -536,320 +527,6 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } - fn get_slice_length(&mut self, slice: &Value) -> usize { - let len = match slice { - Value::Array { array, .. } => array.len(), - Value::Instruction { instruction, .. } => { - match &self.inserter.function.dfg[*instruction] { - Instruction::ArraySet { array: _, index: _, value: _, length } => { - dbg!("got array set else"); - let length = length.expect("ICE: array set on a slice must have a length"); - dbg!(&self.inserter.function.dfg[length]); - - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - let length_instr = &self.inserter.function.dfg[*instruction]; - let x = self - .insert_instruction_with_typevars( - length_instr.clone(), - Some(vec![Type::field()]), - ) - .first(); - dbg!(&self.inserter.function.dfg[x]); - x - } - _ => length, - }; - let len = &self - .inserter - .function - .dfg - .get_numeric_constant(length) - .expect("ICE: length should be numeric constant at this point"); - len.to_u128() as usize - } - - Instruction::Load { address } => { - println!("LOAD address: {address}"); - dbg!(&self.inserter.function.dfg[*address]); - match &self.inserter.function.dfg[*address] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - } - _ => panic!("ahh expected instr"), - } - // dbg!(outer_block_stores.clone()); - // dbg!(self.outer_block_stores.clone()); - - let context_store = self - .outer_block_stores - .get(&address) - .expect("ICE: load in merger should have store from outer block"); - dbg!(context_store.clone()); - let len = match &self.inserter.function.dfg[context_store.new_value] { - Value::Array { array, .. } => array.len(), - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - dbg!("got array set else"); - match &self.inserter.function.dfg[*instruction] { - Instruction::ArraySet { - array: _, - index: _, - value: _, - length, - } => { - dbg!("got array set else"); - let length = length - .expect("ICE: array set on a slice must have a length"); - dbg!(&self.inserter.function.dfg[length]); - - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - let length_instr = - &self.inserter.function.dfg[*instruction]; - let x = self - .insert_instruction_with_typevars( - length_instr.clone(), - Some(vec![Type::field()]), - ) - .first(); - dbg!(&self.inserter.function.dfg[x]); - x - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - len.to_u128() as usize - } - Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushBack => { - // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); - // dbg!(len); - // len.to_u128() as usize + 1 - let length = arguments[0]; - let length = match &self - .inserter - .function - .dfg[length] - { - Value::Instruction { - instruction, - .. - } => { - dbg!( - &self.inserter.function.dfg - [*instruction] - ); - match &self.inserter.function.dfg - [*instruction] - { - Instruction::Call { - func, - arguments, - } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushBack => { - arguments[0] - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => todo!(), - } - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - dbg!(len); - len.to_u128() as usize + 1 - } - Intrinsic::SliceInsert => { - let length = arguments[0]; - let length = match &self - .inserter - .function - .dfg[length] - { - Value::Instruction { - instruction, - .. - } => { - dbg!( - &self.inserter.function.dfg - [*instruction] - ); - match &self.inserter.function.dfg - [*instruction] - { - Instruction::Call { - func, - arguments, - } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushBack => { - arguments[0] - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => todo!(), - } - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - dbg!(len); - len.to_u128() as usize + 1 - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => panic!( - "ahhh got {:?}", - &self.inserter.function.dfg[*instruction] - ), - } - } - _ => unreachable!( - "expected array but got {:?}", - &self.inserter.function.dfg[context_store.new_value] - ), - }; - dbg!(len); - len - } - - Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushBack => { - let length = arguments[0]; - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - match &self.inserter.function.dfg[*instruction] { - Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushBack => { - arguments[0] - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => todo!(), - } - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - dbg!(len); - len.to_u128() as usize + 1 - } - Intrinsic::SlicePushFront => { - let length = arguments[0]; - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - match &self.inserter.function.dfg[*instruction] { - Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - Intrinsic::SlicePushFront => { - arguments[0] - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => todo!(), - } - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - dbg!(len); - len.to_u128() as usize + 1 - } - Intrinsic::SliceInsert => { - let length = arguments[0]; - let length = match &self.inserter.function.dfg[length] { - Value::Instruction { instruction, .. } => { - dbg!(&self.inserter.function.dfg[*instruction]); - match &self.inserter.function.dfg[*instruction] { - Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { - Value::Intrinsic(intrinsic) => { - match intrinsic { - // TODO: we need to make this generializable to multiple slice intrinsics being intertwined - // we probably - Intrinsic::SlicePushBack => { - // let len = &self.inserter.function.dfg.get_numeric_constant(arguments[0]).expect("ICE: length should be numeric constant at this point"); - // dbg!(len); - // len.to_u128() as usize + 1 - arguments[0] - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - _ => todo!(), - } - // let length_instr = &self.inserter.function.dfg[*instruction]; - // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - // dbg!(&self.inserter.function.dfg[x]); - // x - } - _ => length, - }; - let len = &self.inserter.function.dfg.get_numeric_constant(length).expect("ICE: length should be numeric constant at this point"); - dbg!(len); - len.to_u128() as usize + 1 - } - _ => todo!("have to check other intrinsics"), - } - } - _ => todo!(), - } - } - - _ => unreachable!( - "ahh expected array set but got {:?}", - self.inserter.function.dfg[*instruction] - ), - } - } - _ => panic!("Expected array value"), - }; - len - } - fn get_slice_length_from_value(&mut self, value_id: ValueId) -> usize { let len = match &self.inserter.function.dfg[value_id] { Value::Array { array, .. } => array.len(), @@ -858,20 +535,6 @@ impl<'f> Context<'f> { match &self.inserter.function.dfg[*instruction] { Instruction::ArraySet { array: _, index: _, value: _, length } => { let length = length.expect("ICE: array set on a slice must have a length"); - // TODO: for some reason I had to fetch the insert the length instruction previously but now everything is - // is working without. Need to test more - // We are chilling it looks like with this case. - // It was occuring when we had an array set immediately following the first slice intrinsic - // But with the recursive call it looks to all work - // let length = match &self.inserter.function.dfg[length] { - // Value::Instruction { instruction, .. } => { - // let length_instr = &self.inserter.function.dfg[*instruction]; - // let x = self.insert_instruction_with_typevars(length_instr.clone(), Some(vec![Type::field()])).first(); - // dbg!(&self.inserter.function.dfg[x]); - // x - // } - // _ => length, - // }; let len = self.get_slice_length_from_value(length); dbg!(len); len @@ -883,7 +546,6 @@ impl<'f> Context<'f> { .get(&address) .expect("ICE: load in merger should have store from outer block"); dbg!(context_store.clone()); - // let new_value = &self.inserter.function.dfg[context_store.new_value]; let len = self.get_slice_length_from_value(context_store.new_value); dbg!(len); len From d7bf16902408b2653f4dca4c79d723b0b0309831 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 20:37:51 +0000 Subject: [PATCH 19/46] some cleanup --- crates/noirc_evaluator/src/ssa.rs | 2 -- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 1 - .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 25 ++++++++----------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa.rs b/crates/noirc_evaluator/src/ssa.rs index d9ef6cff5d5..04170b07154 100644 --- a/crates/noirc_evaluator/src/ssa.rs +++ b/crates/noirc_evaluator/src/ssa.rs @@ -65,8 +65,6 @@ pub(crate) fn optimize_into_acir( .print(print_ssa_passes, "After Mem2Reg:") .fold_constants() .print(print_ssa_passes, "After Constant Folding:") - .fold_constants() - .print(print_ssa_passes, "After Constant Folding:") .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/acir_gen/acir_ir/acir_variable.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index c77cbf0f8c0..240a62e2388 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -249,7 +249,6 @@ impl AcirContext { // Return Some(constant) if the given AcirVar refers to a constant value, // otherwise return None pub(crate) fn get_constant(&self, var: &AcirVar) -> Option { - dbg!(self.vars[var].clone()); match self.vars[var] { AcirVarData::Const(field) => Some(field), _ => None, diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 192dafcb4c5..1e9df5678fe 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -324,8 +324,6 @@ impl Context { ) -> Result<(), RuntimeError> { let instruction = &dfg[instruction_id]; self.acir_context.set_call_stack(dfg.get_call_stack(instruction_id)); - // println!("instruction: {instruction_id}"); - // dbg!(instruction.clone()); match instruction { Instruction::Binary(binary) => { let result_acir_var = self.convert_ssa_binary(binary, dfg)?; @@ -341,8 +339,6 @@ impl Context { } Instruction::Call { func, arguments } => { let result_ids = dfg.instruction_results(instruction_id); - let resolved_result_ids = vecmap(result_ids, |id| dfg.resolve(*id)); - dbg!(resolved_result_ids.clone()); match &dfg[*func] { Value::Function(id) => { let func = &ssa.functions[id]; @@ -377,17 +373,19 @@ impl Context { // assert_eq!(result_ids.len(), outputs.len()); for (result, output) in result_ids.iter().zip(outputs) { - dbg!(result); - dbg!(output.clone()); - match output.clone() { + match &output { + // We need to make sure we initialize arrays returned from intrinsic calls + // or else they will fail if accessed with a dynamic index AcirValue::Array(values) => { let block_id = self.block_id(result); - dbg!(block_id.0); let values = vecmap(values, |v| v.clone()); self.initialize_array(block_id, values.len(), Some(&values))?; } - _ => { + AcirValue::DynamicArray(_) => { + unreachable!("The output from an intrinsic call is expected to be a single value or an array but got {output:?}"); + } + AcirValue::Var(_, _) => { // Do nothing } } @@ -669,7 +667,7 @@ impl Context { // Use the SSA ID to get or create its block ID let block_id = self.block_id(&array); - dbg!(block_id.0); + // Every array has a length in its type, so we fetch that from // the SSA IR. let len = match dfg.type_of_value(array) { @@ -678,12 +676,9 @@ impl Context { let length = length .expect("ICE: array set on slice must have a length associated with the call"); let length_acir_value = self.convert_value(length, dfg); - dbg!(length_acir_value.clone()); - let x = self.acir_context.get_constant(&length_acir_value.into_var()?); - dbg!(x); + let len = self.acir_context.get_constant(&length_acir_value.into_var()?); let len = - x.expect("ICE: slice length should be fully tracked and constant by ACIR gen"); - // let len = dfg.get_numeric_constant(length).expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + len.expect("ICE: slice length should be fully tracked and constant by ACIR gen"); len.to_u128() as usize } _ => unreachable!("ICE - expected an array"), From b60e23c82b4703c526a5fcd241e249158fb1e34d Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 21:27:14 +0000 Subject: [PATCH 20/46] cargo clippy and fmt git status --- .../execution_success/slices/src/main.nr | 363 ++++++++---------- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 64 +-- .../src/ssa/ir/instruction/call.rs | 3 - .../src/ssa/opt/flatten_cfg.rs | 211 +++------- crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs | 7 +- .../src/hir/resolution/resolver.rs | 3 +- .../noirc_frontend/src/hir/type_check/expr.rs | 6 +- .../src/monomorphization/mod.rs | 19 +- 8 files changed, 271 insertions(+), 405 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index c175b6f5b77..8e6b43342fd 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -2,74 +2,164 @@ use dep::std::slice; use dep::std; fn main(x : Field, y : pub Field) { - // Dynamic indexing works on slice with constant length - // let mut slice = [0, 1, 2, 3, 4]; - - // Need to implement flattening on a dynamic length - // let mut slice = []; - // for i in 0..5 { - // slice = slice.push_back(i); - // } - // let mut slice = [0, 1]; - // if x != y { - // slice = slice.push_back(y); - // } else { - // slice = slice.push_back(x); - // } - // assert(slice.len() == 3); - // assert(slice[2] == 10); - - - // let mut slice = [0; 2]; - // assert(slice[0] == 0); - // assert(slice[0] != 1); - // slice[0] = x; - // assert(slice[0] == x); - - // let slice_plus_10 = slice.push_back(y); - // assert(slice_plus_10[2] == 10); - // assert(slice_plus_10[2] != 8); - // assert(slice_plus_10.len() == 3); - - // let mut new_slice = []; - // for i in 0..5 { - // new_slice = new_slice.push_back(i); - // } - // assert(new_slice.len() == 5); - - // new_slice = new_slice.push_front(20); - // assert(new_slice[0] == 20); - // assert(new_slice.len() == 6); - - // let (popped_slice, last_elem) = new_slice.pop_back(); - // assert(last_elem == 4); - // assert(popped_slice.len() == 5); - - // let (first_elem, rest_of_slice) = popped_slice.pop_front(); - // assert(first_elem == 20); - // assert(rest_of_slice.len() == 4); - - // new_slice = rest_of_slice.insert(2, 100); - // assert(new_slice[2] == 100); - // assert(new_slice[4] == 3); - // assert(new_slice.len() == 5); - - // let (remove_slice, removed_elem) = new_slice.remove(3); - // assert(removed_elem == 2); - // assert(remove_slice[3] == 3); - // assert(remove_slice.len() == 4); - - // let append = [1, 2].append([3, 4, 5]); - // assert(append.len() == 5); - // assert(append[0] == 1); - // assert(append[4] == 5); - - // regression_2083(); - // // The parameters to this function must come from witness values (inputs to main) - // regression_merge_slices(x, y); + let mut slice = [0; 2]; + assert(slice[0] == 0); + assert(slice[0] != 1); + slice[0] = x; + assert(slice[0] == x); + + let slice_plus_10 = slice.push_back(y); + assert(slice_plus_10[2] == 10); + assert(slice_plus_10[2] != 8); + assert(slice_plus_10.len() == 3); + + let mut new_slice = []; + for i in 0..5 { + new_slice = new_slice.push_back(i); + } + assert(new_slice.len() == 5); + + new_slice = new_slice.push_front(20); + assert(new_slice[0] == 20); + assert(new_slice.len() == 6); + + let (popped_slice, last_elem) = new_slice.pop_back(); + assert(last_elem == 4); + assert(popped_slice.len() == 5); + + let (first_elem, rest_of_slice) = popped_slice.pop_front(); + assert(first_elem == 20); + assert(rest_of_slice.len() == 4); + + new_slice = rest_of_slice.insert(2, 100); + assert(new_slice[2] == 100); + assert(new_slice[4] == 3); + assert(new_slice.len() == 5); + + let (remove_slice, removed_elem) = new_slice.remove(3); + assert(removed_elem == 2); + assert(remove_slice[3] == 3); + assert(remove_slice.len() == 4); + + let append = [1, 2].append([3, 4, 5]); + assert(append.len() == 5); + assert(append[0] == 1); + assert(append[4] == 5); + + regression_2083(); + // The parameters to this function must come from witness values (inputs to main) + regression_merge_slices(x, y); regression_dynamic_slice_index(x - 1, x - 4); } +// Ensure that slices of struct/tuple values work. +fn regression_2083() { + let y = [(1, 2)]; + let y = y.push_back((3, 4)); // [(1, 2), (3, 4)] + let y = y.push_back((5, 6)); // [(1, 2), (3, 4), (5, 6)] + assert(y[2].1 == 6); + + let y = y.push_front((10, 11)); // [(10, 11), (1, 2), (3, 4), (5, 6)] + let y = y.push_front((12, 13)); // [(12, 13), (10, 11), (1, 2), (3, 4), (5, 6)] + + assert(y[1].0 == 10); + + let y = y.insert(1, (55, 56)); // [(12, 13), (55, 56), (10, 11), (1, 2), (3, 4), (5, 6)] + assert(y[0].1 == 13); + assert(y[1].1 == 56); + assert(y[2].0 == 10); + + let (y, x) = y.remove(2); // [(12, 13), (55, 56), (1, 2), (3, 4), (5, 6)] + assert(y[2].0 == 1); + assert(x.0 == 10); + assert(x.1 == 11); + + let (x, y) = y.pop_front(); // [(55, 56), (1, 2), (3, 4), (5, 6)] + assert(y[0].0 == 55); + assert(x.0 == 12); + assert(x.1 == 13); + + let (y, x) = y.pop_back(); // [(55, 56), (1, 2), (3, 4)] + assert(y.len() == 3); + assert(x.0 == 5); + assert(x.1 == 6); +} + +// The parameters to this function must come from witness values (inputs to main) +fn regression_merge_slices(x: Field, y: Field) { + merge_slices_if(x, y); + merge_slices_else(x); +} + +fn merge_slices_if(x: Field, y: Field) { + let slice = merge_slices_return(x, y); + assert(slice[2] == 10); + assert(slice.len() == 3); + + let slice = merge_slices_mutate(x, y); + assert(slice[3] == 5); + assert(slice.len() == 4); + + let slice = merge_slices_mutate_in_loop(x, y); + assert(slice[6] == 4); + assert(slice.len() == 7); +} + +fn merge_slices_else(x: Field) { + let slice = merge_slices_return(x, 5); + assert(slice[0] == 0); + assert(slice[1] == 0); + assert(slice.len() == 2); + + let slice = merge_slices_mutate(x, 5); + assert(slice[2] == 5); + assert(slice.len() == 3); + + let slice = merge_slices_mutate_in_loop(x, 5); + assert(slice[2] == 5); + assert(slice.len() == 3); +} + +// Test returning a merged slice without a mutation +fn merge_slices_return(x: Field, y: Field) -> [Field] { + let slice = [0; 2]; + if x != y { + if x != 20 { + slice.push_back(y) + } else { + slice + } + } else { + slice + } +} + +// Test mutating a slice inside of an if statement +fn merge_slices_mutate(x: Field, y: Field) -> [Field] { + let mut slice = [0; 2]; + if x != y { + slice = slice.push_back(y); + slice = slice.push_back(x); + } else { + slice = slice.push_back(x); + } + slice +} + +// Test mutating a slice inside of a loop in an if statement +fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] { + let mut slice = [0; 2]; + if x != y { + for i in 0..5 { + slice = slice.push_back(i); + } + } else { + slice = slice.push_back(x); + } + slice +} + + fn regression_dynamic_slice_index(x: Field, y: Field) { let mut slice = []; for i in 0..5 { @@ -81,11 +171,11 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { dynamic_slice_index_set_else(slice, x, y); dynamic_slice_index_set_nested_if_else_else(slice, x, y); dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - dynamic_slice_index_if(slice, x, y); - dynamic_slice_index_else(slice, x, y); + dynamic_slice_index_if(slice, x); + dynamic_slice_index_else(slice, x); - dynamic_slice_merge_if(slice, x, y); - dynamic_slice_merge_else(slice, x, y); + dynamic_slice_merge_if(slice, x); + dynamic_slice_merge_else(slice, x); } fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { @@ -123,7 +213,7 @@ fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { // This tests the case of missing a store instruction in the else branch // of merging slices -fn dynamic_slice_index_if(mut slice: [Field], x: Field, y: Field) { +fn dynamic_slice_index_if(mut slice: [Field], x: Field) { if x as u32 < 10 { assert(slice[x] == 4); slice[x] = slice[x] - 2; @@ -135,7 +225,7 @@ fn dynamic_slice_index_if(mut slice: [Field], x: Field, y: Field) { // This tests the case of missing a store instruction in the then branch // of merging slices -fn dynamic_slice_index_else(mut slice: [Field], x: Field, y: Field) { +fn dynamic_slice_index_else(mut slice: [Field], x: Field) { if x as u32 > 10 { assert(slice[x] == 0); } else { @@ -146,7 +236,7 @@ fn dynamic_slice_index_else(mut slice: [Field], x: Field, y: Field) { } -fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { +fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { if x as u32 < 10 { assert(slice[x] == 4); slice[x] = slice[x] - 2; @@ -159,43 +249,35 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { assert(slice[slice.len() - 1] == 10); assert(slice.len() == 6); - slice = slice.push_back(10); - // TODO: test this currently broken slice[x] = 20; slice[x] = slice[x] + 10; - assert(slice[slice.len() - 1] == 10); - assert(slice.len() == 7); - - slice = slice.push_back(10); - assert(slice[slice.len() - 1] == 10); - assert(slice.len() == 8); slice = slice.push_front(11); assert(slice[0] == 11); - assert(slice.len() == 9); + assert(slice.len() == 7); assert(slice[5] == 30); slice = slice.push_front(12); assert(slice[0] == 12); - assert(slice.len() == 10); + assert(slice.len() == 8); assert(slice[6] == 30); let (popped_slice, last_elem) = slice.pop_back(); assert(last_elem == 10); - assert(popped_slice.len() == 9); + assert(popped_slice.len() == 7); let (first_elem, rest_of_slice) = popped_slice.pop_front(); assert(first_elem == 12); - assert(rest_of_slice.len() == 8); + assert(rest_of_slice.len() == 6); - // TODO: SliceInsert is not implemented in ACIR gen + // TODO: SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen // slice = slice.insert(x, 20); // let (remove_slice, removed_elem) = new_slice.remove(3); } else { assert(slice[x] == 0); slice = slice.push_back(20); } - assert(slice.len() == 10); + assert(slice.len() == 8); // TODO: This is still broken. Need to verify whether attaching // predicates to memory ops in ACIR solves the issue (PR #2400) @@ -204,7 +286,7 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field, y: Field) { // assert(slice[slice.len() - 1] == 10); } -fn dynamic_slice_merge_else(mut slice: [Field], x: Field, y: Field) { +fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { if x as u32 > 10 { assert(slice[x] == 0); } else { @@ -266,110 +348,3 @@ fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Fi } assert(slice[4] == 5); } - -// Ensure that slices of struct/tuple values work. -fn regression_2083() { - let y = [(1, 2)]; - let y = y.push_back((3, 4)); // [(1, 2), (3, 4)] - let y = y.push_back((5, 6)); // [(1, 2), (3, 4), (5, 6)] - assert(y[2].1 == 6); - - let y = y.push_front((10, 11)); // [(10, 11), (1, 2), (3, 4), (5, 6)] - let y = y.push_front((12, 13)); // [(12, 13), (10, 11), (1, 2), (3, 4), (5, 6)] - - assert(y[1].0 == 10); - - let y = y.insert(1, (55, 56)); // [(12, 13), (55, 56), (10, 11), (1, 2), (3, 4), (5, 6)] - assert(y[0].1 == 13); - assert(y[1].1 == 56); - assert(y[2].0 == 10); - - let (y, x) = y.remove(2); // [(12, 13), (55, 56), (1, 2), (3, 4), (5, 6)] - assert(y[2].0 == 1); - assert(x.0 == 10); - assert(x.1 == 11); - - let (x, y) = y.pop_front(); // [(55, 56), (1, 2), (3, 4), (5, 6)] - assert(y[0].0 == 55); - assert(x.0 == 12); - assert(x.1 == 13); - - let (y, x) = y.pop_back(); // [(55, 56), (1, 2), (3, 4)] - assert(y.len() == 3); - assert(x.0 == 5); - assert(x.1 == 6); -} - -// The parameters to this function must come from witness values (inputs to main) -fn regression_merge_slices(x: Field, y: Field) { - merge_slices_if(x, y); - merge_slices_else(x); -} - -fn merge_slices_if(x: Field, y: Field) { - let slice = merge_slices_return(x, y); - assert(slice[2] == 10); - assert(slice.len() == 3); - - let slice = merge_slices_mutate(x, y); - assert(slice[3] == 5); - assert(slice.len() == 4); - - let slice = merge_slices_mutate_in_loop(x, y); - assert(slice[6] == 4); - assert(slice.len() == 7); -} - -fn merge_slices_else(x: Field) { - let slice = merge_slices_return(x, 5); - assert(slice[0] == 0); - assert(slice[1] == 0); - assert(slice.len() == 2); - - let slice = merge_slices_mutate(x, 5); - assert(slice[2] == 5); - assert(slice.len() == 3); - - let slice = merge_slices_mutate_in_loop(x, 5); - assert(slice[2] == 5); - assert(slice.len() == 3); -} - -// Test returning a merged slice without a mutation -fn merge_slices_return(x: Field, y: Field) -> [Field] { - let slice = [0; 2]; - if x != y { - if x != 20 { - slice.push_back(y) - } else { - slice - } - } else { - slice - } -} - -// Test mutating a slice inside of an if statement -fn merge_slices_mutate(x: Field, y: Field) -> [Field] { - let mut slice = [0; 2]; - if x != y { - slice = slice.push_back(y); - slice = slice.push_back(x); - } else { - slice = slice.push_back(x); - } - slice -} - -// Test mutating a slice inside of a loop in an if statement -fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] { - let mut slice = [0; 2]; - if x != y { - for i in 0..5 { - slice = slice.push_back(i); - } - } else { - slice = slice.push_back(x); - } - slice -} diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 1e9df5678fe..acc8645f541 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -507,9 +507,7 @@ impl Context { })) } AcirValue::Array(array) => { - dbg!("got here"); if let Some(index_const) = index_const { - dbg!(index_const); let array_size = array.len(); let index = match index_const.try_to_u64() { Some(index_const) => index_const as usize, @@ -528,7 +526,6 @@ impl Context { .acir_context .is_constant_one(&self.current_side_effects_enabled_var) { - dbg!("array out of bounds"); let call_stack = self.acir_context.get_call_stack(); return Err(RuntimeError::IndexOutOfBounds { index, @@ -538,7 +535,6 @@ impl Context { } let result_type = dfg.type_of_value(dfg.instruction_results(instruction)[0]); - dbg!("about to create default value"); let value = self.create_default_value(&result_type)?; self.define_result(dfg, instruction, value); return Ok(()); @@ -565,10 +561,8 @@ impl Context { _ => unreachable!("ICE: expected array or slice type"), } - // dbg!("got here"); let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); - // dbg!(store_value.is_some()); if let Some(store) = store_value { self.array_set(instruction, array, index, store, dfg, map_array, length)?; } else { @@ -588,8 +582,6 @@ impl Context { ) -> Result<(), RuntimeError> { let array = dfg.resolve(array); let block_id = self.block_id(&array); - dbg!(block_id.0); - dbg!(self.initialized_arrays.contains(&block_id)); if !self.initialized_arrays.contains(&block_id) { match &dfg[array] { Value::Array { array, .. } => { @@ -597,23 +589,7 @@ impl Context { array.iter().map(|i| self.convert_value(*i, dfg)).collect(); self.initialize_array(block_id, array.len(), Some(&values))?; } - // Value::Instruction { instruction, .. } => { - // let results = dfg.instruction_results(*instruction); - // dbg!(results.clone()); - // dbg!(self.convert_value(results[0], dfg)); - // dbg!(self.convert_value(results[1], dfg)); - // match self.convert_value(results[1], dfg) { - // AcirValue::Array(values) => { - // let values = vecmap(values, |v| v.clone()); - // dbg!(values.len()); - // self.initialize_array(block_id, values.len(), Some(&values))?; - // } - // // TODO: return internal error - // _ => unreachable!("ICE: expected array as result"), - // } - // } _ => { - dbg!(&dfg[array]); return Err(RuntimeError::UnInitialized { name: "array".to_string(), call_stack: self.acir_context.get_call_stack(), @@ -621,7 +597,7 @@ impl Context { } } } - // dbg!(block_id.0); + let index_var = self.convert_value(index, dfg).into_var()?; let read = self.acir_context.read_from_memory(block_id, &index_var)?; let typ = match dfg.type_of_value(array) { @@ -677,13 +653,12 @@ impl Context { .expect("ICE: array set on slice must have a length associated with the call"); let length_acir_value = self.convert_value(length, dfg); let len = self.acir_context.get_constant(&length_acir_value.into_var()?); - let len = - len.expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + let len = len + .expect("ICE: slice length should be fully tracked and constant by ACIR gen"); len.to_u128() as usize } _ => unreachable!("ICE - expected an array"), }; - dbg!(len); // Check if the array has already been initialized in ACIR gen // if not, we initialize it using the values from SSA @@ -703,7 +678,6 @@ impl Context { } } } - dbg!(already_initialized); // Since array_set creates a new array, we create a new block ID for this // array, unless map_array is true. In that case, we operate directly on block_id @@ -730,10 +704,9 @@ impl Context { })?; self.initialize_array(result_block_id, len, Some(&init_values))?; } - dbg!("about to fetch index and value"); + // Write the new value into the new array at the specified index let index_var = self.convert_value(index, dfg).into_var()?; - dbg!(index_var); let value_var = self.convert_value(store_value, dfg).into_var()?; self.acir_context.write_to_memory(result_block_id, &index_var, &value_var)?; @@ -752,7 +725,6 @@ impl Context { values: Option<&[AcirValue]>, ) -> Result<(), InternalError> { self.acir_context.initialize_array(array, len, values)?; - // dbg!(array.0); self.initialized_arrays.insert(array); Ok(()) } @@ -1112,13 +1084,11 @@ impl Context { Ok(Self::convert_vars_to_values(out_vars, dfg, result_ids)) } Intrinsic::ArrayLen => { - dbg!("got here"); let len = match self.convert_value(arguments[0], dfg) { AcirValue::Var(_, _) => unreachable!("Non-array passed to array.len() method"), AcirValue::Array(values) => (values.len() as u128).into(), AcirValue::DynamicArray(array) => (array.len as u128).into(), }; - dbg!(len); Ok(vec![AcirValue::Var(self.acir_context.add_constant(len), AcirType::field())]) } Intrinsic::SlicePushBack => { @@ -1136,7 +1106,7 @@ impl Context { Ok(vec![ AcirValue::Var(new_slice_length, AcirType::field()), - AcirValue::Array(new_slice.into()), + AcirValue::Array(new_slice), ]) } Intrinsic::SlicePushFront => { @@ -1154,7 +1124,7 @@ impl Context { Ok(vec![ AcirValue::Var(new_slice_length, AcirType::field()), - AcirValue::Array(new_slice.into()), + AcirValue::Array(new_slice), ]) } Intrinsic::SlicePopBack => { @@ -1172,7 +1142,7 @@ impl Context { Ok(vec![ AcirValue::Var(new_slice_length, AcirType::field()), - AcirValue::Array(new_slice.into()), + AcirValue::Array(new_slice), elem, ]) } @@ -1192,19 +1162,19 @@ impl Context { Ok(vec![ elem, AcirValue::Var(new_slice_length, AcirType::field()), - AcirValue::Array(new_slice.into()), + AcirValue::Array(new_slice), ]) } Intrinsic::SliceInsert => { - let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - let slice = self.convert_value(arguments[1], dfg); - let index = self.convert_value(arguments[2], dfg).into_var()?; - let element = self.convert_value(arguments[3], dfg); + // TODO: allow slice insert with a constant index + // let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + // let slice = self.convert_value(arguments[1], dfg); + // let index = self.convert_value(arguments[2], dfg).into_var()?; + // let element = self.convert_value(arguments[3], dfg); - let index = self.acir_context.get_constant(&index); - dbg!(index); - let mut new_slice = Vector::new(); - self.slice_intrinsic_input(&mut new_slice, slice)?; + // let index = self.acir_context.get_constant(&index); + // let mut new_slice = Vector::new(); + // self.slice_intrinsic_input(&mut new_slice, slice)?; // Slice insert is a little less obvious on how to implement due to the case // of having a dynamic index @@ -1212,6 +1182,7 @@ impl Context { todo!("need to write acir gen for SliceInsert") } Intrinsic::SliceRemove => { + // TODO: allow slice remove with a constant index // Slice remove is a little less obvious on how to implement due to the case // of having a dynamic index // The slice remove logic will need a more involved codegen @@ -1254,7 +1225,6 @@ impl Context { old_slice.push_back(value_read); } } - _ => unreachable!("ICE: expected a dynamic array but got {:?}", input), } Ok(()) } diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index 6f04e12bdaf..1e2e70213a8 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -94,9 +94,6 @@ pub(super) fn simplify_call( let new_slice = dfg.make_array(slice, element_type); SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]) - } else if let Some(slice_length) = dfg.get_numeric_constant(arguments[0]) { - // SimplifyResult::SimplifiedToMultiple() - SimplifyResult::None } else { SimplifyResult::None } diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 7bc8aaf671f..c81aade6b90 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -175,12 +175,19 @@ struct Context<'f> { branch_ends: HashMap, /// Maps an address to the old and new value of the element at that address + /// These only hold stores for one block at a time and is cleared + /// 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 preceding block - // outer_block_stores: HashMap<(ValueId, BasicBlockId), Store>, + /// 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. @@ -241,7 +248,7 @@ impl<'f> Context<'f> { fn flatten(&mut self) { // Start with following the terminator of the entry block since we don't // need to flatten the entry block into itself. - self.handle_terminator(self.inserter.function.entry_block(), HashMap::new()); + self.handle_terminator(self.inserter.function.entry_block()); } /// Check the terminator of the given block and recursively inline any blocks reachable from @@ -254,38 +261,22 @@ 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, - mut outer_block_stores: HashMap, - ) -> BasicBlockId { - // let mut outer_block_stores = HashMap::new(); + fn handle_terminator(&mut self, block: BasicBlockId) -> BasicBlockId { + // 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(); - // let entry_block = self.inserter.function.entry_block(); - println!("inline_block destination: {block}"); for instruction in instructions { - // self.push_instruction(instruction); let (instruction, _) = self.inserter.map_instruction(instruction); - match instruction { - Instruction::Store { address, value } => { - println!("remember STORE address: {address}"); - let load = Instruction::Load { address }; - let load_type = Some(vec![self.inserter.function.dfg.type_of_value(value)]); - let old_value = self.insert_instruction_with_typevars(load, load_type).first(); - println!("remember old_value: {old_value}"); - println!("remember new_value: {value}"); - // dbg!(&self.inserter.function.dfg[value]); - outer_block_stores.insert(address, Store { old_value, new_value: value }); - // self.outer_block_stores.insert((address, block), Store { old_value, new_value: value }); - self.outer_block_stores.insert(address, Store { old_value, new_value: value }); - } - _ => { - // DO nothing - // println!("Not a store instruction") - } + if let Instruction::Store { address, value } = instruction { + let load = Instruction::Load { address }; + let load_type = Some(vec![self.inserter.function.dfg.type_of_value(value)]); + let old_value = self.insert_instruction_with_typevars(load, load_type).first(); + self.outer_block_stores.insert(address, Store { old_value, new_value: value }); } } - // dbg!(outer_block_stores.clone()); match self.inserter.function.dfg[block].unwrap_terminator() { TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { @@ -295,18 +286,8 @@ impl<'f> Context<'f> { let then_condition = self.inserter.resolve(old_condition); let one = FieldElement::one(); - println!("block: {block}"); - println!("then_block: {then_block}"); - dbg!(self.store_values.clone()); - let then_branch = self.inline_branch( - block, - then_block, - old_condition, - then_condition, - one, - outer_block_stores.clone(), - ); - // dbg!(self.store_values.clone()); + let then_branch = + self.inline_branch(block, then_block, old_condition, then_condition, one); let else_condition = self.insert_instruction(Instruction::Not(then_condition), CallStack::new()); @@ -315,17 +296,9 @@ impl<'f> Context<'f> { // Make sure the else branch sees the previous values of each store // rather than any values created in the 'then' branch. self.undo_stores_in_then_branch(&then_branch); - println!("else_block: {else_block}"); - // dbg!(self.store_values.clone()); - let else_branch = self.inline_branch( - block, - else_block, - old_condition, - else_condition, - zero, - outer_block_stores.clone(), - ); - // dbg!(self.store_values.clone()); + + let else_branch = + self.inline_branch(block, else_block, old_condition, else_condition, zero); // We must remember to reset whether side effects are enabled when both branches // end, in addition to resetting the value of old_condition since it is set to @@ -340,8 +313,7 @@ impl<'f> Context<'f> { // until it is popped. This ensures we inline the full then and else branches // before continuing from the end of the conditional here where they can be merged properly. let end = self.branch_ends[&block]; - println!("end: {end}"); - self.inline_branch_end(end, then_branch, else_branch, outer_block_stores) + self.inline_branch_end(end, then_branch, else_branch) } TerminatorInstruction::Jmp { destination, arguments, call_stack: _ } => { if let Some((end_block, _)) = self.conditions.last() { @@ -351,7 +323,7 @@ impl<'f> Context<'f> { } let destination = *destination; let arguments = vecmap(arguments.clone(), |value| self.inserter.resolve(value)); - self.inline_block(destination, &arguments, outer_block_stores) + self.inline_block(destination, &arguments) } TerminatorInstruction::Return { return_values } => { let return_values = @@ -442,27 +414,16 @@ impl<'f> Context<'f> { else_condition: ValueId, then_value: ValueId, else_value: ValueId, - outer_block_stores: HashMap, ) -> ValueId { match self.inserter.function.dfg.type_of_value(then_value) { Type::Numeric(_) => { - dbg!("merging numeric values"); self.merge_numeric_values(then_condition, else_condition, then_value, else_value) } typ @ Type::Array(_, _) => { - dbg!("merging array values"); self.merge_array_values(typ, then_condition, else_condition, then_value, else_value) } typ @ Type::Slice(_) => { - dbg!("merging slice values"); - self.merge_slice_values( - typ, - then_condition, - else_condition, - then_value, - else_value, - outer_block_stores, - ) + self.merge_slice_values(typ, then_condition, else_condition, then_value, else_value) } Type::Reference => panic!("Cannot return references from an if expression"), Type::Function => panic!("Cannot return functions from an if expression"), @@ -476,7 +437,6 @@ impl<'f> Context<'f> { else_condition: ValueId, then_value_id: ValueId, else_value_id: ValueId, - outer_block_stores: HashMap, ) -> ValueId { let mut merged = im::Vector::new(); @@ -487,8 +447,6 @@ impl<'f> Context<'f> { let then_len = self.get_slice_length_from_value(then_value_id); let else_len = self.get_slice_length_from_value(else_value_id); - dbg!(then_len); - dbg!(else_len); let len = then_len.max(else_len); @@ -519,7 +477,6 @@ impl<'f> Context<'f> { else_condition, then_element, else_element, - outer_block_stores.clone(), )); } } @@ -528,30 +485,28 @@ impl<'f> Context<'f> { } fn get_slice_length_from_value(&mut self, value_id: ValueId) -> usize { - let len = match &self.inserter.function.dfg[value_id] { + let value = &self.inserter.function.dfg[value_id]; + match value { Value::Array { array, .. } => array.len(), Value::NumericConstant { constant, .. } => constant.to_u128() as usize, Value::Instruction { instruction, .. } => { - match &self.inserter.function.dfg[*instruction] { - Instruction::ArraySet { array: _, index: _, value: _, length } => { + let instruction = &self.inserter.function.dfg[*instruction]; + match instruction { + Instruction::ArraySet { length, .. } => { let length = length.expect("ICE: array set on a slice must have a length"); - let len = self.get_slice_length_from_value(length); - dbg!(len); - len + self.get_slice_length_from_value(length) } Instruction::Load { address } => { - println!("LOAD address: {address}"); let context_store = self .outer_block_stores - .get(&address) + .get(address) .expect("ICE: load in merger should have store from outer block"); - dbg!(context_store.clone()); - let len = self.get_slice_length_from_value(context_store.new_value); - dbg!(len); - len + + self.get_slice_length_from_value(context_store.new_value) } Instruction::Call { func, arguments } => { - match &self.inserter.function.dfg[*func] { + let func = &self.inserter.function.dfg[*func]; + match func { Value::Intrinsic(intrinsic) => match intrinsic { Intrinsic::SlicePushBack | Intrinsic::SlicePushFront @@ -560,24 +515,20 @@ impl<'f> Context<'f> { | Intrinsic::SliceInsert | Intrinsic::SliceRemove => { let length = arguments[0]; - let len = self.get_slice_length_from_value(length); - dbg!(len); - len + self.get_slice_length_from_value(length) + } + _ => { + unreachable!("ICE: Intrinsic not supported, got {intrinsic:?}") } - _ => todo!("have to check other intrinsics"), }, - _ => todo!(), + _ => unreachable!("ICE: Expected intrinsic value but got {func:?}"), } } - _ => unreachable!( - "ahh expected array set but got {:?}", - self.inserter.function.dfg[*instruction] - ), + _ => unreachable!("ICE: Got unexpected instruction: {instruction:?}"), } } - _ => panic!("Expected array value, but got {:?}", value_id), - }; - len + _ => unreachable!("ICE: Got unexpected value when resolving slice length {value:?}"), + } } /// Given an if expression that returns an array: `if c { array1 } else { array2 }`, @@ -618,7 +569,6 @@ impl<'f> Context<'f> { else_condition, then_element, else_element, - HashMap::new(), )); } } @@ -698,7 +648,6 @@ impl<'f> Context<'f> { old_condition: ValueId, new_condition: ValueId, condition_value: FieldElement, - outer_block_stores: HashMap, ) -> Branch { if destination == self.branch_ends[&jmpif_block] { // If the branch destination is the same as the end of the branch, this must be the @@ -729,14 +678,13 @@ impl<'f> Context<'f> { self.inserter.map_value(old_condition, known_value); } - let final_block = self.inline_block(destination, &[], outer_block_stores); + let final_block = self.inline_block(destination, &[]); self.conditions.pop(); - // dbg!(self.store_values.clone()); let stores_in_branch = std::mem::replace(&mut self.store_values, old_stores); let local_allocations = std::mem::replace(&mut self.local_allocations, old_allocations); - // dbg!(stores_in_branch.clone()); + Branch { condition: new_condition, last_block: final_block, @@ -760,7 +708,6 @@ impl<'f> Context<'f> { destination: BasicBlockId, then_branch: Branch, else_branch: Branch, - outer_block_stores: HashMap, ) -> BasicBlockId { assert_eq!(self.cfg.predecessors(destination).len(), 2); @@ -779,23 +726,13 @@ impl<'f> Context<'f> { // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { - dbg!(&self.inserter.function.dfg[then_arg]); - dbg!(&self.inserter.function.dfg[else_arg]); - self.merge_values( - then_branch.condition, - else_branch.condition, - then_arg, - else_arg, - outer_block_stores.clone(), - ) + self.merge_values(then_branch.condition, else_branch.condition, then_arg, else_arg) }); - println!("then_branch.last_block: {}", then_branch.last_block); - println!("else_branch.last_block: {}", else_branch.last_block); - self.merge_stores(then_branch, else_branch, outer_block_stores.clone()); - dbg!(destination); + self.merge_stores(then_branch, else_branch); + // insert merge instruction - self.inline_block(destination, &args, outer_block_stores) + self.inline_block(destination, &args) } /// Merge any store instructions found in each branch. @@ -803,48 +740,27 @@ impl<'f> Context<'f> { /// This function relies on the 'then' branch being merged before the 'else' branch of a jmpif /// instruction. If this ordering is changed, the ordering that store values are merged within /// this function also needs to be changed to reflect that. - fn merge_stores( - &mut self, - then_branch: Branch, - else_branch: Branch, - outer_block_stores: HashMap, - ) { + fn merge_stores(&mut self, then_branch: Branch, else_branch: Branch) { // Address -> (then_value, else_value, value_before_the_if) let mut new_map = BTreeMap::new(); - dbg!(then_branch.store_values.clone()); for (address, store) in then_branch.store_values { new_map.insert(address, (store.new_value, store.old_value, store.old_value)); } - dbg!(else_branch.store_values.clone()); for (address, store) in else_branch.store_values { if let Some(entry) = new_map.get_mut(&address) { - let n = store.new_value; - println!("store.new_value: {n}"); entry.1 = store.new_value; } else { new_map.insert(address, (store.old_value, store.new_value, store.old_value)); } } - // for (address, store) in outer_block_stores { - - // } - let then_condition = then_branch.condition; let else_condition = else_branch.condition; for (address, (then_case, else_case, old_value)) in new_map { - // dbg!(&self.inserter.function.dfg[then_case]); - // dbg!(&self.inserter.function.dfg[else_case]); - let value = self.merge_values( - then_condition, - else_condition, - then_case, - else_case, - outer_block_stores.clone(), - ); + let value = self.merge_values(then_condition, else_condition, then_case, else_case); self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); if let Some(store) = self.store_values.get_mut(&address) { @@ -860,12 +776,9 @@ impl<'f> Context<'f> { if let Some(store_value) = self.store_values.get_mut(&address) { store_value.new_value = new_value; } else { - // println!("remember STORE address: {address}"); let load = Instruction::Load { address }; 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(); - // println!("remember old_value: {old_value}"); - // println!("remember new_value: {new_value}"); self.store_values.insert(address, Store { old_value, new_value }); } @@ -880,23 +793,18 @@ impl<'f> Context<'f> { /// Expects that the `arguments` given are already translated via self.inserter.resolve. /// If they are not, it is possible some values which no longer exist, such as block /// parameters, will be kept in the program. - fn inline_block( - &mut self, - destination: BasicBlockId, - arguments: &[ValueId], - outer_block_stores: HashMap, - ) -> BasicBlockId { + fn inline_block(&mut self, destination: BasicBlockId, arguments: &[ValueId]) -> BasicBlockId { self.inserter.remember_block_params(destination, arguments); // If this is not a separate variable, clippy gets confused and says the to_vec is // unnecessary, when removing it actually causes an aliasing/mutability error. let instructions = self.inserter.function.dfg[destination].instructions().to_vec(); - println!("inline_block destination: {destination}"); + for instruction in instructions { self.push_instruction(instruction); } - self.handle_terminator(destination, outer_block_stores) + self.handle_terminator(destination) } /// Push the given instruction to the end of the entry block of the current function. @@ -941,7 +849,6 @@ impl<'f> Context<'f> { Instruction::Constrain(eq) } Instruction::Store { address, value } => { - println!("STORE: {address}"); self.remember_store(address, value); Instruction::Store { address, value } } diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 59bdf7ff0e6..3ba5c574110 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -207,7 +207,12 @@ impl<'a> FunctionContext<'a> { let rhs = rhs.into_leaf().eval(self); let typ = self.builder.type_of_value(rhs); let zero = self.builder.numeric_constant(0u128, typ); - self.insert_binary(zero, noirc_frontend::BinaryOpKind::Subtract, rhs, unary.location) + self.insert_binary( + zero, + noirc_frontend::BinaryOpKind::Subtract, + rhs, + unary.location, + ) } noirc_frontend::UnaryOp::MutableReference => { self.codegen_reference(&unary.rhs).map(|rhs| { diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 47d624e028e..bcb9cf4a9bf 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -700,8 +700,7 @@ impl<'a> Resolver<'a> { self.push_err(ResolverError::DistinctNotAllowed { ident: func.name_ident().clone() }); } - if matches!(attributes, Some(Attribute::Test { .. })) && !parameters.is_empty() - { + if matches!(attributes, Some(Attribute::Test { .. })) && !parameters.is_empty() { self.push_err(ResolverError::TestFunctionHasParameters { span: func.name_ident().span(), }); diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index 7a0c0eb2c7d..a150f198281 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -343,7 +343,11 @@ impl<'interner> TypeChecker<'interner> { rhs: method_call.object, })); self.interner.push_expr_type(&method_call.object, new_type); - self.interner.push_expr_location(method_call.object, location.span, location.file); + self.interner.push_expr_location( + method_call.object, + location.span, + location.file, + ); } } } diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 86a0ca1f20c..4a04cb7b58e 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -1187,7 +1187,11 @@ impl<'interner> Monomorphizer<'interner> { /// Implements std::unsafe::zeroed by returning an appropriate zeroed /// ast literal or collection node for the given type. Note that for functions /// there is no obvious zeroed value so this should be considered unsafe to use. - fn zeroed_value_of_type(&mut self, typ: &ast::Type, location: noirc_errors::Location) -> ast::Expression { + fn zeroed_value_of_type( + &mut self, + typ: &ast::Type, + location: noirc_errors::Location, + ) -> ast::Expression { match typ { ast::Type::Field | ast::Type::Integer(..) => { ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), typ.clone())) @@ -1218,9 +1222,9 @@ impl<'interner> Monomorphizer<'interner> { Box::new(zeroed_tuple), )) } - ast::Type::Tuple(fields) => { - ast::Expression::Tuple(vecmap(fields, |field| self.zeroed_value_of_type(field, location))) - } + ast::Type::Tuple(fields) => ast::Expression::Tuple(vecmap(fields, |field| { + self.zeroed_value_of_type(field, location) + })), ast::Type::Function(parameter_types, ret_type, env) => { self.create_zeroed_function(parameter_types, ret_type, env, location) } @@ -1234,7 +1238,12 @@ impl<'interner> Monomorphizer<'interner> { use crate::UnaryOp::MutableReference; let rhs = Box::new(self.zeroed_value_of_type(element, location)); let result_type = typ.clone(); - ast::Expression::Unary(ast::Unary { rhs, result_type, operator: MutableReference, location }) + ast::Expression::Unary(ast::Unary { + rhs, + result_type, + operator: MutableReference, + location, + }) } } } From c6720881b1d25962b6564c901c1156a41b96a7b7 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 21:28:04 +0000 Subject: [PATCH 21/46] remove commented out patch from cargo toml --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b4d0397734..998eb2bb65b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,3 @@ url = "2.2.0" wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] } wasm-bindgen-test = "0.3.33" base64 = "0.21.2" - -# [patch.crates-io] -# acvm = { path = "/mnt/user-data/maxim/acvm/acvm" } From 25d21b4c8565609900d0b5c235cd606225904f8a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 22:05:35 +0000 Subject: [PATCH 22/46] fix how we resolve length for slice intrinsics in flattening --- .../execution_success/slices/src/main.nr | 110 +++++++++--------- .../src/ssa/opt/flatten_cfg.rs | 20 ++-- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 8e6b43342fd..ae0cdbce0b3 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -2,53 +2,53 @@ use dep::std::slice; use dep::std; fn main(x : Field, y : pub Field) { - let mut slice = [0; 2]; - assert(slice[0] == 0); - assert(slice[0] != 1); - slice[0] = x; - assert(slice[0] == x); - - let slice_plus_10 = slice.push_back(y); - assert(slice_plus_10[2] == 10); - assert(slice_plus_10[2] != 8); - assert(slice_plus_10.len() == 3); - - let mut new_slice = []; - for i in 0..5 { - new_slice = new_slice.push_back(i); - } - assert(new_slice.len() == 5); - - new_slice = new_slice.push_front(20); - assert(new_slice[0] == 20); - assert(new_slice.len() == 6); - - let (popped_slice, last_elem) = new_slice.pop_back(); - assert(last_elem == 4); - assert(popped_slice.len() == 5); - - let (first_elem, rest_of_slice) = popped_slice.pop_front(); - assert(first_elem == 20); - assert(rest_of_slice.len() == 4); - - new_slice = rest_of_slice.insert(2, 100); - assert(new_slice[2] == 100); - assert(new_slice[4] == 3); - assert(new_slice.len() == 5); - - let (remove_slice, removed_elem) = new_slice.remove(3); - assert(removed_elem == 2); - assert(remove_slice[3] == 3); - assert(remove_slice.len() == 4); - - let append = [1, 2].append([3, 4, 5]); - assert(append.len() == 5); - assert(append[0] == 1); - assert(append[4] == 5); - - regression_2083(); - // The parameters to this function must come from witness values (inputs to main) - regression_merge_slices(x, y); + // let mut slice = [0; 2]; + // assert(slice[0] == 0); + // assert(slice[0] != 1); + // slice[0] = x; + // assert(slice[0] == x); + + // let slice_plus_10 = slice.push_back(y); + // assert(slice_plus_10[2] == 10); + // assert(slice_plus_10[2] != 8); + // assert(slice_plus_10.len() == 3); + + // let mut new_slice = []; + // for i in 0..5 { + // new_slice = new_slice.push_back(i); + // } + // assert(new_slice.len() == 5); + + // new_slice = new_slice.push_front(20); + // assert(new_slice[0] == 20); + // assert(new_slice.len() == 6); + + // let (popped_slice, last_elem) = new_slice.pop_back(); + // assert(last_elem == 4); + // assert(popped_slice.len() == 5); + + // let (first_elem, rest_of_slice) = popped_slice.pop_front(); + // assert(first_elem == 20); + // assert(rest_of_slice.len() == 4); + + // new_slice = rest_of_slice.insert(2, 100); + // assert(new_slice[2] == 100); + // assert(new_slice[4] == 3); + // assert(new_slice.len() == 5); + + // let (remove_slice, removed_elem) = new_slice.remove(3); + // assert(removed_elem == 2); + // assert(remove_slice[3] == 3); + // assert(remove_slice.len() == 4); + + // let append = [1, 2].append([3, 4, 5]); + // assert(append.len() == 5); + // assert(append[0] == 1); + // assert(append[4] == 5); + + // regression_2083(); + // // The parameters to this function must come from witness values (inputs to main) + // regression_merge_slices(x, y); regression_dynamic_slice_index(x - 1, x - 4); } @@ -167,14 +167,14 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { } assert(slice.len() == 5); - dynamic_slice_index_set_if(slice, x, y); - dynamic_slice_index_set_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - dynamic_slice_index_if(slice, x); - dynamic_slice_index_else(slice, x); - - dynamic_slice_merge_if(slice, x); + // dynamic_slice_index_set_if(slice, x, y); + // dynamic_slice_index_set_else(slice, x, y); + // dynamic_slice_index_set_nested_if_else_else(slice, x, y); + // dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + // dynamic_slice_index_if(slice, x); + // dynamic_slice_index_else(slice, x); + + // dynamic_slice_merge_if(slice, x); dynamic_slice_merge_else(slice, x); } diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index c81aade6b90..33449c1a737 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -445,8 +445,8 @@ impl<'f> Context<'f> { _ => panic!("Expected slice type"), }; - let then_len = self.get_slice_length_from_value(then_value_id); - let else_len = self.get_slice_length_from_value(else_value_id); + let then_len = self.get_slice_length(then_value_id); + let else_len = self.get_slice_length(else_value_id); let len = then_len.max(else_len); @@ -484,7 +484,7 @@ impl<'f> Context<'f> { self.inserter.function.dfg.make_array(merged, typ) } - fn get_slice_length_from_value(&mut self, value_id: ValueId) -> usize { + fn get_slice_length(&mut self, value_id: ValueId) -> usize { let value = &self.inserter.function.dfg[value_id]; match value { Value::Array { array, .. } => array.len(), @@ -494,7 +494,7 @@ impl<'f> Context<'f> { match instruction { Instruction::ArraySet { length, .. } => { let length = length.expect("ICE: array set on a slice must have a length"); - self.get_slice_length_from_value(length) + self.get_slice_length(length) } Instruction::Load { address } => { let context_store = self @@ -502,21 +502,19 @@ impl<'f> Context<'f> { .get(address) .expect("ICE: load in merger should have store from outer block"); - self.get_slice_length_from_value(context_store.new_value) + self.get_slice_length(context_store.new_value) } Instruction::Call { func, arguments } => { let func = &self.inserter.function.dfg[*func]; + let length = arguments[0]; match func { Value::Intrinsic(intrinsic) => match intrinsic { Intrinsic::SlicePushBack | Intrinsic::SlicePushFront - | Intrinsic::SlicePopBack + | Intrinsic::SliceInsert => self.get_slice_length(length) + 1, + Intrinsic::SlicePopBack | Intrinsic::SlicePopFront - | Intrinsic::SliceInsert - | Intrinsic::SliceRemove => { - let length = arguments[0]; - self.get_slice_length_from_value(length) - } + | Intrinsic::SliceRemove => self.get_slice_length(length) - 1, _ => { unreachable!("ICE: Intrinsic not supported, got {intrinsic:?}") } From a1adca3509c35f1aa3592af930e994b59a114a51 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 22:06:18 +0000 Subject: [PATCH 23/46] bring back full slice test --- .../execution_success/slices/src/main.nr | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index ae0cdbce0b3..9447c17bb64 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -2,53 +2,53 @@ use dep::std::slice; use dep::std; fn main(x : Field, y : pub Field) { - // let mut slice = [0; 2]; - // assert(slice[0] == 0); - // assert(slice[0] != 1); - // slice[0] = x; - // assert(slice[0] == x); - - // let slice_plus_10 = slice.push_back(y); - // assert(slice_plus_10[2] == 10); - // assert(slice_plus_10[2] != 8); - // assert(slice_plus_10.len() == 3); - - // let mut new_slice = []; - // for i in 0..5 { - // new_slice = new_slice.push_back(i); - // } - // assert(new_slice.len() == 5); - - // new_slice = new_slice.push_front(20); - // assert(new_slice[0] == 20); - // assert(new_slice.len() == 6); - - // let (popped_slice, last_elem) = new_slice.pop_back(); - // assert(last_elem == 4); - // assert(popped_slice.len() == 5); - - // let (first_elem, rest_of_slice) = popped_slice.pop_front(); - // assert(first_elem == 20); - // assert(rest_of_slice.len() == 4); - - // new_slice = rest_of_slice.insert(2, 100); - // assert(new_slice[2] == 100); - // assert(new_slice[4] == 3); - // assert(new_slice.len() == 5); - - // let (remove_slice, removed_elem) = new_slice.remove(3); - // assert(removed_elem == 2); - // assert(remove_slice[3] == 3); - // assert(remove_slice.len() == 4); - - // let append = [1, 2].append([3, 4, 5]); - // assert(append.len() == 5); - // assert(append[0] == 1); - // assert(append[4] == 5); - - // regression_2083(); - // // The parameters to this function must come from witness values (inputs to main) - // regression_merge_slices(x, y); + let mut slice = [0; 2]; + assert(slice[0] == 0); + assert(slice[0] != 1); + slice[0] = x; + assert(slice[0] == x); + + let slice_plus_10 = slice.push_back(y); + assert(slice_plus_10[2] == 10); + assert(slice_plus_10[2] != 8); + assert(slice_plus_10.len() == 3); + + let mut new_slice = []; + for i in 0..5 { + new_slice = new_slice.push_back(i); + } + assert(new_slice.len() == 5); + + new_slice = new_slice.push_front(20); + assert(new_slice[0] == 20); + assert(new_slice.len() == 6); + + let (popped_slice, last_elem) = new_slice.pop_back(); + assert(last_elem == 4); + assert(popped_slice.len() == 5); + + let (first_elem, rest_of_slice) = popped_slice.pop_front(); + assert(first_elem == 20); + assert(rest_of_slice.len() == 4); + + new_slice = rest_of_slice.insert(2, 100); + assert(new_slice[2] == 100); + assert(new_slice[4] == 3); + assert(new_slice.len() == 5); + + let (remove_slice, removed_elem) = new_slice.remove(3); + assert(removed_elem == 2); + assert(remove_slice[3] == 3); + assert(remove_slice.len() == 4); + + let append = [1, 2].append([3, 4, 5]); + assert(append.len() == 5); + assert(append[0] == 1); + assert(append[4] == 5); + + regression_2083(); + // The parameters to this function must come from witness values (inputs to main) + regression_merge_slices(x, y); regression_dynamic_slice_index(x - 1, x - 4); } From 3749b90730cb1e75a58c808e5b2ae4ab186adbfd Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 22:09:38 +0000 Subject: [PATCH 24/46] uncomment slices test --- .../tests/execution_success/slices/src/main.nr | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 9447c17bb64..e0a9535b67c 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -143,6 +143,7 @@ fn merge_slices_mutate(x: Field, y: Field) -> [Field] { } else { slice = slice.push_back(x); } + assert(slice[3] == 5); slice } @@ -167,14 +168,14 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { } assert(slice.len() == 5); - // dynamic_slice_index_set_if(slice, x, y); - // dynamic_slice_index_set_else(slice, x, y); - // dynamic_slice_index_set_nested_if_else_else(slice, x, y); - // dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - // dynamic_slice_index_if(slice, x); - // dynamic_slice_index_else(slice, x); - - // dynamic_slice_merge_if(slice, x); + dynamic_slice_index_set_if(slice, x, y); + dynamic_slice_index_set_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + dynamic_slice_index_if(slice, x); + dynamic_slice_index_else(slice, x); + + dynamic_slice_merge_if(slice, x); dynamic_slice_merge_else(slice, x); } From 3b7ef52ce02a4eef8277feae009044579e117c66 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 25 Aug 2023 22:55:54 +0000 Subject: [PATCH 25/46] remove unnecessary assert that was for testing --- .../nargo_cli/tests/execution_success/slices/src/main.nr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index e0a9535b67c..35dd8c2a00c 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -143,7 +143,6 @@ fn merge_slices_mutate(x: Field, y: Field) -> [Field] { } else { slice = slice.push_back(x); } - assert(slice[3] == 5); slice } @@ -295,11 +294,15 @@ fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { slice[x] = slice[x] - 2; slice = slice.push_back(10); } - assert(slice[4] == 2); // TODO: This is still broken. Need to verify whether attaching // predicates to memory ops in ACIR solves the issue (PR #2400) // assert(slice[slice.len() - 1] == 10); + // Even trying to access an element below trigger this error: + // Error: Index out of bounds, array has size 5, but index was 5 at 126 + // without an error location + // std::println(slice[x - 1]); + // std::println(slice[5]) } fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { From 5c30a446fefb9d8a6d5449dcdc7fa385d78a6a1d Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 16:51:18 +0000 Subject: [PATCH 26/46] fix outer block stores in flattening --- .../execution_success/slices/src/main.nr | 16 +++------ .../src/ssa/opt/flatten_cfg.rs | 34 ++++++++++--------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 35dd8c2a00c..6fc089f6b59 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -142,7 +142,7 @@ fn merge_slices_mutate(x: Field, y: Field) -> [Field] { slice = slice.push_back(x); } else { slice = slice.push_back(x); - } + }; slice } @@ -283,26 +283,20 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { // predicates to memory ops in ACIR solves the issue (PR #2400) // We are able to print the slice.len() and return the correct value but // we still get an index out of bounds error - // assert(slice[slice.len() - 1] == 10); + assert(slice[slice.len() - 1] == 10); } fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { if x as u32 > 10 { assert(slice[x] == 0); + slice[x] = 2; } else { assert(slice[x] == 4); slice[x] = slice[x] - 2; slice = slice.push_back(10); } - assert(slice[4] == 2); - // TODO: This is still broken. Need to verify whether attaching - // predicates to memory ops in ACIR solves the issue (PR #2400) - // assert(slice[slice.len() - 1] == 10); - // Even trying to access an element below trigger this error: - // Error: Index out of bounds, array has size 5, but index was 5 at 126 - // without an error location - // std::println(slice[x - 1]); - // std::println(slice[5]) + + assert(slice[slice.len() - 1] == 10); } fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 33449c1a737..e674f315eff 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -262,19 +262,21 @@ 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 { - // 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 { - let load = Instruction::Load { address }; - let load_type = Some(vec![self.inserter.function.dfg.type_of_value(value)]); - let old_value = self.insert_instruction_with_typevars(load, load_type).first(); - self.outer_block_stores.insert(address, Store { old_value, new_value: value }); + 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 { + let load = Instruction::Load { address }; + let load_type = Some(vec![self.inserter.function.dfg.type_of_value(value)]); + let old_value = self.insert_instruction_with_typevars(load, load_type).first(); + self.outer_block_stores.insert(address, Store { old_value, new_value: value }); + } } } @@ -469,7 +471,7 @@ impl<'f> Context<'f> { } }; - let then_element = get_element(then_value_id, typevars.clone(), len); + let then_element = get_element(then_value_id, typevars.clone(), then_len); let else_element = get_element(else_value_id, typevars, else_len); merged.push_back(self.merge_values( @@ -489,8 +491,8 @@ impl<'f> Context<'f> { match value { Value::Array { array, .. } => array.len(), Value::NumericConstant { constant, .. } => constant.to_u128() as usize, - Value::Instruction { instruction, .. } => { - let instruction = &self.inserter.function.dfg[*instruction]; + Value::Instruction { instruction: instruction_id, .. } => { + let instruction = &self.inserter.function.dfg[*instruction_id]; match instruction { Instruction::ArraySet { length, .. } => { let length = length.expect("ICE: array set on a slice must have a length"); From 7fb1b1137b2e53cef84861174e14cb63debb4196 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 16:57:43 +0000 Subject: [PATCH 27/46] cargo fmt --- crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index e674f315eff..29190830ac4 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -262,7 +262,9 @@ 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() { + 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. From cdfa36b7aa993691f7e0244f3be445eb920a2dd4 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 17:01:55 +0000 Subject: [PATCH 28/46] move regression_dynamic_slice_index to new project --- .../slice_dynamic_index/Nargo.toml | 7 + .../slice_dynamic_index/Prover.toml | 1 + .../slice_dynamic_index/src/main.nr | 191 ++++++++++++++++++ .../execution_success/slices/src/main.nr | 191 +----------------- 4 files changed, 200 insertions(+), 190 deletions(-) create mode 100644 crates/nargo_cli/tests/execution_success/slice_dynamic_index/Nargo.toml create mode 100644 crates/nargo_cli/tests/execution_success/slice_dynamic_index/Prover.toml create mode 100644 crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Nargo.toml b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Nargo.toml new file mode 100644 index 00000000000..08322784151 --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "slice_dynamic_index" +type = "bin" +authors = [""] +compiler_version = "0.10.3" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Prover.toml b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Prover.toml new file mode 100644 index 00000000000..0e5dfd5638d --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/Prover.toml @@ -0,0 +1 @@ +x = "5" diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr new file mode 100644 index 00000000000..169bc321f07 --- /dev/null +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr @@ -0,0 +1,191 @@ +fn main(x : Field) { + assert(x == 5); + // The parameters to this function must come from witness values (inputs to main). + // The assert on `x` before this call is to guarantee we test the expected + // conditional branches. + regression_dynamic_slice_index(x - 1, x - 4); +} + +fn regression_dynamic_slice_index(x: Field, y: Field) { + let mut slice = []; + for i in 0..5 { + slice = slice.push_back(i); + } + assert(slice.len() == 5); + + dynamic_slice_index_set_if(slice, x, y); + dynamic_slice_index_set_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_else(slice, x, y); + dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); + dynamic_slice_index_if(slice, x); + dynamic_slice_index_else(slice, x); + + dynamic_slice_merge_if(slice, x); + dynamic_slice_merge_else(slice, x); +} + +fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[1] == 0); + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice[x - 1] = slice[x]; + } else { + slice[x] = 0; + } + assert(slice[3] == 2); + assert(slice[4] == 2); +} + +fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[1] == 0); + if x as u32 > 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice[x - 1] = slice[x]; + } else { + slice[x] = 0; + } + assert(slice[4] == 0); +} + +// This tests the case of missing a store instruction in the else branch +// of merging slices +fn dynamic_slice_index_if(mut slice: [Field], x: Field) { + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + } else { + assert(slice[x] == 0); + } + assert(slice[4] == 2); +} + +// This tests the case of missing a store instruction in the then branch +// of merging slices +fn dynamic_slice_index_else(mut slice: [Field], x: Field) { + if x as u32 > 10 { + assert(slice[x] == 0); + } else { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + } + assert(slice[4] == 2); +} + + +fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { + if x as u32 < 10 { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + + slice = slice.push_back(10); + // Having an array set here checks whether we appropriately + // handle a slice length that is not yet resolving to a constant + // during flattening + slice[x] = 10; + assert(slice[slice.len() - 1] == 10); + assert(slice.len() == 6); + + slice[x] = 20; + slice[x] = slice[x] + 10; + + slice = slice.push_front(11); + assert(slice[0] == 11); + assert(slice.len() == 7); + assert(slice[5] == 30); + + slice = slice.push_front(12); + assert(slice[0] == 12); + assert(slice.len() == 8); + assert(slice[6] == 30); + + let (popped_slice, last_elem) = slice.pop_back(); + assert(last_elem == 10); + assert(popped_slice.len() == 7); + + let (first_elem, rest_of_slice) = popped_slice.pop_front(); + assert(first_elem == 12); + assert(rest_of_slice.len() == 6); + + // TODO: SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen + // slice = slice.insert(x, 20); + // let (remove_slice, removed_elem) = new_slice.remove(3); + } else { + assert(slice[x] == 0); + slice = slice.push_back(20); + } + assert(slice.len() == 8); + + assert(slice[slice.len() - 1] == 10); +} + +fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { + if x as u32 > 10 { + assert(slice[x] == 0); + slice[x] = 2; + } else { + assert(slice[x] == 4); + slice[x] = slice[x] - 2; + slice = slice.push_back(10); + } + + assert(slice[slice.len() - 1] == 10); +} + +fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 1); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[1] == 0); + if x as u32 < 10 { + slice[x] = slice[x] - 2; + if y != 1 { + slice[x] = slice[x] + 20; + } else { + if x == 5 { + // We should not hit this case + assert(slice[x] == 22); + } else { + slice[x] = 10; + } + assert(slice[4] == 10); + } + } else { + slice[x] = 0; + } + assert(slice[4] == 10); +} + +fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Field) { + assert(slice[x] == 4); + assert(slice[y] == 2); + slice[y] = 0; + assert(slice[x] == 4); + assert(slice[2] == 0); + if x as u32 < 10 { + slice[x] = slice[x] - 2; + // TODO: this panics as we have a load for the slice in flattening + if y == 1 { + slice[x] = slice[x] + 20; + } else { + if x == 4 { + slice[x] = 5; + } + assert(slice[4] == 5); + } + } else { + slice[x] = 0; + } + assert(slice[4] == 5); +} + diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 6fc089f6b59..91a5a24dc8f 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -49,7 +49,6 @@ fn main(x : Field, y : pub Field) { regression_2083(); // The parameters to this function must come from witness values (inputs to main) regression_merge_slices(x, y); - regression_dynamic_slice_index(x - 1, x - 4); } // Ensure that slices of struct/tuple values work. @@ -157,192 +156,4 @@ fn merge_slices_mutate_in_loop(x: Field, y: Field) -> [Field] { slice = slice.push_back(x); } slice -} - - -fn regression_dynamic_slice_index(x: Field, y: Field) { - let mut slice = []; - for i in 0..5 { - slice = slice.push_back(i); - } - assert(slice.len() == 5); - - dynamic_slice_index_set_if(slice, x, y); - dynamic_slice_index_set_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_else(slice, x, y); - dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); - dynamic_slice_index_if(slice, x); - dynamic_slice_index_else(slice, x); - - dynamic_slice_merge_if(slice, x); - dynamic_slice_merge_else(slice, x); -} - -fn dynamic_slice_index_set_if(mut slice: [Field], x: Field, y: Field) { - assert(slice[x] == 4); - assert(slice[y] == 1); - slice[y] = 0; - assert(slice[x] == 4); - assert(slice[1] == 0); - if x as u32 < 10 { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - slice[x - 1] = slice[x]; - } else { - slice[x] = 0; - } - assert(slice[3] == 2); - assert(slice[4] == 2); -} - -fn dynamic_slice_index_set_else(mut slice: [Field], x: Field, y: Field) { - assert(slice[x] == 4); - assert(slice[y] == 1); - slice[y] = 0; - assert(slice[x] == 4); - assert(slice[1] == 0); - if x as u32 > 10 { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - slice[x - 1] = slice[x]; - } else { - slice[x] = 0; - } - assert(slice[4] == 0); -} - -// This tests the case of missing a store instruction in the else branch -// of merging slices -fn dynamic_slice_index_if(mut slice: [Field], x: Field) { - if x as u32 < 10 { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - } else { - assert(slice[x] == 0); - } - assert(slice[4] == 2); -} - -// This tests the case of missing a store instruction in the then branch -// of merging slices -fn dynamic_slice_index_else(mut slice: [Field], x: Field) { - if x as u32 > 10 { - assert(slice[x] == 0); - } else { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - } - assert(slice[4] == 2); -} - - -fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { - if x as u32 < 10 { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - - slice = slice.push_back(10); - // Having an array set here checks whether we appropriately - // handle a slice length that is not yet resolving to a constant - // during flattening - slice[x] = 10; - assert(slice[slice.len() - 1] == 10); - assert(slice.len() == 6); - - slice[x] = 20; - slice[x] = slice[x] + 10; - - slice = slice.push_front(11); - assert(slice[0] == 11); - assert(slice.len() == 7); - assert(slice[5] == 30); - - slice = slice.push_front(12); - assert(slice[0] == 12); - assert(slice.len() == 8); - assert(slice[6] == 30); - - let (popped_slice, last_elem) = slice.pop_back(); - assert(last_elem == 10); - assert(popped_slice.len() == 7); - - let (first_elem, rest_of_slice) = popped_slice.pop_front(); - assert(first_elem == 12); - assert(rest_of_slice.len() == 6); - - // TODO: SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen - // slice = slice.insert(x, 20); - // let (remove_slice, removed_elem) = new_slice.remove(3); - } else { - assert(slice[x] == 0); - slice = slice.push_back(20); - } - assert(slice.len() == 8); - - // TODO: This is still broken. Need to verify whether attaching - // predicates to memory ops in ACIR solves the issue (PR #2400) - // We are able to print the slice.len() and return the correct value but - // we still get an index out of bounds error - assert(slice[slice.len() - 1] == 10); -} - -fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { - if x as u32 > 10 { - assert(slice[x] == 0); - slice[x] = 2; - } else { - assert(slice[x] == 4); - slice[x] = slice[x] - 2; - slice = slice.push_back(10); - } - - assert(slice[slice.len() - 1] == 10); -} - -fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { - assert(slice[x] == 4); - assert(slice[y] == 1); - slice[y] = 0; - assert(slice[x] == 4); - assert(slice[1] == 0); - if x as u32 < 10 { - slice[x] = slice[x] - 2; - if y != 1 { - slice[x] = slice[x] + 20; - } else { - if x == 5 { - // We should not hit this case - assert(slice[x] == 22); - } else { - slice[x] = 10; - } - assert(slice[4] == 10); - } - } else { - slice[x] = 0; - } - assert(slice[4] == 10); -} - -fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Field) { - assert(slice[x] == 4); - assert(slice[y] == 2); - slice[y] = 0; - assert(slice[x] == 4); - assert(slice[2] == 0); - if x as u32 < 10 { - slice[x] = slice[x] - 2; - // TODO: this panics as we have a load for the slice in flattening - if y == 1 { - slice[x] = slice[x] + 20; - } else { - if x == 4 { - slice[x] = 5; - } - assert(slice[4] == 5); - } - } else { - slice[x] = 0; - } - assert(slice[4] == 5); -} +} \ No newline at end of file From d0dded93949fbd812522effcebacbff9bd9e12da Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 17:05:48 +0000 Subject: [PATCH 29/46] switch to var_to_expression and remove get_constant method --- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 11 +---------- crates/noirc_evaluator/src/ssa/acir_gen/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 35924985420..cf73e1baba8 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -242,7 +242,7 @@ impl AcirContext { } /// Converts an [`AcirVar`] to an [`Expression`] - fn var_to_expression(&self, var: AcirVar) -> Result { + pub(crate) fn var_to_expression(&self, var: AcirVar) -> Result { let var_data = match self.vars.get(&var) { Some(var_data) => var_data, None => { @@ -260,15 +260,6 @@ impl AcirContext { } } - // Return Some(constant) if the given AcirVar refers to a constant value, - // otherwise return None - pub(crate) fn get_constant(&self, var: &AcirVar) -> Option { - match self.vars[var] { - AcirVarData::Const(field) => Some(field), - _ => None, - } - } - /// Adds a new Variable to context whose value will /// be constrained to be the negation of `var`. /// diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index acc8645f541..eacff8cdbc7 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -651,8 +651,8 @@ impl Context { Type::Slice(_) => { let length = length .expect("ICE: array set on slice must have a length associated with the call"); - let length_acir_value = self.convert_value(length, dfg); - let len = self.acir_context.get_constant(&length_acir_value.into_var()?); + let length_acir_var = self.convert_value(length, dfg).into_var()?; + let len = self.acir_context.var_to_expression(length_acir_var)?.to_const(); let len = len .expect("ICE: slice length should be fully tracked and constant by ACIR gen"); len.to_u128() as usize From 347e9a64f3de9850a9b0fd08eab634dc77dade3a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 17:45:15 +0000 Subject: [PATCH 30/46] slcie insert and remove with constant index --- .../slice_dynamic_index/src/main.nr | 17 ++++-- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 54 +++++++++++++++---- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr index 169bc321f07..ba8f89427d5 100644 --- a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr @@ -117,15 +117,24 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { assert(rest_of_slice.len() == 6); // TODO: SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen - // slice = slice.insert(x, 20); - // let (remove_slice, removed_elem) = new_slice.remove(3); + slice = rest_of_slice.insert(2, 20); + assert(slice[2] == 20); + assert(slice[6] == 30); + assert(slice.len() == 7); + + let (removed_slice, removed_elem) = slice.remove(3); + // TODO: The deconstructed tuple assigns to the slice but is not seen outside of the if statement + slice = removed_slice; + + assert(removed_elem == 1); + assert(slice.len() == 6); } else { assert(slice[x] == 0); slice = slice.push_back(20); } - assert(slice.len() == 8); - assert(slice[slice.len() - 1] == 10); + assert(slice.len() == 6); + assert(slice[slice.len() - 1] == 30); } fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index eacff8cdbc7..0b0fa67f644 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1166,27 +1166,59 @@ impl Context { ]) } Intrinsic::SliceInsert => { - // TODO: allow slice insert with a constant index - // let slice_length = self.convert_value(arguments[0], dfg).into_var()?; - // let slice = self.convert_value(arguments[1], dfg); - // let index = self.convert_value(arguments[2], dfg).into_var()?; - // let element = self.convert_value(arguments[3], dfg); + // Slice insert with a constant index + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + let index = self.convert_value(arguments[2], dfg).into_var()?; + let element = self.convert_value(arguments[3], dfg); - // let index = self.acir_context.get_constant(&index); - // let mut new_slice = Vector::new(); - // self.slice_intrinsic_input(&mut new_slice, slice)?; + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.add_var(slice_length, one)?; - // Slice insert is a little less obvious on how to implement due to the case + // TODO: Slice insert is a little less obvious on how to implement due to the case // of having a dynamic index // The slice insert logic will need a more involved codegen - todo!("need to write acir gen for SliceInsert") + let index = self.acir_context.var_to_expression(index)?.to_const(); + let index = index + .expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + let index = index.to_u128() as usize; + + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + + new_slice.insert(index, element); + Ok(vec![ + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice), + ]) } Intrinsic::SliceRemove => { + // Slice insert with a constant index + let slice_length = self.convert_value(arguments[0], dfg).into_var()?; + let slice = self.convert_value(arguments[1], dfg); + let index = self.convert_value(arguments[2], dfg).into_var()?; + + let one = self.acir_context.add_constant(FieldElement::one()); + let new_slice_length = self.acir_context.sub_var(slice_length, one)?; + // TODO: allow slice remove with a constant index // Slice remove is a little less obvious on how to implement due to the case // of having a dynamic index // The slice remove logic will need a more involved codegen - todo!("need to write acir gen for SliceInsert") + let index = self.acir_context.var_to_expression(index)?.to_const(); + let index = index + .expect("ICE: slice length should be fully tracked and constant by ACIR gen"); + let index = index.to_u128() as usize; + + let mut new_slice = Vector::new(); + self.slice_intrinsic_input(&mut new_slice, slice)?; + let removed_elem = new_slice.remove(index); + + Ok(vec![ + AcirValue::Var(new_slice_length, AcirType::field()), + AcirValue::Array(new_slice), + removed_elem, + ]) } _ => todo!("expected a black box function"), } From 681d3e99ae8d73bb45478ce2484b646d8cfce7a1 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 18:07:07 +0000 Subject: [PATCH 31/46] add comments for issues --- .../slice_dynamic_index/src/main.nr | 6 ++++-- crates/noirc_evaluator/src/ssa/acir_gen/mod.rs | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr index ba8f89427d5..9794b80c3dc 100644 --- a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr @@ -116,14 +116,16 @@ fn dynamic_slice_merge_if(mut slice: [Field], x: Field) { assert(first_elem == 12); assert(rest_of_slice.len() == 6); - // TODO: SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen + // TODO(#2462): SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen slice = rest_of_slice.insert(2, 20); assert(slice[2] == 20); assert(slice[6] == 30); assert(slice.len() == 7); + // TODO(#2462): SliceInsert and SliceRemove with a dynamic index are not yet implemented in ACIR gen let (removed_slice, removed_elem) = slice.remove(3); - // TODO: The deconstructed tuple assigns to the slice but is not seen outside of the if statement + // The deconstructed tuple assigns to the slice but is not seen outside of the if statement + // without a direct assignment slice = removed_slice; assert(removed_elem == 1); diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 0b0fa67f644..60bce2bcf8b 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -603,6 +603,7 @@ impl Context { let typ = match dfg.type_of_value(array) { Type::Array(typ, _) => { if typ.len() != 1 { + // TODO(#2461) unimplemented!( "Non-const array indices is not implemented for non-homogenous array" ); @@ -611,6 +612,7 @@ impl Context { } Type::Slice(typ) => { if typ.len() != 1 { + // TODO(#2461) unimplemented!( "Non-const array indices is not implemented for non-homogenous array" ); @@ -1094,7 +1096,7 @@ impl Context { Intrinsic::SlicePushBack => { let slice_length = self.convert_value(arguments[0], dfg).into_var()?; let slice = self.convert_value(arguments[1], dfg); - // TODO: make sure that we have handled nested struct inputs + // TODO(#2461): make sure that we have handled nested struct inputs let element = self.convert_value(arguments[2], dfg); let one = self.acir_context.add_constant(FieldElement::one()); @@ -1112,7 +1114,7 @@ impl Context { Intrinsic::SlicePushFront => { let slice_length = self.convert_value(arguments[0], dfg).into_var()?; let slice = self.convert_value(arguments[1], dfg); - // TODO: make sure that we have handled nested struct inputs + // TODO(#2461): make sure that we have handled nested struct inputs let element = self.convert_value(arguments[2], dfg); let one = self.acir_context.add_constant(FieldElement::one()); @@ -1136,6 +1138,7 @@ impl Context { let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; + // TODO(#2461): make sure that we have handled nested struct inputs let elem = new_slice .pop_back() .expect("There are no elements in this slice to be removed"); @@ -1155,6 +1158,7 @@ impl Context { let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; + // TODO(#2461): make sure that we have handled nested struct inputs let elem = new_slice .pop_front() .expect("There are no elements in this slice to be removed"); @@ -1175,7 +1179,7 @@ impl Context { let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.add_var(slice_length, one)?; - // TODO: Slice insert is a little less obvious on how to implement due to the case + // TODO(#2462): Slice insert is a little less obvious on how to implement due to the case // of having a dynamic index // The slice insert logic will need a more involved codegen let index = self.acir_context.var_to_expression(index)?.to_const(); @@ -1185,8 +1189,9 @@ impl Context { let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; - + // TODO(#2461): make sure that we have handled nested struct inputs new_slice.insert(index, element); + Ok(vec![ AcirValue::Var(new_slice_length, AcirType::field()), AcirValue::Array(new_slice), @@ -1201,7 +1206,7 @@ impl Context { let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.sub_var(slice_length, one)?; - // TODO: allow slice remove with a constant index + // TODO(#2462): allow slice remove with a constant index // Slice remove is a little less obvious on how to implement due to the case // of having a dynamic index // The slice remove logic will need a more involved codegen @@ -1212,6 +1217,7 @@ impl Context { let mut new_slice = Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; + // TODO(#2461): make sure that we have handled nested struct inputs let removed_elem = new_slice.remove(index); Ok(vec![ From 9b95a3a21ab83dfae8bc767b0fe7d6e73868e129 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 18:57:40 +0000 Subject: [PATCH 32/46] Empty-Commit From e0678841e413efc85b324c68f52a92ebd2675804 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 20:23:42 +0000 Subject: [PATCH 33/46] fix constant folding optimization and cargo fmt --- .../src/ssa/opt/constant_folding.rs | 4 +++- .../src/hir/resolution/resolver.rs | 3 ++- crates/noirc_frontend/src/parser/parser.rs | 22 ++++++++----------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/constant_folding.rs b/crates/noirc_evaluator/src/ssa/opt/constant_folding.rs index e53bfac8c03..a3f96161626 100644 --- a/crates/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/crates/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -102,7 +102,9 @@ impl Context { // If the instruction doesn't have side-effects, cache the results so we can reuse them if // the same instruction appears again later in the block. - if !instruction.has_side_effects(&function.dfg) { + if !instruction.has_side_effects(&function.dfg) + && !matches!(instruction, Instruction::Allocate) + { instruction_result_cache.insert(instruction, new_results.clone()); } for (old_result, new_result) in old_results.iter().zip(new_results) { diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index d0159474606..7cde9ae231d 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -371,7 +371,8 @@ impl<'a> Resolver<'a> { // expect() here is valid, because the only places we don't have a span are omitted types // e.g. a function without return type implicitly has a spanless UnresolvedType::Unit return type // To get an invalid env type, the user must explicitly specify the type, which will have a span - let env_span = env.span.expect("Unexpected missing span for closure environment type"); + let env_span = + env.span.expect("Unexpected missing span for closure environment type"); let env = Box::new(self.resolve_type_inner(*env, new_variables)); diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 5f4b5f5484a..1da476a42ee 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/noirc_frontend/src/parser/parser.rs @@ -40,8 +40,8 @@ use crate::{ BinaryOp, BinaryOpKind, BlockExpression, ConstrainStatement, Distinctness, FunctionDefinition, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, Pattern, Recoverable, - TraitConstraint, TraitImpl, TraitImplItem, TraitItem, TypeImpl, UnaryOp, - UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, TraitBound, + TraitBound, TraitConstraint, TraitImpl, TraitImplItem, TraitItem, TypeImpl, UnaryOp, + UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; use chumsky::prelude::*; @@ -527,19 +527,17 @@ fn trait_implementation_body() -> impl NoirParser> { } fn where_clause() -> impl NoirParser> { - struct MultiTraitConstraint { typ: UnresolvedType, trait_bounds: Vec, } - let constraints = parse_type() - .then_ignore(just(Token::Colon)) - .then(trait_bounds()) - .validate(|(typ, trait_bounds), span, emit| { + let constraints = parse_type().then_ignore(just(Token::Colon)).then(trait_bounds()).validate( + |(typ, trait_bounds), span, emit| { emit(ParserError::with_reason(ParserErrorReason::ExperimentalFeature("Traits"), span)); MultiTraitConstraint { typ, trait_bounds } - }); + }, + ); keyword(Keyword::Where) .ignore_then(constraints.separated_by(just(Token::Comma))) @@ -549,7 +547,8 @@ fn where_clause() -> impl NoirParser> { let mut result: Vec = Vec::new(); for constraint in x { for bound in constraint.trait_bounds { - result.push(TraitConstraint { typ:constraint.typ.clone(), trait_bound:bound } ); + result + .push(TraitConstraint { typ: constraint.typ.clone(), trait_bound: bound }); } } result @@ -557,10 +556,7 @@ fn where_clause() -> impl NoirParser> { } fn trait_bounds() -> impl NoirParser> { - trait_bound() - .separated_by(just(Token::Plus)) - .at_least(1) - .allow_trailing() + trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() } fn trait_bound() -> impl NoirParser { From d093f199dca608760853c886f13bda1dd7e370c3 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 20:49:42 +0000 Subject: [PATCH 34/46] refactor handle array operation --- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 60bce2bcf8b..5855936d3f8 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -415,27 +415,8 @@ impl Context { let acir_var = self.convert_numeric_value(*condition, dfg)?; self.current_side_effects_enabled_var = acir_var; } - Instruction::ArrayGet { array, index } => { - self.handle_array_operation( - instruction_id, - *array, - *index, - None, - dfg, - last_array_uses, - None, - )?; - } - Instruction::ArraySet { array, index, value, length } => { - self.handle_array_operation( - instruction_id, - *array, - *index, - Some(*value), - dfg, - last_array_uses, - *length, - )?; + Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { + self.handle_array_operation(instruction_id, dfg, last_array_uses)?; } Instruction::Allocate => { unreachable!("Expected all allocate instructions to be removed before acir_gen") @@ -483,17 +464,26 @@ impl Context { /// Handles an ArrayGet or ArraySet instruction. /// To set an index of the array (and create a new array in doing so), pass Some(value) for /// store_value. To just retrieve an index of the array, pass None for store_value. - #[allow(clippy::too_many_arguments)] fn handle_array_operation( &mut self, instruction: InstructionId, - array: ValueId, - index: ValueId, - store_value: Option, dfg: &DataFlowGraph, last_array_uses: &HashMap, - length: Option, ) -> Result<(), RuntimeError> { + // Pass the instruction between array methods rather than the internal fields themselves + let (array, index, store_value) = match dfg[instruction] { + Instruction::ArrayGet { array, index } => (array, index, None), + Instruction::ArraySet { array, index, value, .. } => (array, index, Some(value)), + _ => { + return Err(InternalError::UnExpected { + expected: format!("Instruction should be ArrayGet or ArraySet"), + found: format!("Instead got {:?}", dfg[instruction]), + call_stack: self.acir_context.get_call_stack(), + } + .into()) + } + }; + let index_const = dfg.get_numeric_constant(index); match dfg.type_of_value(array) { @@ -563,8 +553,8 @@ impl Context { let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); - if let Some(store) = store_value { - self.array_set(instruction, array, index, store, dfg, map_array, length)?; + if let Some(_) = store_value { + self.array_set(instruction, dfg, map_array)?; } else { self.array_get(instruction, array, index, dfg)?; } @@ -629,17 +619,24 @@ impl Context { /// 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. - #[allow(clippy::too_many_arguments)] fn array_set( &mut self, instruction: InstructionId, - array: ValueId, - index: ValueId, - store_value: ValueId, dfg: &DataFlowGraph, map_array: bool, - length: Option, ) -> Result<(), InternalError> { + // Pass the instruction between array methods rather than the internal fields themselves + let (array, index, store_value, length) = match dfg[instruction] { + Instruction::ArraySet { array, index, value, length } => (array, index, value, length), + _ => { + return Err(InternalError::UnExpected { + expected: format!("Instruction should be ArraySet"), + found: format!("Instead got {:?}", dfg[instruction]), + call_stack: self.acir_context.get_call_stack(), + }) + } + }; + // Fetch the internal SSA ID for the array let array = dfg.resolve(array); From 6e4f75ac6a64360f4b846707b1de1b867ef4f586 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 28 Aug 2023 21:04:00 +0000 Subject: [PATCH 35/46] cargo clipy --- crates/noirc_evaluator/src/ssa/acir_gen/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 5855936d3f8..a9397aa64f8 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -476,7 +476,7 @@ impl Context { Instruction::ArraySet { array, index, value, .. } => (array, index, Some(value)), _ => { return Err(InternalError::UnExpected { - expected: format!("Instruction should be ArrayGet or ArraySet"), + expected: "Instruction should be an ArrayGet or ArraySet".to_owned(), found: format!("Instead got {:?}", dfg[instruction]), call_stack: self.acir_context.get_call_stack(), } @@ -553,7 +553,7 @@ impl Context { let resolved_array = dfg.resolve(array); let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); - if let Some(_) = store_value { + if store_value.is_some() { self.array_set(instruction, dfg, map_array)?; } else { self.array_get(instruction, array, index, dfg)?; @@ -630,7 +630,7 @@ impl Context { Instruction::ArraySet { array, index, value, length } => (array, index, value, length), _ => { return Err(InternalError::UnExpected { - expected: format!("Instruction should be ArraySet"), + expected: "Instruction should be an ArraySet".to_owned(), found: format!("Instead got {:?}", dfg[instruction]), call_stack: self.acir_context.get_call_stack(), }) From 15c79e06c7fdc857a008bd9b483990f389696b83 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 29 Aug 2023 13:57:01 +0000 Subject: [PATCH 36/46] remove assert in slice_dynamic_index_test --- .../tests/execution_success/slice_dynamic_index/src/main.nr | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr index 9794b80c3dc..6627ae3437d 100644 --- a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr @@ -1,8 +1,5 @@ fn main(x : Field) { - assert(x == 5); - // The parameters to this function must come from witness values (inputs to main). - // The assert on `x` before this call is to guarantee we test the expected - // conditional branches. + // The parameters to this function must come directly from witness values (inputs to main). regression_dynamic_slice_index(x - 1, x - 4); } From 1ad7de08bac37230e012f4a68688494ce113a437 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 30 Aug 2023 13:50:48 +0000 Subject: [PATCH 37/46] remove old debug derive --- crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 6764cd27f22..15ef929b71e 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -206,7 +206,6 @@ struct Context<'f> { conditions: Vec<(BasicBlockId, ValueId)>, } -#[derive(Debug, Clone)] struct Store { old_value: ValueId, new_value: ValueId, From 39fee2012c1b16a8f8ad1d47342ffcda906866c7 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 1 Sep 2023 15:19:36 +0000 Subject: [PATCH 38/46] add assert constant in slice insert and remove --- noir_stdlib/src/slice.nr | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index 4f73f3a2994..053a8acacb6 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -22,16 +22,28 @@ impl [T] { #[builtin(slice_pop_front)] fn pop_front(_self: Self) -> (T, Self) { } + fn insert(self, _index: Field, _elem: T) -> Self { + // TODO(#2462): Slice insert with a dynamic index + crate::assert_constant(_index); + self.__slice_insert(_index, _elem) + } + /// Insert an element at a specified index, shifting all elements /// after it to the right #[builtin(slice_insert)] - fn insert(_self: Self, _index: Field, _elem: T) -> Self { } + fn __slice_insert(_self: Self, _index: Field, _elem: T) -> Self { } + + fn remove(self, _index: Field) -> (Self, T) { + // TODO(#2462): Slice remove with a dynamic index + crate::assert_constant(_index); + self.__slice_remove(_index) + } /// Remove an element at a specified index, shifting all elements /// after it to the left, returning the altered slice and /// the removed element #[builtin(slice_remove)] - fn remove(_self: Self, _index: Field) -> (Self, T) { } + fn __slice_remove(_self: Self, _index: Field) -> (Self, T) { } // Append each element of the `other` slice to the end of `self`. // This returns a new slice and leaves both input slices unchanged. From 8afe885a33255b6955f15d9df06f10058556a219 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 1 Sep 2023 19:22:15 +0000 Subject: [PATCH 39/46] fix brillig slices --- .../noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs | 4 ++++ 1 file changed, 4 insertions(+) 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 afc93e25790..7db76e91c37 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -423,6 +423,10 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.deallocate_register(radix); } + Value::Intrinsic(Intrinsic::AssertConstant) => { + // Do nothing here as Brillig should be able to handle + // runtime values everywhere including loop bounds and array indices + } _ => { unreachable!("unsupported function call type {:?}", dfg[*func]) } From e6d7aa8d9e1278bf2492989f7e0b5752484b70fb Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 1 Sep 2023 20:36:11 +0000 Subject: [PATCH 40/46] pass correct inputs for recursive aggregation but we need to most likely simulate recursion again --- Cargo.toml | 2 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f6ff3fa1d2..573ee99b9a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,4 +60,4 @@ tower = "0.4" url = "2.2.0" wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] } wasm-bindgen-test = "0.3.33" -base64 = "0.21.2" +base64 = "0.21.2" \ No newline at end of file diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index d2edd596503..525f0000b22 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -130,10 +130,11 @@ impl GeneratedAcir { constants: Vec, output_count: usize, ) -> Result, InternalError> { + dbg!(output_count); let input_count = inputs.iter().fold(0usize, |sum, val| sum + val.len()); intrinsics_check_inputs(func_name, input_count); intrinsics_check_outputs(func_name, output_count); - + dbg!(input_count); let outputs = vecmap(0..output_count, |_| self.next_witness_index()); // clone is needed since outputs is moved when used in blackbox function. @@ -258,15 +259,19 @@ impl GeneratedAcir { AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::RecursiveAggregation { .. }) ) }); - + + // Slices are represented as a tuple of (length, slice contents). + // All inputs to the `RecursiveAggregation` black box func are slices, + // thus we must make sure to only fetch the slice contents rather than + // the slice length when setting up the inputs. let input_aggregation_object = - if !has_previous_aggregation { None } else { Some(inputs[4].clone()) }; + if !has_previous_aggregation { None } else { Some(inputs[7].clone()) }; BlackBoxFuncCall::RecursiveAggregation { - verification_key: inputs[0].clone(), - proof: inputs[1].clone(), - public_inputs: inputs[2].clone(), - key_hash: inputs[3][0], + verification_key: inputs[1].clone(), + proof: inputs[3].clone(), + public_inputs: inputs[5].clone(), + key_hash: inputs[6][0], input_aggregation_object, output_aggregation_object: outputs, } From dc2defb8c1fe00ded055b4125c369f3d2c2a810f Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 5 Sep 2023 17:07:42 +0000 Subject: [PATCH 41/46] use default after fxHashMap --- crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 23fe39d0fb5..2b9991844c8 100644 --- a/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -236,7 +236,7 @@ fn flatten_function_cfg(function: &mut Function) { local_allocations: HashSet::new(), branch_ends, conditions: Vec::new(), - outer_block_stores: HashMap::new(), + outer_block_stores: HashMap::default(), }; context.flatten(); } From cbf6f362a5a1307b82540a5391a17a00e37f49da Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 5 Sep 2023 20:08:15 +0000 Subject: [PATCH 42/46] fix call_black_box for recursive agg --- .../src/ssa/acir_gen/acir_ir/generated_acir.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 11925da8fa8..a573195a89b 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -132,11 +132,10 @@ impl GeneratedAcir { constants: Vec, output_count: usize, ) -> Result, InternalError> { - dbg!(output_count); let input_count = inputs.iter().fold(0usize, |sum, val| sum + val.len()); intrinsics_check_inputs(func_name, input_count); intrinsics_check_outputs(func_name, output_count); - dbg!(input_count); + let outputs = vecmap(0..output_count, |_| self.next_witness_index()); // clone is needed since outputs is moved when used in blackbox function. @@ -227,18 +226,14 @@ impl GeneratedAcir { ) }); - // Slices are represented as a tuple of (length, slice contents). - // All inputs to the `RecursiveAggregation` black box func are slices, - // thus we must make sure to only fetch the slice contents rather than - // the slice length when setting up the inputs. let input_aggregation_object = - if !has_previous_aggregation { None } else { Some(inputs[7].clone()) }; + if !has_previous_aggregation { None } else { Some(inputs[4].clone()) }; BlackBoxFuncCall::RecursiveAggregation { - verification_key: inputs[1].clone(), - proof: inputs[3].clone(), - public_inputs: inputs[5].clone(), - key_hash: inputs[6][0], + verification_key: inputs[0].clone(), + proof: inputs[1].clone(), + public_inputs: inputs[2].clone(), + key_hash: inputs[3][0], input_aggregation_object, output_aggregation_object: outputs, } From a14f9f3cd9830b71c873bbe35d93cdfa36032a4a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 5 Sep 2023 20:15:20 +0000 Subject: [PATCH 43/46] cleanup printer and cargo fmt --- .../src/brillig/brillig_gen/brillig_block.rs | 2 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 2 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 2 +- crates/noirc_evaluator/src/ssa/ir/printer.rs | 24 +++++++------------ 4 files changed, 12 insertions(+), 18 deletions(-) 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 b1e0b6f8bc7..c62adf8e7b6 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -445,7 +445,7 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.deallocate_register(radix); } Value::Intrinsic(Intrinsic::AssertConstant) => { - // Do nothing here as Brillig should be able to handle + // Do nothing here as Brillig should be able to handle // runtime values everywhere including loop bounds and array indices } _ => { diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index a573195a89b..a914d32a8f7 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -225,7 +225,7 @@ impl GeneratedAcir { AcirOpcode::BlackBoxFuncCall(BlackBoxFuncCall::RecursiveAggregation { .. }) ) }); - + let input_aggregation_object = if !has_previous_aggregation { None } else { Some(inputs[4].clone()) }; diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs index 6130ad77564..092fb1e966b 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -29,8 +29,8 @@ use acvm::{ acir::{circuit::opcodes::BlockId, native_types::Expression}, FieldElement, }; -use im::Vector; use fxhash::FxHashMap as HashMap; +use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_frontend::Distinctness; diff --git a/crates/noirc_evaluator/src/ssa/ir/printer.rs b/crates/noirc_evaluator/src/ssa/ir/printer.rs index 8e01c3c5c38..537b474a8d9 100644 --- a/crates/noirc_evaluator/src/ssa/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa/ir/printer.rs @@ -164,23 +164,17 @@ pub(crate) fn display_instruction( writeln!(f, "array_get {}, index {}", show(*array), show(*index)) } Instruction::ArraySet { array, index, value, length } => { + write!( + f, + "array_set {}, index {}, value {}", + show(*array), + show(*index), + show(*value) + )?; if let Some(length) = length { - writeln!( - f, - "array_set {}, index {}, value {}, length {}", - show(*array), - show(*index), - show(*value), - show(*length), - ) + writeln!(f, ", length {}", show(*length)) } else { - writeln!( - f, - "array_set {}, index {}, value {}", - show(*array), - show(*index), - show(*value) - ) + writeln!(f) } } } From 2c695e33df0c002a4c2ed50422e84014e98db2d9 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 6 Sep 2023 18:15:28 +0000 Subject: [PATCH 44/46] remove assert_constant case from brillig_block --- crates/nargo_cli/tests/execution_success/slices/src/main.nr | 2 +- .../noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slices/src/main.nr b/crates/nargo_cli/tests/execution_success/slices/src/main.nr index 91a5a24dc8f..26cf173a253 100644 --- a/crates/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slices/src/main.nr @@ -141,7 +141,7 @@ fn merge_slices_mutate(x: Field, y: Field) -> [Field] { slice = slice.push_back(x); } else { slice = slice.push_back(x); - }; + } slice } 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 c62adf8e7b6..2e31f618e8f 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -444,10 +444,6 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.deallocate_register(radix); } - Value::Intrinsic(Intrinsic::AssertConstant) => { - // Do nothing here as Brillig should be able to handle - // runtime values everywhere including loop bounds and array indices - } _ => { unreachable!("unsupported function call type {:?}", dfg[*func]) } From 1e346be9e140ca197ed428d48bdb49dd3f3026b5 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 6 Sep 2023 18:26:17 +0000 Subject: [PATCH 45/46] remove arguments.len() check in simplify_call ArrayLen --- crates/noirc_evaluator/src/ssa/ir/instruction/call.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index f2cba0476cd..34ec8a90092 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -75,9 +75,7 @@ pub(super) fn simplify_call( if let Some(length) = dfg.try_get_array_length(arguments[0]) { let length = FieldElement::from(length as u128); SimplifyResult::SimplifiedTo(dfg.make_constant(length, Type::field())) - } else if arguments.len() > 1 - && matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) - { + } else if matches!(dfg.type_of_value(arguments[1]), Type::Slice(_)) { SimplifyResult::SimplifiedTo(arguments[0]) } else { SimplifyResult::None From 576b15ca8672521cf9f80b2eb035d33d5b13e5c5 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 6 Sep 2023 20:22:32 +0000 Subject: [PATCH 46/46] add test for using push back on dyn slice outside of if statement --- .../slice_dynamic_index/src/main.nr | 25 ++++++++++++++++++- .../noirc_evaluator/src/ssa/ir/instruction.rs | 2 +- .../src/ssa/ir/instruction/call.rs | 23 ++++++++++------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr index 6627ae3437d..48229a0ced3 100644 --- a/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr +++ b/crates/nargo_cli/tests/execution_success/slice_dynamic_index/src/main.nr @@ -15,6 +15,7 @@ fn regression_dynamic_slice_index(x: Field, y: Field) { dynamic_slice_index_set_nested_if_else_else(slice, x, y); dynamic_slice_index_set_nested_if_else_if(slice, x, y + 1); 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); @@ -66,6 +67,16 @@ fn dynamic_slice_index_if(mut slice: [Field], x: Field) { assert(slice[4] == 2); } +fn dynamic_array_index_if(mut array: [Field; 5], x: Field) { + if x as u32 < 10 { + assert(array[x] == 4); + array[x] = array[x] - 2; + } else { + assert(array[x] == 0); + } + assert(array[4] == 2); +} + // This tests the case of missing a store instruction in the then branch // of merging slices fn dynamic_slice_index_else(mut slice: [Field], x: Field) { @@ -145,8 +156,12 @@ fn dynamic_slice_merge_else(mut slice: [Field], x: Field) { slice[x] = slice[x] - 2; slice = slice.push_back(10); } - + assert(slice.len() == 6); assert(slice[slice.len() - 1] == 10); + + slice = slice.push_back(20); + assert(slice.len() == 7); + assert(slice[slice.len() - 1] == 20); } fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: Field) { @@ -165,6 +180,8 @@ fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: assert(slice[x] == 22); } else { slice[x] = 10; + slice = slice.push_back(15); + assert(slice.len() == 6); } assert(slice[4] == 10); } @@ -172,6 +189,12 @@ fn dynamic_slice_index_set_nested_if_else_else(mut slice: [Field], x: Field, y: slice[x] = 0; } assert(slice[4] == 10); + assert(slice.len() == 6); + assert(slice[slice.len() - 1] == 15); + + slice = slice.push_back(20); + assert(slice.len() == 7); + assert(slice[slice.len() - 1] == 20); } fn dynamic_slice_index_set_nested_if_else_if(mut slice: [Field], x: Field, y: Field) { diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction.rs b/crates/noirc_evaluator/src/ssa/ir/instruction.rs index 0bae0ee436d..67c6ded4da2 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction.rs @@ -425,7 +425,7 @@ impl Instruction { None } } - Instruction::Call { func, arguments } => simplify_call(*func, arguments, dfg), + Instruction::Call { func, arguments } => simplify_call(*func, arguments, dfg, block), Instruction::EnableSideEffects { condition } => { if let Some(last) = dfg[block].instructions().last().copied() { let last = &mut dfg[last]; diff --git a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs index 34ec8a90092..dbc358c5f19 100644 --- a/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/crates/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -9,17 +9,23 @@ use crate::ssa::ir::{ instruction::Intrinsic, map::Id, types::Type, - value::{Value, ValueId}, + value::{Value, ValueId}, basic_block::BasicBlockId, }; use super::{Binary, BinaryOp, Endian, Instruction, SimplifyResult}; /// Try to simplify this call instruction. If the instruction can be simplified to a known value, /// that value is returned. Otherwise None is returned. +/// +/// The `block` parameter indicates the block any new instructions that are part of a call's +/// simplification will be inserted into. For example, all slice intrinsics require updates +/// to the slice length, which requires inserting a binary instruction. This update instruction +/// must be inserted into the same block that the call itself is being simplified into. pub(super) fn simplify_call( func: ValueId, arguments: &[ValueId], dfg: &mut DataFlowGraph, + block: BasicBlockId, ) -> SimplifyResult { let intrinsic = match &dfg[func] { Value::Intrinsic(intrinsic) => *intrinsic, @@ -88,7 +94,7 @@ pub(super) fn simplify_call( slice.push_back(*elem); } - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add, block); let new_slice = dfg.make_array(slice, element_type); SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]) @@ -103,7 +109,7 @@ pub(super) fn simplify_call( slice.push_front(*elem); } - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add, block); let new_slice = dfg.make_array(slice, element_type); SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]) @@ -128,7 +134,7 @@ pub(super) fn simplify_call( let new_slice = dfg.make_array(slice, typ); results.push_front(new_slice); - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub, block); results.push_front(new_slice_length); SimplifyResult::SimplifiedToMultiple(results.into()) @@ -146,7 +152,7 @@ pub(super) fn simplify_call( slice.pop_front().expect("There are no elements in this slice to be removed") }); - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub, block); results.push(new_slice_length); @@ -171,7 +177,7 @@ pub(super) fn simplify_call( index += 1; } - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Add, block); let new_slice = dfg.make_array(slice, typ); SimplifyResult::SimplifiedToMultiple(vec![new_slice_length, new_slice]) @@ -194,7 +200,7 @@ pub(super) fn simplify_call( let new_slice = dfg.make_array(slice, typ); results.insert(0, new_slice); - let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub); + let new_slice_length = update_slice_length(arguments[0], dfg, BinaryOp::Sub, block); results.insert(0, new_slice_length); @@ -226,9 +232,8 @@ pub(super) fn simplify_call( /// The binary operation performed on the slice length is always an addition or subtraction of `1`. /// This is because the slice length holds the user length (length as displayed by a `.len()` call), /// and not a flattened length used internally to represent arrays of tuples. -fn update_slice_length(slice_len: ValueId, dfg: &mut DataFlowGraph, operator: BinaryOp) -> ValueId { +fn update_slice_length(slice_len: ValueId, dfg: &mut DataFlowGraph, operator: BinaryOp, block: BasicBlockId) -> ValueId { let one = dfg.make_constant(FieldElement::one(), Type::field()); - let block = dfg.make_block(); let instruction = Instruction::Binary(Binary { lhs: slice_len, operator, rhs: one }); let call_stack = dfg.get_value_call_stack(slice_len); dfg.insert_instruction_and_results(instruction, block, None, call_stack).first()