diff --git a/crates/fm/src/file_map.rs b/crates/fm/src/file_map.rs index f186438a0dd..2a1cd662d4e 100644 --- a/crates/fm/src/file_map.rs +++ b/crates/fm/src/file_map.rs @@ -42,6 +42,10 @@ impl FileId { pub fn as_usize(&self) -> usize { self.0 } + + pub fn dummy() -> FileId { + FileId(0) + } } pub struct File<'input>(&'input SimpleFile); diff --git a/crates/nargo/tests/test_data/9_conditional/src/main.nr b/crates/nargo/tests/test_data/9_conditional/src/main.nr index 8215ecc5241..ab4270f1e38 100644 --- a/crates/nargo/tests/test_data/9_conditional/src/main.nr +++ b/crates/nargo/tests/test_data/9_conditional/src/main.nr @@ -49,6 +49,25 @@ fn main(a: u32, mut c: [u32; 4], x: [u8; 5], result: pub [u8; 32]){ } } + //Test case for short-circuit + let mut data = [0 as u32; 32]; + let mut ba = a; + for i in 0..32 { + let i_u32 = i as u32; + if i_u32 == a { + for j in 0..4 { + data[i + j] = c[4 - 1 - j]; + for k in 0..4 { + ba = ba +data[k]; + }; + if ba == 4864 { + c[3]=ba; + } + }; + } + }; + constrain data[31] == 0; + constrain ba != 13; if a == 3 { c = test4(); } diff --git a/crates/noirc_errors/src/position.rs b/crates/noirc_errors/src/position.rs index 97f510d91bf..8966fab1a2f 100644 --- a/crates/noirc_errors/src/position.rs +++ b/crates/noirc_errors/src/position.rs @@ -124,4 +124,8 @@ impl Location { pub fn new(span: Span, file: FileId) -> Self { Self { span, file } } + + pub fn dummy() -> Location { + Location::new(Span::new(0..0), FileId::dummy()) + } } diff --git a/crates/noirc_evaluator/src/ssa/block.rs b/crates/noirc_evaluator/src/ssa/block.rs index 74df75d0702..8c0a4f17139 100644 --- a/crates/noirc_evaluator/src/ssa/block.rs +++ b/crates/noirc_evaluator/src/ssa/block.rs @@ -1,7 +1,7 @@ use super::{ conditional::AssumptionId, context::SsaContext, - node::{self, NodeId}, + node::{self, Instruction, Mark, NodeId, Opcode}, }; use std::collections::{HashMap, HashSet, VecDeque}; @@ -19,6 +19,9 @@ impl BlockId { pub fn dummy() -> BlockId { BlockId(SsaContext::dummy_id()) } + pub fn is_dummy(&self) -> bool { + *self == BlockId::dummy() + } } #[derive(Debug)] @@ -94,6 +97,35 @@ impl BasicBlock { } result } + + //Returns true if the block is a short-circuit + fn is_short_circuit(&self, ctx: &SsaContext, assumption: Option) -> bool { + if let Some(ass_value) = assumption { + if self.instructions.len() >= 3 { + if let Some(Instruction { operation, .. }) = + ctx.try_get_instruction(self.instructions[1]) + { + if *operation + == (node::Operation::Cond { + condition: ass_value, + val_true: ctx.zero(), + val_false: ctx.one(), + }) + { + if let Some(Instruction { operation, .. }) = + ctx.try_get_instruction(self.instructions[2]) + { + if *operation == node::Operation::Constrain(self.instructions[1], None) + { + return true; + } + } + } + } + } + } + false + } } pub fn create_first_block(ctx: &mut SsaContext) { @@ -217,7 +249,7 @@ pub fn bfs(start: BlockId, stop: Option, ctx: &SsaContext) -> Vec, ctx: &SsaContext) -> Vec BlockId { - if b1 == b2 { - return b1; - } - let mut process_left = vec![b1]; - let mut process_right = vec![b2]; - let mut descendants = HashMap::new(); - while !process_left.is_empty() || !process_right.is_empty() { - if let Some(block) = process_son(ctx, true, &mut descendants, &mut process_left) { - return block; +//Find the exit (i.e join) block from a IF (i.e split) block +pub fn find_join(ctx: &SsaContext, block_id: BlockId) -> BlockId { + let mut processed = HashMap::new(); + find_join_helper(ctx, block_id, &mut processed) +} + +//We follow down the path from the THEN and ELSE branches until we reach a common descendant +fn find_join_helper( + ctx: &SsaContext, + block_id: BlockId, + processed: &mut HashMap, +) -> BlockId { + let mut left = ctx[block_id].left.unwrap(); + let mut right = ctx[block_id].right.unwrap(); + let mut left_descendants = Vec::new(); + let mut right_descendants = Vec::new(); + + while !left.is_dummy() || !right.is_dummy() { + if let Some(block) = get_only_descendant(ctx, left, processed) { + left_descendants.push(block); + left = block; + if right_descendants.contains(&block) { + return block; + } } - if let Some(block) = process_son(ctx, false, &mut descendants, &mut process_right) { - return block; + if let Some(block) = get_only_descendant(ctx, right, processed) { + right_descendants.push(block); + right = block; + if left_descendants.contains(&block) { + return block; + } } } unreachable!("no join"); } -fn process_son( +//get the most direct descendant which is 'only child' +fn get_only_descendant( ctx: &SsaContext, - left: bool, - descendants: &mut HashMap, - to_process: &mut Vec, + block_id: BlockId, + processed: &mut HashMap, ) -> Option { - if let Some(block) = to_process.pop() { - if descendants.contains_key(&block) { - if descendants[&block] && left { - return None; //cycle - } - if !descendants[&block] && !left { - return None; //cycle - } - return Some(block); - } - descendants.insert(block, left); - if let Some(left) = ctx[block].left { - to_process.push(left); + if block_id == BlockId::dummy() { + return None; + } + let block = &ctx[block_id]; + if block.right.is_none() || block.kind == BlockType::ForJoin { + if let Some(left) = block.left { + processed.insert(block_id, left); } - if let Some(right) = ctx[block].right { - to_process.push(right); + block.left + } else { + if processed.contains_key(&block_id) { + return Some(processed[&block_id]); } + let descendant = find_join_helper(ctx, block_id, processed); + processed.insert(block_id, descendant); + Some(descendant) } - None } //Set left as the left block of block_id @@ -298,13 +346,44 @@ pub fn rewire_block_left(ctx: &mut SsaContext, block_id: BlockId, left: BlockId) } } +//Delete all instructions in the block +pub fn short_circuit_block(ctx: &mut SsaContext, block_id: BlockId) { + let instructions = ctx[block_id].instructions.clone(); + short_circuit_instructions(ctx, &instructions); +} + +pub fn short_circuit_instructions(ctx: &mut SsaContext, instructions: &Vec) { + let mut zeros = HashMap::new(); + for i in instructions { + let ins = ctx.get_instruction(*i); + if ins.res_type != node::ObjectType::NotAnObject { + zeros.insert(ins.res_type, ctx.zero_with_type(ins.res_type)); + } + } + for i in instructions { + let ins = ctx.get_mut_instruction(*i); + if ins.res_type != node::ObjectType::NotAnObject { + ins.mark = Mark::ReplaceWith(zeros[&ins.res_type]); + } else if ins.operation.opcode() != Opcode::Nop { + ins.mark = Mark::Deleted; + } + } +} + //merge subgraph from start to end in one block, excluding end -pub fn merge_path(ctx: &mut SsaContext, start: BlockId, end: BlockId) -> VecDeque { +pub fn merge_path( + ctx: &mut SsaContext, + start: BlockId, + end: BlockId, + assumption: Option, +) -> VecDeque { let mut removed_blocks = VecDeque::new(); if start != end { let mut next = start; let mut instructions = Vec::new(); let mut block = &ctx[start]; + let mut short_circuit = BlockId::dummy(); + while next != end { if block.dominated.len() > 1 || block.right.is_some() { dbg!(&block); @@ -312,21 +391,39 @@ pub fn merge_path(ctx: &mut SsaContext, start: BlockId, end: BlockId) -> VecDequ } block = &ctx[next]; removed_blocks.push_back(next); - instructions.extend(&block.instructions); + + if short_circuit.is_dummy() { + instructions.extend(&block.instructions); + } + + if short_circuit.is_dummy() && block.is_short_circuit(ctx, assumption) { + instructions.clear(); + instructions.extend(&block.instructions); + short_circuit = block.id; + } + if let Some(left) = block.left { next = left; } else { - if end != BlockId::dummy() { + if !end.is_dummy() { unreachable!("cannot reach block {:?}", end); } next = BlockId::dummy(); } } + if !short_circuit.is_dummy() { + for &b in &removed_blocks { + if b != short_circuit { + short_circuit_block(ctx, b); + } + } + } + //we assign the concatened list of instructions to the start block, using a CSE pass let mut modified = false; super::optim::cse_block(ctx, start, &mut instructions, &mut modified).unwrap(); //Wires start to end - if end != BlockId::dummy() { + if !end.is_dummy() { rewire_block_left(ctx, start, end); } else { ctx[start].left = None; diff --git a/crates/noirc_evaluator/src/ssa/code_gen.rs b/crates/noirc_evaluator/src/ssa/code_gen.rs index aceb2c77dcf..e4cc7d130c1 100644 --- a/crates/noirc_evaluator/src/ssa/code_gen.rs +++ b/crates/noirc_evaluator/src/ssa/code_gen.rs @@ -326,7 +326,7 @@ impl IRGenerator { location: noirc_errors::Location, ) -> Result { let cond = self.codegen_expression(env, expr)?.unwrap_id(); - let operation = Operation::Constrain(cond, location); + let operation = Operation::Constrain(cond, Some(location)); self.context.new_instruction(operation, ObjectType::NotAnObject)?; Ok(Value::dummy()) } diff --git a/crates/noirc_evaluator/src/ssa/conditional.rs b/crates/noirc_evaluator/src/ssa/conditional.rs index e1d357b6ca8..c163c6c563e 100644 --- a/crates/noirc_evaluator/src/ssa/conditional.rs +++ b/crates/noirc_evaluator/src/ssa/conditional.rs @@ -3,7 +3,10 @@ use num_traits::One; use crate::{ errors::RuntimeError, - ssa::{node::ObjectType, optim}, + ssa::{ + node::{Mark, ObjectType}, + optim, + }, }; use super::{ @@ -246,8 +249,7 @@ impl DecisionTree { } //find exit node: - let exit = - block::find_join(ctx, current_block.left.unwrap(), current_block.right.unwrap()); + let exit = block::find_join(ctx, current_block.id); debug_assert!(ctx[exit].kind == BlockType::IfJoin); if_decision.entry_block = current; if_decision.exit_block = exit; @@ -289,7 +291,7 @@ impl DecisionTree { let children = self.process_block(ctx, current, data); let mut test_and_push = |block_id: BlockId| { - if block_id != BlockId::dummy() && !queue.contains(&block_id) { + if !block_id.is_dummy() && !queue.contains(&block_id) { queue.push(block_id); } }; @@ -314,7 +316,7 @@ impl DecisionTree { self.reduce(ctx, i)?; } //reduce the node - if assumption.entry_block != BlockId::dummy() { + if !assumption.entry_block.is_dummy() { self.reduce_sub_graph(ctx, assumption.entry_block, assumption.exit_block)?; } Ok(()) @@ -333,18 +335,28 @@ impl DecisionTree { let left = if_block.left.unwrap(); let right = if_block.right.unwrap(); let mut const_condition = None; - if self[if_block.assumption].condition == ctx.one() { + if ctx.is_one(self[if_block.assumption].condition) { const_condition = Some(true); } - if self[if_block.assumption].condition == ctx.zero() { + if ctx.is_zero(self[if_block.assumption].condition) { const_condition = Some(false); } //merge then branch - to_remove.extend(block::merge_path(ctx, left, exit_block_id)); + to_remove.extend(block::merge_path( + ctx, + left, + exit_block_id, + self[ctx[left].assumption].value, + )); //merge else branch - to_remove.extend(block::merge_path(ctx, right, exit_block_id)); + to_remove.extend(block::merge_path( + ctx, + right, + exit_block_id, + self[ctx[right].assumption].value, + )); to_remove.push(right); let mut merged_ins; @@ -400,8 +412,11 @@ impl DecisionTree { predicate: AssumptionId, ) { if predicate == AssumptionId::dummy() || self[predicate].value != Some(ctx.zero()) { + let mut short_circuit = false; for i in instructions { - self.conditionalise_into(ctx, result, *i, predicate); + if !self.conditionalise_into(ctx, result, *i, predicate, short_circuit) { + short_circuit = true; + } } } } @@ -417,13 +432,41 @@ impl DecisionTree { } } + fn short_circuit(ctx: &mut SsaContext, stack: &mut StackFrame, condition: NodeId) -> bool { + if ctx.under_assumption(condition) { + block::short_circuit_instructions(ctx, &stack.stack); + let nop = stack.stack[0]; + stack.stack.clear(); + stack.stack.push(nop); + let operation = + Operation::Cond { condition, val_true: ctx.zero(), val_false: ctx.one() }; + let cond = ctx.add_instruction(Instruction::new( + operation, + ObjectType::Boolean, + Some(stack.block), + )); + stack.push(cond); + let unreachable = Operation::Constrain(cond, None); + let ins2 = ctx.add_instruction(Instruction::new( + unreachable, + ObjectType::NotAnObject, + Some(stack.block), + )); + stack.push(ins2); + true + } else { + false + } + } + pub fn conditionalise_into( &self, ctx: &mut SsaContext, stack: &mut StackFrame, ins_id: NodeId, predicate: AssumptionId, - ) { + short_circtuit: bool, + ) -> bool { let ass_cond; let ass_value; if predicate == AssumptionId::dummy() { @@ -452,23 +495,47 @@ impl DecisionTree { } let ins = ins1.clone(); - match &ins.operation { - Operation::Phi { block_args, .. } => { - if ctx[stack.block].kind == BlockType::IfJoin { - assert_eq!(block_args.len(), 2); - let ins2 = ctx.get_mut_instruction(ins_id); - ins2.operation = Operation::Cond { - condition: ass_cond, - val_true: block_args[0].0, - val_false: block_args[1].0, - }; - optim::simplify_id(ctx, ins_id).unwrap(); - } - stack.push(ins_id); + if short_circtuit { + stack.set_zero(ctx, ins.res_type); + let ins2 = ctx.get_mut_instruction(ins_id); + if ins2.res_type == ObjectType::NotAnObject { + ins2.mark = Mark::Deleted; + } else { + ins2.mark = Mark::ReplaceWith(stack.get_zero(ins2.res_type)); } - Operation::Binary(binop) => { - stack.push(ins_id); - if !ctx.is_one(ass_value) { + } else { + match &ins.operation { + Operation::Phi { block_args, .. } => { + if ctx[stack.block].kind == BlockType::IfJoin { + assert_eq!(block_args.len(), 2); + let ins2 = ctx.get_mut_instruction(ins_id); + ins2.operation = Operation::Cond { + condition: ass_cond, + val_true: block_args[0].0, + val_false: block_args[1].0, + }; + optim::simplify_id(ctx, ins_id).unwrap(); + } + stack.push(ins_id); + } + + Operation::Load { array_id, index } => { + if let Some(idx) = ctx.get_as_constant(*index) { + if (idx.to_u128() as u32) >= ctx.mem[*array_id].len { + if !DecisionTree::short_circuit(ctx, stack, ass_value) { + unreachable!( + "index out of bounds: the len is {} but the index is {}", + ctx.mem[*array_id].len, + idx.to_u128() + ); + } + return false; + } + } + stack.push(ins_id); + } + Operation::Binary(binop) => { + stack.push(ins_id); assert!(binop.predicate.is_none()); match binop.operator { BinaryOp::Udiv @@ -476,103 +543,151 @@ impl DecisionTree { | BinaryOp::Urem | BinaryOp::Srem | BinaryOp::Div => { - let ins2 = ctx.get_mut_instruction(ins_id); - ins2.operation = Operation::Binary(crate::node::Binary { - lhs: binop.lhs, - rhs: binop.rhs, - operator: binop.operator.clone(), - predicate: Some(ass_value), - }); + if ctx.is_zero(binop.rhs) { + if !DecisionTree::short_circuit(ctx, stack, ass_value) { + unreachable!("error: attempt to divide by zero"); + } + return false; + } + if ctx.under_assumption(ass_value) { + let ins2 = ctx.get_mut_instruction(ins_id); + ins2.operation = Operation::Binary(crate::node::Binary { + lhs: binop.lhs, + rhs: binop.rhs, + operator: binop.operator.clone(), + predicate: Some(ass_value), + }); + } } _ => (), } } - } - Operation::Store { array_id, index, value } => { - if !ins.operation.is_dummy_store() - && ctx.under_assumption(ass_value) - && stack.created_arrays[array_id] != stack.block - { - let load = Operation::Load { array_id: *array_id, index: *index }; - let e_type = ctx.mem[*array_id].element_type; - let dummy = - ctx.add_instruction(Instruction::new(load, e_type, Some(stack.block))); - let operation = Operation::Cond { - condition: ass_value, - val_true: *value, - val_false: dummy, - }; - let cond = - ctx.add_instruction(Instruction::new(operation, e_type, Some(stack.block))); - - stack.push(dummy); - stack.push(cond); - //store the conditional value - let ins2 = ctx.get_mut_instruction(ins_id); - ins2.operation = - Operation::Store { array_id: *array_id, index: *index, value: cond }; - } - stack.push(ins_id); - } - Operation::Intrinsic(_, _) => { - stack.push(ins_id); - if ctx.under_assumption(ass_value) { - if let ObjectType::Pointer(a) = ins.res_type { - if stack.created_arrays[&a] != stack.block { - let array = &ctx.mem[a].clone(); - let name = array.name.to_string() + DUPLICATED; - ctx.new_array(&name, array.element_type, array.len, None); - let array_dup = ctx.mem.last_id(); + Operation::Store { array_id, index, value } => { + if !ins.operation.is_dummy_store() { + if let Some(idx) = ctx.get_as_constant(*index) { + if (idx.to_u128() as u32) >= ctx.mem[*array_id].len { + if !DecisionTree::short_circuit(ctx, stack, ass_value) { + unreachable!( + "index out of bounds: the len is {} but the index is {}", + ctx.mem[*array_id].len, + idx.to_u128() + ); + } + return false; + } + } + if stack.created_arrays[array_id] != stack.block + && ctx.under_assumption(ass_value) + { + let load = Operation::Load { array_id: *array_id, index: *index }; + let e_type = ctx.mem[*array_id].element_type; + let dummy = ctx.add_instruction(Instruction::new( + load, + e_type, + Some(stack.block), + )); + let operation = Operation::Cond { + condition: ass_value, + val_true: *value, + val_false: dummy, + }; + let cond = ctx.add_instruction(Instruction::new( + operation, + e_type, + Some(stack.block), + )); + + stack.push(dummy); + stack.push(cond); + //store the conditional value let ins2 = ctx.get_mut_instruction(ins_id); - ins2.res_type = ObjectType::Pointer(array_dup); - - let mut memcpy_stack = StackFrame::new(stack.block); - ctx.memcpy_inline( - ins.res_type, - ObjectType::Pointer(array_dup), - &mut memcpy_stack, - ); - self.conditionalise_inline(ctx, &memcpy_stack.stack, stack, predicate); + ins2.operation = Operation::Store { + array_id: *array_id, + index: *index, + value: cond, + }; + } + } + stack.push(ins_id); + } + Operation::Intrinsic(_, _) => { + stack.push(ins_id); + if ctx.under_assumption(ass_value) { + if let ObjectType::Pointer(a) = ins.res_type { + if stack.created_arrays[&a] != stack.block { + let array = &ctx.mem[a].clone(); + let name = array.name.to_string() + DUPLICATED; + ctx.new_array(&name, array.element_type, array.len, None); + let array_dup = ctx.mem.last_id(); + let ins2 = ctx.get_mut_instruction(ins_id); + ins2.res_type = ObjectType::Pointer(array_dup); + + let mut memcpy_stack = StackFrame::new(stack.block); + ctx.memcpy_inline( + ins.res_type, + ObjectType::Pointer(array_dup), + &mut memcpy_stack, + ); + self.conditionalise_inline( + ctx, + &memcpy_stack.stack, + stack, + predicate, + ); + } } } } - } - Operation::Call { - func_id, arguments, returned_arrays, predicate: ins_pred, .. - } => { - if ctx.under_assumption(ass_value) { - assert!(*ins_pred == AssumptionId::dummy()); - let mut ins2 = ctx.get_mut_instruction(ins_id); - ins2.operation = Operation::Call { - func_id: *func_id, - arguments: arguments.clone(), - returned_arrays: returned_arrays.clone(), - predicate, - }; + Operation::Call { + func_id, + arguments, + returned_arrays, + predicate: ins_pred, + .. + } => { + if ctx.under_assumption(ass_value) { + assert!(*ins_pred == AssumptionId::dummy()); + let mut ins2 = ctx.get_mut_instruction(ins_id); + ins2.operation = Operation::Call { + func_id: *func_id, + arguments: arguments.clone(), + returned_arrays: returned_arrays.clone(), + predicate, + }; + } + stack.push(ins_id); } - stack.push(ins_id); - } - Operation::Constrain(expr, loc) => { - if ctx.under_assumption(ass_value) { - let operation = Operation::Cond { - condition: ass_value, - val_true: *expr, - val_false: ctx.one(), - }; - let cond = ctx.add_instruction(Instruction::new( - operation, - ObjectType::Boolean, - Some(stack.block), - )); - stack.push(cond); - let ins2 = ctx.get_mut_instruction(ins_id); - ins2.operation = Operation::Constrain(cond, *loc); + Operation::Constrain(expr, loc) => { + if ctx.under_assumption(ass_value) { + let operation = Operation::Cond { + condition: ass_value, + val_true: *expr, + val_false: ctx.one(), + }; + if ctx.is_zero(*expr) { + stack.clear(); + } + let cond = ctx.add_instruction(Instruction::new( + operation, + ObjectType::Boolean, + Some(stack.block), + )); + stack.push(cond); + let ins2 = ctx.get_mut_instruction(ins_id); + ins2.operation = Operation::Constrain(cond, *loc); + if ctx.is_zero(*expr) { + stack.push(ins_id); + return false; + } + } + stack.push(ins_id); } - stack.push(ins_id); + _ => stack.push(ins_id), } - _ => stack.push(ins_id), } + + true } fn synchronise( @@ -728,11 +843,10 @@ pub fn unroll_if( let left = if_block.left.unwrap(); let right = if_block.right.unwrap(); debug_assert!(if_block.kind == BlockType::Normal); - let exit = block::find_join(ctx, if_block.left.unwrap(), if_block.right.unwrap()); + let exit = block::find_join(ctx, if_block.id); // simple mode: - if unroll_ctx.unroll_into == BlockId::dummy() || unroll_ctx.unroll_into == unroll_ctx.to_unroll - { + if unroll_ctx.unroll_into.is_dummy() || unroll_ctx.unroll_into == unroll_ctx.to_unroll { unroll_ctx.unroll_into = unroll_ctx.to_unroll; flatten::unroll_std_block(ctx, unroll_ctx)?; unroll_ctx.to_unroll = left; diff --git a/crates/noirc_evaluator/src/ssa/context.rs b/crates/noirc_evaluator/src/ssa/context.rs index b9cef1a049c..6406d11c530 100644 --- a/crates/noirc_evaluator/src/ssa/context.rs +++ b/crates/noirc_evaluator/src/ssa/context.rs @@ -243,7 +243,14 @@ impl SsaContext { pub fn print_block(&self, b: &block::BasicBlock) { println!("************* Block n.{}", b.id.0.into_raw_parts().0); println!("Assumption:{:?}", b.assumption); - for id in &b.instructions { + self.print_instructions(&b.instructions); + if b.left.is_some() { + println!("Next block: {}", b.left.unwrap().0.into_raw_parts().0); + } + } + + pub fn print_instructions(&self, instructions: &Vec) { + for id in instructions { let ins = self.get_instruction(*id); let mut str_res = if ins.res_name.is_empty() { format!("{:?}", id.0.into_raw_parts().0) @@ -258,11 +265,7 @@ impl SsaContext { let ins_str = self.operation_to_string(&ins.operation); println!("{}: {}", str_res, ins_str); } - if b.left.is_some() { - println!("Next block: {}", b.left.unwrap().0.into_raw_parts().0); - } } - pub fn print(&self, text: &str) { println!("{}", text); for (_, b) in self.blocks.iter() { @@ -672,7 +675,7 @@ impl SsaContext { self.log(enable_logging, "reduce", "\ninlining:"); inline::inline_tree(self, self.first_block, &decision)?; - block::merge_path(self, self.first_block, BlockId::dummy()); + block::merge_path(self, self.first_block, BlockId::dummy(), None); //The CFG is now fully flattened, so we keep only the first block. let mut to_remove = Vec::new(); for b in &self.blocks { @@ -697,6 +700,7 @@ impl SsaContext { Acir::print_circuit(&evaluator.gates); println!("DONE"); } + println!("ACIR gates generated : {}", evaluator.gates.len()); Ok(()) } diff --git a/crates/noirc_evaluator/src/ssa/flatten.rs b/crates/noirc_evaluator/src/ssa/flatten.rs index 26ad549fb36..140d3d2da4e 100644 --- a/crates/noirc_evaluator/src/ssa/flatten.rs +++ b/crates/noirc_evaluator/src/ssa/flatten.rs @@ -22,7 +22,7 @@ pub fn unroll_tree( unroll_into: block_id, eval_map: HashMap::new(), }; - while unroll_ctx.to_unroll != BlockId::dummy() { + while !unroll_ctx.to_unroll.is_dummy() { unroll_block(ctx, &mut unroll_ctx)?; } //clean-up @@ -60,7 +60,7 @@ pub fn unroll_until( let mut prev = BlockId::dummy(); while b != end { - assert_ne!(b, BlockId::dummy(), "could not reach end block"); + assert!(!b.is_dummy(), "could not reach end block"); prev = b; unroll_block(ctx, unroll_ctx)?; b = unroll_ctx.to_unroll; @@ -144,7 +144,7 @@ pub fn unroll_std_block( } } } - _ => todo!(), //ERROR + _ => unreachable!("Block instruction list should only only contain instruction"), } } if unroll_ctx.to_unroll != unroll_ctx.unroll_into @@ -173,7 +173,7 @@ pub fn unroll_join( assert!(join.is_join()); let body_id = join.right.unwrap(); let end = unroll_ctx.to_unroll; - if unroll_ctx.unroll_into != BlockId::dummy() { + if !unroll_ctx.unroll_into.is_dummy() { prev = unroll_ctx.unroll_into; } ssa_ctx.current_block = prev; diff --git a/crates/noirc_evaluator/src/ssa/function.rs b/crates/noirc_evaluator/src/ssa/function.rs index a672638f940..8cb276e18ec 100644 --- a/crates/noirc_evaluator/src/ssa/function.rs +++ b/crates/noirc_evaluator/src/ssa/function.rs @@ -70,7 +70,7 @@ impl SSAFunction { decision.reduce(&mut igen.context, decision.root)?; //merge blocks let to_remove = - super::block::merge_path(&mut igen.context, self.entry_block, BlockId::dummy()); + super::block::merge_path(&mut igen.context, self.entry_block, BlockId::dummy(), None); igen.context[self.entry_block].dominated.retain(|b| !to_remove.contains(b)); for i in to_remove { igen.context.remove_block(i); diff --git a/crates/noirc_evaluator/src/ssa/inline.rs b/crates/noirc_evaluator/src/ssa/inline.rs index acc9e26371b..a0d1d50a84d 100644 --- a/crates/noirc_evaluator/src/ssa/inline.rs +++ b/crates/noirc_evaluator/src/ssa/inline.rs @@ -16,7 +16,7 @@ use super::{ context::SsaContext, function, mem::{ArrayId, Memory}, - node::{self, Instruction, Mark, NodeId}, + node::{self, Instruction, Mark, NodeId, ObjectType}, }; // Number of allowed times for inlining function calls inside a code block. @@ -114,6 +114,7 @@ pub struct StackFrame { pub block: BlockId, array_map: HashMap, pub created_arrays: HashMap, + zeros: HashMap, } impl StackFrame { @@ -123,9 +124,16 @@ impl StackFrame { block, array_map: HashMap::new(), created_arrays: HashMap::new(), + zeros: HashMap::new(), } } + pub fn clear(&mut self) { + self.stack.clear(); + self.array_map.clear(); + self.created_arrays.clear(); + } + pub fn push(&mut self, ins_id: NodeId) { self.stack.push(ins_id); } @@ -153,6 +161,13 @@ impl StackFrame { ctx[block].instructions.extend_from_slice(&after); self.stack.clear(); } + + pub fn set_zero(&mut self, ctx: &mut SsaContext, o_type: ObjectType) { + self.zeros.entry(o_type).or_insert_with(|| ctx.zero_with_type(o_type)); + } + pub fn get_zero(&self, o_type: ObjectType) -> NodeId { + self.zeros[&o_type] + } } //inline a function call diff --git a/crates/noirc_evaluator/src/ssa/node.rs b/crates/noirc_evaluator/src/ssa/node.rs index 2ffebd0a3e3..3351acaf091 100644 --- a/crates/noirc_evaluator/src/ssa/node.rs +++ b/crates/noirc_evaluator/src/ssa/node.rs @@ -429,10 +429,17 @@ impl Instruction { // Delete the constrain, it is always true return Ok(NodeEval::VarOrInstruction(NodeId::dummy())); } else if obj.is_zero() { - return Err(RuntimeErrorKind::UnstructuredError { - message: "Constraint is always false".into(), + if let Some(location) = *location { + return Err(RuntimeErrorKind::UnstructuredError { + message: "Constraint is always false".into(), + } + .add_location(location)); + } else { + return Err(RuntimeErrorKind::Spanless( + "Constraint is always false".into(), + ) + .add_location(Location::dummy())); } - .add_location(*location)); } } } @@ -501,7 +508,7 @@ pub enum Operation { }, //truncate Not(NodeId), //(!) Bitwise Not - Constrain(NodeId, Location), + Constrain(NodeId, Option), //control flow Jne(NodeId, BlockId), //jump on not equal