diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 97de1aea8c7..ae8d49ce0d8 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1,6 +1,6 @@ use crate::brillig::brillig_ir::artifact::Label; use crate::brillig::brillig_ir::brillig_variable::{ - type_to_heap_value_type, BrilligArray, BrilligVariable, SingleAddrVariable, + type_to_heap_value_type, BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable, }; use crate::brillig::brillig_ir::registers::RegisterAllocator; @@ -853,8 +853,64 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { Instruction::EnableSideEffectsIf { .. } => { unreachable!("enable_side_effects not supported by brillig") } - Instruction::IfElse { .. } => { - unreachable!("IfElse instructions should not be possible in brillig") + Instruction::IfElse { then_condition, then_value, else_condition: _, else_value } => { + let then_condition = self.convert_ssa_single_addr_value(*then_condition, dfg); + let then_value = self.convert_ssa_value(*then_value, dfg); + let else_value = self.convert_ssa_value(*else_value, dfg); + let result = self.variables.define_variable( + self.function_context, + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + dfg, + ); + match (then_value, else_value) { + ( + BrilligVariable::SingleAddr(then_address), + BrilligVariable::SingleAddr(else_address), + ) => { + self.brillig_context.conditional_move_instruction( + then_condition, + then_address, + else_address, + result.extract_single_addr(), + ); + } + ( + BrilligVariable::BrilligArray(then_array), + BrilligVariable::BrilligArray(else_array), + ) => { + // Pointer to the array which result from the if-else + let pointer = self.brillig_context.allocate_register(); + self.brillig_context.conditional_move_instruction( + then_condition, + SingleAddrVariable::new_usize(then_array.pointer), + SingleAddrVariable::new_usize(else_array.pointer), + SingleAddrVariable::new_usize(pointer), + ); + let if_else_array = BrilligArray { pointer, size: then_array.size }; + // Copy the if-else array to the result + self.brillig_context + .call_array_copy_procedure(if_else_array, result.extract_array()); + } + ( + BrilligVariable::BrilligVector(then_vector), + BrilligVariable::BrilligVector(else_vector), + ) => { + // Pointer to the vector which result from the if-else + let pointer = self.brillig_context.allocate_register(); + self.brillig_context.conditional_move_instruction( + then_condition, + SingleAddrVariable::new_usize(then_vector.pointer), + SingleAddrVariable::new_usize(else_vector.pointer), + SingleAddrVariable::new_usize(pointer), + ); + let if_else_vector = BrilligVector { pointer }; + // Copy the if-else vector to the result + self.brillig_context + .call_vector_copy_procedure(if_else_vector, result.extract_vector()); + } + _ => unreachable!("ICE - then and else values must have the same type"), + } } Instruction::MakeArray { elements: array, typ } => { let value_id = dfg.instruction_results(instruction_id)[0]; diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 283c0d67eb8..152405ee665 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -126,6 +126,24 @@ impl DebugShow { debug_println!(self.enable_debug_trace, " MOV {}, {}", destination, source); } + /// Emits a `conditional mov` instruction. + pub(crate) fn conditional_mov_instruction( + &self, + destination: MemoryAddress, + source_a: MemoryAddress, + source_b: MemoryAddress, + condition: MemoryAddress, + ) { + debug_println!( + self.enable_debug_trace, + " {} = CONDITIONAL MOV {} then {}, else {}", + destination, + condition, + source_a, + source_b + ); + } + /// Emits a `cast` instruction. pub(crate) fn cast_instruction( &self, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index 9dd541c7180..bc2d835eb88 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -75,6 +75,28 @@ impl BrilligContext< ); } + /// Insert a conditional move instruction + pub(crate) fn conditional_move_instruction( + &mut self, + condition: SingleAddrVariable, + then_address: SingleAddrVariable, + else_address: SingleAddrVariable, + destination: SingleAddrVariable, + ) { + self.debug_show.conditional_mov_instruction( + destination.address, + then_address.address, + else_address.address, + condition.address, + ); + self.push_opcode(BrilligOpcode::ConditionalMov { + destination: destination.address, + source_a: then_address.address, + source_b: else_address.address, + condition: condition.address, + }); + } + fn binary( &mut self, lhs: SingleAddrVariable, diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index c17fc2d0b7a..16c97f12f1f 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -198,6 +198,7 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result !matches!( - instruction, - Instruction::EnableSideEffectsIf { .. } | Instruction::IfElse { .. } - ), + RuntimeType::Brillig(_) => { + !matches!(instruction, Instruction::EnableSideEffectsIf { .. }) + } } } @@ -372,6 +371,11 @@ impl DataFlowGraph { } } + /// Replace an existing instruction with a new one. + pub(crate) fn set_instruction(&mut self, id: InstructionId, instruction: Instruction) { + self.instructions[id] = instruction; + } + /// Set the value of value_to_replace to refer to the value referred to by new_value. /// /// This is the preferred method to call for optimizations simplifying diff --git a/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs b/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs index 9e4557e06a6..13b5ead5eb6 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs @@ -79,6 +79,13 @@ impl<'f> FunctionInserter<'f> { (instruction, self.function.dfg.get_instruction_call_stack_id(id)) } + /// Get an instruction, map all its values, and replace it with the resolved instruction. + pub(crate) fn map_instruction_in_place(&mut self, id: InstructionId) { + let mut instruction = self.function.dfg[id].clone(); + instruction.map_values_mut(|id| self.resolve(id)); + self.function.dfg.set_instruction(id, instruction); + } + /// Maps a terminator in place, replacing any ValueId in the terminator with the /// resolved version of that value id from this FunctionInserter's internal value mapping. pub(crate) fn map_terminator_in_place(&mut self, block: BasicBlockId) { @@ -251,4 +258,22 @@ impl<'f> FunctionInserter<'f> { self.values.entry(*param).or_insert(*new_param); } } + + /// Merge the internal mapping into the given mapping + /// The merge is guaranteed to be coherent because ambiguous cases are prevented + pub(crate) fn extract_mapping(&self, mapping: &mut HashMap) { + for (k, v) in &self.values { + if mapping.contains_key(k) { + unreachable!("cannot merge key"); + } + if mapping.contains_key(v) { + unreachable!("cannot merge value"); + } + mapping.insert(*k, *v); + } + } + + pub(crate) fn set_mapping(&mut self, mapping: HashMap) { + self.values = mapping; + } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs b/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs new file mode 100644 index 00000000000..a08ac1fbf0d --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs @@ -0,0 +1,377 @@ +use std::collections::{HashSet, VecDeque}; + +use acvm::AcirField; +use fxhash::FxHashMap as HashMap; +use iter_extended::vecmap; + +use crate::ssa::{ + ir::{ + basic_block::BasicBlockId, + cfg::ControlFlowGraph, + dfg::DataFlowGraph, + function::{Function, FunctionId}, + function_inserter::FunctionInserter, + instruction::{BinaryOp, Instruction, TerminatorInstruction}, + post_order::PostOrder, + value::ValueId, + }, + Ssa, +}; + +use super::flatten_cfg::Context; +#[derive(Debug, Clone)] +struct BasicConditional { + block_entry: BasicBlockId, + block_then: Option, + block_else: Option, + block_exit: BasicBlockId, +} + +impl Ssa { + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn flatten_basic_conditionals(mut self) -> Ssa { + // Retrieve the 'no_predicates' attribute of the functions in a map, to avoid problems with borrowing + let mut no_predicates = HashMap::default(); + for function in self.functions.values() { + no_predicates.insert(function.id(), function.is_no_predicates()); + } + for function in self.functions.values_mut() { + flatten_function(function, &mut no_predicates); + } + self + } +} + +// Returns the blocks of the simple conditional sub-graph whose input block is the entry. +// Returns None if the input block is not the entry block of a simple conditional. +fn is_conditional( + block: BasicBlockId, + cfg: &ControlFlowGraph, + function: &Function, +) -> Option { + // jump overhead is the cost for doing the conditional and jump around the blocks + // We use 10 as a rough estimate, the real cost is less. + let jump_overhead = 10; + let mut successors = cfg.successors(block); + let mut result = None; + // a conditional must have 2 branches + if successors.len() != 2 { + return None; + } + let left = successors.next().unwrap(); + let right = successors.next().unwrap(); + let mut left_successors = cfg.successors(left); + let mut right_successors = cfg.successors(right); + let left_successors_len = left_successors.len(); + let right_successors_len = right_successors.len(); + let next_left = left_successors.next(); + let next_right = right_successors.next(); + if next_left == Some(block) || next_right == Some(block) { + // this is a loop, not a conditional + return None; + } + if left_successors_len == 1 && right_successors_len == 1 && next_left == next_right { + // The branches join on one block so it is a non-nested conditional + let cost_left = block_cost(left, &function.dfg); + let cost_right = block_cost(right, &function.dfg); + // For the flattening to be valuable, we compare the cost of the flattened code with the average cost of the 2 branches, + // including an overhead to take into account the jumps between the blocks. + let cost = cost_right.saturating_add(cost_left); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + result = Some(BasicConditional { + block_entry: block, + block_then: Some(*then_destination), + block_else: Some(*else_destination), + block_exit: next_left.unwrap(), + }); + } + } + } else if left_successors_len == 1 && next_left == Some(right) { + // Left branch joins the right branch, it is a if/then statement with no else + // I am not sure whether this case can happen, but it is not difficult to handle it + let cost = block_cost(left, &function.dfg); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + let (block_then, block_else) = if left == *then_destination { + (Some(left), None) + } else if left == *else_destination { + (None, Some(left)) + } else { + return None; + }; + + result = Some(BasicConditional { + block_entry: block, + block_then, + block_else, + block_exit: right, + }); + } + } + } else if right_successors_len == 1 && next_right == Some(left) { + // Right branch joins the right branch, it is a if/else statement with no then + // I am not sure whether this case can happen, but it is not difficult to handle it + let cost = block_cost(right, &function.dfg); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + let (block_then, block_else) = if right == *then_destination { + (Some(right), None) + } else if right == *else_destination { + (None, Some(right)) + } else { + return None; + }; + result = Some(BasicConditional { + block_entry: block, + block_then, + block_else, + block_exit: right, + }); + } + } + } + // A conditional exit would have exactly 2 predecessors + result.filter(|result| cfg.predecessors(result.block_exit).len() == 2) +} + +/// Computes a cost estimate of a basic block +/// returns u32::MAX if the block has side-effect instructions +/// WARNING: the estimates are estimate of the runtime cost of each instructions, +/// 1 being the cost of the simplest instruction. These numbers can be improved. +fn block_cost(block: BasicBlockId, dfg: &DataFlowGraph) -> u32 { + let mut cost: u32 = 0; + for instruction in dfg[block].instructions() { + let instruction_cost = match &dfg[*instruction] { + Instruction::Binary(binary) => { + match binary.operator { + BinaryOp::Add { unchecked } + | BinaryOp::Sub { unchecked } + | BinaryOp::Mul { unchecked } => if unchecked { 3 } else { return u32::MAX }, + BinaryOp::Div + | BinaryOp::Mod => return u32::MAX, + BinaryOp::Eq => 1, + BinaryOp::Lt => 5, + BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor => 1, + BinaryOp::Shl + | BinaryOp::Shr => return u32::MAX, + } + }, + // A Cast can be either simplified, or lead to a truncate + Instruction::Cast(_, _) => 3, + Instruction::Not(_) => 1, + Instruction::Truncate { .. } => 7, + + Instruction::Constrain(_,_,_) + | Instruction::ConstrainNotEqual(_,_,_) + | Instruction::RangeCheck { .. } + // Calls with no-predicate set to true could be supported, but + // they are likely to be too costly anyways. Simple calls would + // have been inlined already. + | Instruction::Call { .. } + | Instruction::Load { .. } + | Instruction::Store { .. } + | Instruction::ArraySet { .. } => return u32::MAX, + + Instruction::ArrayGet { array, index } => { + // A get can fail because of out-of-bound index + let mut get_cost = u32::MAX; + // check if index is in bound + if let (Some(index), Some(len)) = (dfg.get_numeric_constant(*index), dfg.try_get_array_length(*array)) { + // The index is in-bounds + if index.to_u128() < len as u128 { + get_cost = 1; + } + } + get_cost + }, + + Instruction::Allocate + | Instruction::EnableSideEffectsIf { .. } + | Instruction::IncrementRc { .. } + | Instruction::DecrementRc { .. } + | Instruction::MakeArray { .. } + | Instruction::Noop => 0, + Instruction::IfElse { .. } => 1, + }; + cost += instruction_cost; + } + cost +} + +/// Identifies all simple conditionals in the function and flattens them +fn flatten_function(function: &mut Function, no_predicates: &mut HashMap) { + // This pass is dedicated to brillig functions + if !function.runtime().is_brillig() { + return; + } + let cfg = ControlFlowGraph::with_function(function); + let mut stack = vec![function.entry_block()]; + let mut processed = HashSet::new(); + let mut conditionals = Vec::new(); + + // 1. Process all blocks of the cfg, starting from the root and following the successors + while let Some(block) = stack.pop() { + // Avoid cycles + if processed.contains(&block) { + continue; + } + processed.insert(block); + + // Identify the simple conditionals + if let Some(conditional) = is_conditional(block, &cfg, function) { + // no need to check the branches, process the join block directly + stack.push(conditional.block_exit); + conditionals.push(conditional); + } else { + stack.extend(cfg.successors(block)); + } + } + + // 2. Flatten all simple conditionals + // process basic conditionals in reverse order so that + // a conditional does not impact the previous ones + conditionals.reverse(); + Context::flatten_multiple(&conditionals, function, no_predicates); +} + +impl<'f> Context<'f> { + fn flatten_single_conditional( + &mut self, + conditional: &BasicConditional, + no_predicates: &mut HashMap, + ) { + // Manually inline 'then', 'else' and 'exit' into the entry block + //0. initialize the context for flattening a 'single conditional' + let mut queue = vec![]; + self.target_block = conditional.block_entry; + self.no_predicate = true; + //1. process 'then' branch + self.inline_block(conditional.block_entry, no_predicates); + let to_process = self.handle_terminator(conditional.block_entry, &queue); + queue.extend(to_process); + if let Some(then) = conditional.block_then { + assert_eq!(queue.pop(), conditional.block_then); + self.inline_block(then, no_predicates); + let to_process = self.handle_terminator(then, &queue); + + for incoming_block in to_process { + if !queue.contains(&incoming_block) { + queue.push(incoming_block); + } + } + } + + //2. process 'else' branch, in case there is no 'then' + let next = queue.pop(); + if next == conditional.block_else { + let next = next.unwrap(); + self.inline_block(next, no_predicates); + let to_process = self.handle_terminator(next, &queue); + for incoming_block in to_process { + if !queue.contains(&incoming_block) { + queue.push(incoming_block); + } + } + } else { + assert_eq!(next, Some(conditional.block_exit)); + } + + //3. process 'exit' block + self.inline_block(conditional.block_exit, no_predicates); + // Manually set the terminator of the entry block to the one of the exit block + let terminator = + self.inserter.function.dfg[conditional.block_exit].terminator().unwrap().clone(); + let mut next_blocks = VecDeque::new(); + let new_terminator = match terminator { + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } => { + let condition = self.inserter.resolve(condition); + next_blocks.extend([then_destination, else_destination]); + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } + } + TerminatorInstruction::Jmp { destination, arguments, call_stack } => { + let arguments = vecmap(arguments, |value| self.inserter.resolve(value)); + next_blocks.push_back(destination); + TerminatorInstruction::Jmp { destination, arguments, call_stack } + } + TerminatorInstruction::Return { return_values, call_stack } => { + let return_values = vecmap(return_values, |value| self.inserter.resolve(value)); + TerminatorInstruction::Return { return_values, call_stack } + } + }; + self.inserter.function.dfg.set_block_terminator(conditional.block_entry, new_terminator); + self.inserter.map_data_bus_in_place(); + } + + fn map_block_with_mapping( + mapping: HashMap, + func: &mut Function, + block: BasicBlockId, + ) { + // Map all instructions in the block + let mut inserter = FunctionInserter::new(func); + inserter.set_mapping(mapping); + let instructions = inserter.function.dfg[block].instructions().to_vec(); + for instruction in instructions { + inserter.map_instruction_in_place(instruction); + } + inserter.map_terminator_in_place(block); + } + + fn flatten_multiple( + conditionals: &Vec, + function: &mut Function, + no_predicates: &mut HashMap, + ) { + // 1. process each basic conditional, using a new context per conditional + let post_order = PostOrder::with_function(function); + + let mut mapping = HashMap::default(); + for conditional in conditionals { + let cfg = ControlFlowGraph::with_function(function); + let cfg_root = function.entry_block(); + let mut branch_ends = HashMap::default(); + branch_ends.insert(conditional.block_entry, conditional.block_exit); + let mut context = Context::new(function, cfg, branch_ends, cfg_root); + context.flatten_single_conditional(conditional, no_predicates); + // extract the mapping into 'mapping + context.inserter.extract_mapping(&mut mapping); + } + // 2. re-map the full program for values that may been simplified. + if !mapping.is_empty() { + for block in post_order.as_slice() { + Context::map_block_with_mapping(mapping.clone(), function, *block); + } + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 76f8495c009..a20235f06bc 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -176,12 +176,15 @@ impl Ssa { } } -struct Context<'f> { - inserter: FunctionInserter<'f>, +pub(crate) struct Context<'f> { + pub(crate) inserter: FunctionInserter<'f>, /// This ControlFlowGraph is the graph from before the function was modified by this flattening pass. cfg: ControlFlowGraph, + /// Target block of the flattening + pub(crate) target_block: BasicBlockId, + /// Maps start of branch -> end of branch branch_ends: HashMap, @@ -213,6 +216,10 @@ struct Context<'f> { /// us from unnecessarily inserting extra instructions, and keeps ids unique which /// helps simplifications. not_instructions: HashMap, + + /// Flag to tell the context to not issue 'enable_side_effect' instructions during flattening. + /// This should set to true only by flatten_single(), when none instruction is known to fail. + pub(crate) no_predicate: bool, } #[derive(Clone)] @@ -249,6 +256,7 @@ fn flatten_function_cfg(function: &mut Function, no_predicates: &HashMap Context<'f> { - fn flatten(&mut self, no_predicates: &HashMap) { + pub(crate) fn new( + function: &'f mut Function, + cfg: ControlFlowGraph, + branch_ends: HashMap, + target_block: BasicBlockId, + ) -> Self { + Context { + inserter: FunctionInserter::new(function), + cfg, + branch_ends, + condition_stack: Vec::new(), + arguments_stack: Vec::new(), + local_allocations: HashSet::default(), + not_instructions: HashMap::default(), + target_block, + no_predicate: false, + } + } + + pub(crate) fn flatten(&mut self, no_predicates: &HashMap) { // Flatten the CFG by inlining all instructions from the queued blocks // until all blocks have been flattened. // We follow the terminator of each block to determine which blocks to // process next - let mut queue = vec![self.inserter.function.entry_block()]; + let mut queue = vec![self.target_block]; while let Some(block) = queue.pop() { self.inline_block(block, no_predicates); let to_process = self.handle_terminator(block, &queue); @@ -318,10 +347,14 @@ impl<'f> Context<'f> { result } - // Inline all instructions from the given block into the entry block, and track slice capacities - fn inline_block(&mut self, block: BasicBlockId, no_predicates: &HashMap) { - if self.inserter.function.entry_block() == block { - // we do not inline the entry block into itself + // Inline all instructions from the given block into the target block, and track slice capacities + pub(crate) fn inline_block( + &mut self, + block: BasicBlockId, + no_predicates: &HashMap, + ) { + if self.target_block == block { + // we do not inline the target block into itself // for the outer block before we start inlining return; } @@ -354,7 +387,7 @@ impl<'f> Context<'f> { /// For a normal block, it would be its successor /// For blocks related to a conditional statement, we ensure to process /// the 'then-branch', then the 'else-branch' (if it exists), and finally the end block - fn handle_terminator( + pub(crate) fn handle_terminator( &mut self, block: BasicBlockId, work_list: &[BasicBlockId], @@ -388,9 +421,9 @@ impl<'f> Context<'f> { let return_values = vecmap(return_values.clone(), |value| self.inserter.resolve(value)); let new_return = TerminatorInstruction::Return { return_values, call_stack }; - let entry = self.inserter.function.entry_block(); + let target = self.target_block; - self.inserter.function.dfg.set_block_terminator(entry, new_return); + self.inserter.function.dfg.set_block_terminator(target, new_return); vec![] } } @@ -544,7 +577,7 @@ impl<'f> Context<'f> { } else { self.inserter.function.dfg.make_constant(FieldElement::zero(), NumericType::bool()) }; - let block = self.inserter.function.entry_block(); + let block = self.target_block; // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { @@ -568,11 +601,11 @@ impl<'f> Context<'f> { destination } - /// Insert a new instruction into the function's entry block. + /// Insert a new instruction into the target block. /// Unlike push_instruction, this function will not map any ValueIds. /// within the given instruction, nor will it modify self.values in any way. fn insert_instruction(&mut self, instruction: Instruction, call_stack: CallStackId) -> ValueId { - let block = self.inserter.function.entry_block(); + let block = self.target_block; self.inserter .function .dfg @@ -580,7 +613,7 @@ impl<'f> Context<'f> { .first() } - /// Inserts a new instruction into the function's entry block, using the given + /// Inserts a new instruction into the target block, using the given /// control type variables to specify result types if needed. /// Unlike push_instruction, this function will not map any ValueIds. /// within the given instruction, nor will it modify self.values in any way. @@ -590,7 +623,7 @@ impl<'f> Context<'f> { ctrl_typevars: Option>, call_stack: CallStackId, ) -> InsertInstructionResult { - let block = self.inserter.function.entry_block(); + let block = self.target_block; self.inserter.function.dfg.insert_instruction_and_results( instruction, block, @@ -600,11 +633,14 @@ impl<'f> Context<'f> { } /// Checks the branch condition on the top of the stack and uses it to build and insert an - /// `EnableSideEffectsIf` instruction into the entry block. + /// `EnableSideEffectsIf` instruction into the target block. /// /// If the stack is empty, a "true" u1 constant is taken to be the active condition. This is /// necessary for re-enabling side-effects when re-emerging to a branch depth of 0. fn insert_current_side_effects_enabled(&mut self) { + if self.no_predicate { + return; + } let condition = match self.get_last_condition() { Some(cond) => cond, None => { @@ -616,7 +652,7 @@ impl<'f> Context<'f> { self.insert_instruction_with_typevars(enable_side_effects, None, call_stack); } - /// Push the given instruction to the end of the entry block of the current function. + /// Push the given instruction to the end of the target block of the current function. /// /// Note that each ValueId of the instruction will be mapped via self.inserter.resolve. /// As a result, the instruction that will be pushed will actually be a new instruction @@ -631,8 +667,8 @@ impl<'f> Context<'f> { let instruction = self.handle_instruction_side_effects(instruction, call_stack); let instruction_is_allocate = matches!(&instruction, Instruction::Allocate); - let entry = self.inserter.function.entry_block(); - let results = self.inserter.push_instruction_value(instruction, id, entry, call_stack); + let results = + self.inserter.push_instruction_value(instruction, id, self.target_block, call_stack); // Remember an allocate was created local to this branch so that we do not try to merge store // values across branches for it later. diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 7ec419890c0..3f835fcdfdb 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -7,6 +7,7 @@ mod array_set; mod as_slice_length; mod assert_constant; +mod basic_conditional; mod constant_folding; mod defunctionalize; mod die;