diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index 1a1ca9eab89..1f6cca9157d 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -1,5 +1,4 @@ pub(crate) mod basic_block; -pub(crate) mod basic_block_visitors; pub(crate) mod cfg; pub(crate) mod constant; pub(crate) mod dfg; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs index f6ca293f0fd..8a3f74c4a64 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -18,12 +18,6 @@ pub(crate) struct BasicBlock { /// Instructions in the basic block. instructions: Vec, - /// A basic block is considered sealed - /// if no further predecessors will be added to it. - /// Since only filled blocks can have successors, - /// predecessors are always filled. - is_sealed: bool, - /// The terminating instruction for the basic block. /// /// This will be a control flow instruction. This is only @@ -35,14 +29,20 @@ pub(crate) struct BasicBlock { pub(crate) type BasicBlockId = Id; impl BasicBlock { + /// Create a new BasicBlock with the given parameters. + /// Parameters can also be added later via BasicBlock::add_parameter pub(crate) fn new(parameters: Vec) -> Self { - Self { parameters, instructions: Vec::new(), is_sealed: false, terminator: None } + Self { parameters, instructions: Vec::new(), terminator: None } } + /// Returns the parameters of this block pub(crate) fn parameters(&self) -> &[ValueId] { &self.parameters } + /// Adds a parameter to this BasicBlock. + /// Expects that the ValueId given should refer to a Value::Param + /// instance with its position equal to self.parameters.len(). pub(crate) fn add_parameter(&mut self, parameter: ValueId) { self.parameters.push(parameter); } @@ -52,14 +52,23 @@ impl BasicBlock { self.instructions.push(instruction); } + /// Retrieve a reference to all instructions in this block. pub(crate) fn instructions(&self) -> &[InstructionId] { &self.instructions } + /// Sets the terminator instruction of this block. + /// + /// A properly-constructed block will always terminate with a TerminatorInstruction - + /// which either jumps to another block or returns from the current function. A block + /// will only have no terminator if it is still under construction. pub(crate) fn set_terminator(&mut self, terminator: TerminatorInstruction) { self.terminator = Some(terminator); } + /// Returns the terminator of this block. + /// + /// Once this block has finished construction, this is expected to always be Some. pub(crate) fn terminator(&self) -> Option<&TerminatorInstruction> { self.terminator.as_ref() } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block_visitors.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block_visitors.rs deleted file mode 100644 index e0d5dc1b3df..00000000000 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block_visitors.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::{ - basic_block::{BasicBlock, BasicBlockId}, - instruction::TerminatorInstruction, -}; - -/// Visit all successors of a block with a given visitor closure. The closure -/// arguments are the branch instruction that is used to reach the successor, -/// and the id of the successor block itself. -pub(crate) fn visit_block_succs(basic_block: &BasicBlock, mut visit: F) { - match basic_block - .terminator() - .expect("ICE: No terminator indicates block is still under construction.") - { - TerminatorInstruction::Jmp { destination, .. } => visit(*destination), - TerminatorInstruction::JmpIf { then_destination, else_destination, .. } => { - visit(*then_destination); - visit(*else_destination); - } - TerminatorInstruction::Return { .. } => { - // The last block of the control flow - no successors - } - } -} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs index d443d574ca8..b2d16b29bfd 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs @@ -2,7 +2,6 @@ use std::collections::{HashMap, HashSet}; use super::{ basic_block::{BasicBlock, BasicBlockId}, - basic_block_visitors, function::Function, }; @@ -33,25 +32,30 @@ impl ControlFlowGraph { cfg } + /// Compute all of the edges between each block in the function fn compute(&mut self, func: &Function) { for (basic_block_id, basic_block) in func.dfg.basic_blocks_iter() { self.compute_block(basic_block_id, basic_block); } } + /// Compute all of the edges for the current block given fn compute_block(&mut self, basic_block_id: BasicBlockId, basic_block: &BasicBlock) { - basic_block_visitors::visit_block_succs(basic_block, |dest| { + for dest in basic_block.successors() { self.add_edge(basic_block_id, dest); - }); + } } + /// Clears out a given block's successors. This also removes the given block from + /// being a predecessor of any of its previous successors. fn invalidate_block_successors(&mut self, basic_block_id: BasicBlockId) { let node = self .data .get_mut(&basic_block_id) .expect("ICE: Attempted to invalidate cfg node successors for non-existent node."); - let old_successors = node.successors.clone(); - node.successors.clear(); + + let old_successors = std::mem::take(&mut node.successors); + for successor_id in old_successors { self.data .get_mut(&successor_id) @@ -71,6 +75,7 @@ impl ControlFlowGraph { self.compute_block(basic_block_id, basic_block); } + /// Add a directed edge making `from` a predecessor of `to`. fn add_edge(&mut self, from: BasicBlockId, to: BasicBlockId) { let predecessor_node = self.data.entry(from).or_default(); assert!( @@ -87,7 +92,7 @@ impl ControlFlowGraph { } /// Get an iterator over the CFG predecessors to `basic_block_id`. - pub(crate) fn pred_iter( + pub(crate) fn predecessors( &self, basic_block_id: BasicBlockId, ) -> impl ExactSizeIterator + '_ { @@ -100,7 +105,7 @@ impl ControlFlowGraph { } /// Get an iterator over the CFG successors to `basic_block_id`. - pub(crate) fn succ_iter( + pub(crate) fn successors( &self, basic_block_id: BasicBlockId, ) -> impl ExactSizeIterator + '_ { @@ -133,11 +138,11 @@ mod tests { fn jumps() { // Build function of form // fn func { - // block0(cond: u1): + // block0(cond: u1): // jmpif cond, then: block2, else: block1 - // block1(): + // block1(): // jmpif cond, then: block1, else: block2 - // block2(): + // block2(): // return () // } let func_id = Id::test_new(0); @@ -163,13 +168,13 @@ mod tests { #[allow(clippy::needless_collect)] { - let block0_predecessors: Vec<_> = cfg.pred_iter(block0_id).collect(); - let block1_predecessors: Vec<_> = cfg.pred_iter(block1_id).collect(); - let block2_predecessors: Vec<_> = cfg.pred_iter(block2_id).collect(); + let block0_predecessors: Vec<_> = cfg.predecessors(block0_id).collect(); + let block1_predecessors: Vec<_> = cfg.predecessors(block1_id).collect(); + let block2_predecessors: Vec<_> = cfg.predecessors(block2_id).collect(); - let block0_successors: Vec<_> = cfg.succ_iter(block0_id).collect(); - let block1_successors: Vec<_> = cfg.succ_iter(block1_id).collect(); - let block2_successors: Vec<_> = cfg.succ_iter(block2_id).collect(); + let block0_successors: Vec<_> = cfg.successors(block0_id).collect(); + let block1_successors: Vec<_> = cfg.successors(block1_id).collect(); + let block2_successors: Vec<_> = cfg.successors(block2_id).collect(); assert_eq!(block0_predecessors.len(), 0); assert_eq!(block1_predecessors.len(), 2); @@ -192,13 +197,13 @@ mod tests { // Modify function to form: // fn func { - // block0(cond: u1): + // block0(cond: u1): // jmpif cond, then: block1, else: ret_block - // block1(): + // block1(): // jmpif cond, then: block1, else: block2 - // block2(): + // block2(): // jmp ret_block() - // ret_block(): + // ret_block(): // return () // } let ret_block_id = func.dfg.make_block(); @@ -221,13 +226,13 @@ mod tests { #[allow(clippy::needless_collect)] { - let block0_predecessors: Vec<_> = cfg.pred_iter(block0_id).collect(); - let block1_predecessors: Vec<_> = cfg.pred_iter(block1_id).collect(); - let block2_predecessors: Vec<_> = cfg.pred_iter(block2_id).collect(); + let block0_predecessors: Vec<_> = cfg.predecessors(block0_id).collect(); + let block1_predecessors: Vec<_> = cfg.predecessors(block1_id).collect(); + let block2_predecessors: Vec<_> = cfg.predecessors(block2_id).collect(); - let block0_successors: Vec<_> = cfg.succ_iter(block0_id).collect(); - let block1_successors: Vec<_> = cfg.succ_iter(block1_id).collect(); - let block2_successors: Vec<_> = cfg.succ_iter(block2_id).collect(); + let block0_successors: Vec<_> = cfg.successors(block0_id).collect(); + let block1_successors: Vec<_> = cfg.successors(block1_id).collect(); + let block2_successors: Vec<_> = cfg.successors(block2_id).collect(); assert_eq!(block0_predecessors.len(), 0); assert_eq!(block1_predecessors.len(), 2); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs index 4c793a144da..63c1e528471 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs @@ -2,7 +2,7 @@ use acvm::FieldElement; use super::map::Id; -/// Represents a numeric constant in Ssa. Constants themselves are +/// Represents a numeric constant in SSA. Constants themselves are /// uniqued in the DataFlowGraph and immutable. /// /// This is just a thin wrapper around FieldElement so that @@ -12,10 +12,12 @@ use super::map::Id; pub(crate) struct NumericConstant(FieldElement); impl NumericConstant { + /// Create a new NumericConstant with the given Field value pub(crate) fn new(value: FieldElement) -> Self { Self(value) } + /// Retrieves the Field value for this constant pub(crate) fn value(&self) -> FieldElement { self.0 } @@ -23,6 +25,8 @@ impl NumericConstant { pub(crate) type NumericConstantId = Id; +// Implement some common numeric operations for NumericConstants +// for convenience so developers do not always have to unwrap them to use them. impl std::ops::Add for NumericConstant { type Output = NumericConstant; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 4d2ebe31efb..67569c6a4c2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -15,34 +15,10 @@ use super::{ use acvm::FieldElement; use iter_extended::vecmap; -#[derive(Debug, Default)] -/// A convenience wrapper to store `Value`s. -pub(crate) struct ValueList(Vec>); - -impl ValueList { - /// Inserts an element to the back of the list and - /// returns the `position` - pub(crate) fn push(&mut self, value: ValueId) -> usize { - self.0.push(value); - self.len() - 1 - } - - /// Returns the number of values in the list. - fn len(&self) -> usize { - self.0.len() - } - - /// Removes all items from the list. - fn clear(&mut self) { - self.0.clear(); - } - - /// Returns the ValueId's as a slice. - pub(crate) fn as_slice(&self) -> &[ValueId] { - &self.0 - } -} - +/// The DataFlowGraph contains most of the actual data in a function including +/// its blocks, instructions, and values. This struct is largely responsible for +/// owning most data in a function and handing out Ids to this data that can be +/// shared without worrying about ownership. #[derive(Debug, Default)] pub(crate) struct DataFlowGraph { /// All of the instructions in a function @@ -57,7 +33,7 @@ pub(crate) struct DataFlowGraph { /// Currently, we need to define them in a better way /// Call instructions require the func signature, but /// other instructions may need some more reading on my part - results: HashMap, + results: HashMap>, /// Storage for all of the values defined in this /// function. @@ -243,8 +219,7 @@ impl DataFlowGraph { }); // Add value to the list of results for this instruction - let actual_res_position = results.push(value_id); - assert_eq!(actual_res_position, expected_res_position); + results.push(value_id); value_id } @@ -259,6 +234,7 @@ impl DataFlowGraph { self.results.get(&instruction_id).expect("expected a list of Values").as_slice() } + /// Add a parameter to the given block pub(crate) fn add_block_parameter(&mut self, block_id: BasicBlockId, typ: Type) -> Id { let block = &mut self.blocks[block_id]; let position = block.parameters().len(); @@ -267,6 +243,8 @@ impl DataFlowGraph { parameter } + /// Insert an instruction at the end of a given block. + /// If the block already has a terminator, the instruction is inserted before the terminator. pub(crate) fn insert_instruction_in_block( &mut self, block: BasicBlockId, diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 6789e5364fe..8d90a139118 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -1,24 +1,16 @@ -use std::collections::HashMap; - use super::basic_block::BasicBlockId; use super::dfg::DataFlowGraph; -use super::instruction::InstructionId; use super::map::Id; use super::types::Type; -use noirc_errors::Location; - /// A function holds a list of instructions. /// These instructions are further grouped into Basic blocks /// -/// Like Crane-lift all functions outside of the current function is seen as external. -/// To reference external functions, one must first import the function signature -/// into the current function's context. +/// All functions outside of the current function are seen as external. +/// To reference external functions its FunctionId can be used but this +/// cannot be checked for correctness until inlining is performed. #[derive(Debug)] pub struct Function { - /// Maps instructions to source locations - source_locations: HashMap, - /// The first basic block in the function entry_block: BasicBlockId, @@ -27,6 +19,8 @@ pub struct Function { id: FunctionId, + /// The DataFlowGraph holds the majority of data pertaining to the function + /// including its blocks, instructions, and values. pub(crate) dfg: DataFlowGraph, } @@ -37,23 +31,35 @@ impl Function { pub(crate) fn new(name: String, id: FunctionId) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.make_block(); - Self { name, source_locations: HashMap::new(), id, entry_block, dfg } + Self { name, id, entry_block, dfg } } + /// The name of the function. + /// Used exclusively for debugging purposes. pub(crate) fn name(&self) -> &str { &self.name } + /// The id of the function. pub(crate) fn id(&self) -> FunctionId { self.id } + /// Retrieves the entry block of a function. + /// + /// A function's entry block contains the instructions + /// to be executed first when the function is called. + /// The function's parameters are also stored as the + /// entry block's parameters. pub(crate) fn entry_block(&self) -> BasicBlockId { self.entry_block } } /// FunctionId is a reference for a function +/// +/// This Id is how each function refers to other functions +/// within Call instructions. pub(crate) type FunctionId = Id; #[derive(Debug, Default, Clone)] diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 545519e316f..66f8b1e3b17 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -3,6 +3,11 @@ use acvm::acir::BlackBoxFunc; use super::{basic_block::BasicBlockId, map::Id, types::Type, value::ValueId}; /// Reference to an instruction +/// +/// Note that InstructionIds are not unique. That is, two InstructionIds +/// may refer to the same Instruction data. This is because, although +/// identical, instructions may have different results based on their +/// placement within a block. pub(crate) type InstructionId = Id; /// These are similar to built-ins in other languages. @@ -36,6 +41,8 @@ impl std::fmt::Display for Intrinsic { } impl Intrinsic { + /// Lookup an Intrinsic by name and return it if found. + /// If there is no such intrinsic by that name, None is returned. pub(crate) fn lookup(name: &str) -> Option { match name { "println" => Some(Intrinsic::Println), @@ -94,42 +101,6 @@ pub(crate) enum Instruction { } impl Instruction { - /// Returns the number of results that this instruction - /// produces. - pub(crate) fn num_fixed_results(&self) -> usize { - match self { - Instruction::Binary(_) => 1, - Instruction::Cast(..) => 0, - Instruction::Not(_) => 1, - Instruction::Truncate { .. } => 1, - Instruction::Constrain(_) => 0, - // This returns 0 as the result depends on the function being called - Instruction::Call { .. } => 0, - Instruction::Allocate { .. } => 1, - Instruction::Load { .. } => 1, - Instruction::Store { .. } => 0, - } - } - - /// Returns the number of arguments required for a call - pub(crate) fn num_fixed_arguments(&self) -> usize { - // Match-all fields syntax (..) is avoided on most cases of this match to ensure that - // if an extra argument is ever added to any of these variants, an error - // is issued pointing to this spot to update it here as well. - match self { - Instruction::Binary(_) => 2, - Instruction::Cast(_, _) => 1, - Instruction::Not(_) => 1, - Instruction::Truncate { value: _, bit_size: _, max_bit_size: _ } => 1, - Instruction::Constrain(_) => 1, - // This returns 0 as the arguments depend on the function being called - Instruction::Call { .. } => 0, - Instruction::Allocate { size: _ } => 1, - Instruction::Load { address: _ } => 1, - Instruction::Store { address: _, value: _ } => 2, - } - } - /// Returns the type that this instruction will return. pub(crate) fn result_type(&self) -> InstructionResultType { match self { @@ -204,6 +175,7 @@ pub(crate) struct Binary { } impl Binary { + /// The type of this Binary instruction's result pub(crate) fn result_type(&self) -> InstructionResultType { match self.operator { BinaryOp::Eq | BinaryOp::Lt => InstructionResultType::Known(Type::bool()), diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index a0ab65bf639..2e467017885 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -13,6 +13,7 @@ use super::{ value::ValueId, }; +/// Helper function for Function's Display impl to pretty-print the function with the given formatter. pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result { writeln!(f, "fn {} {} {{", function.name(), function.id())?; display_block_with_successors(function, function.entry_block(), &mut HashSet::new(), f)?; @@ -20,7 +21,7 @@ pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result } /// Displays a block followed by all of its successors recursively. -/// This uses a HashSet to keep track of the visited blocks. Otherwise, +/// This uses a HashSet to keep track of the visited blocks. Otherwise /// there would be infinite recursion for any loops in the IR. pub(crate) fn display_block_with_successors( function: &Function, @@ -39,6 +40,7 @@ pub(crate) fn display_block_with_successors( Ok(()) } +/// Display a single block. This will not display the block's successors. pub(crate) fn display_block( function: &Function, block_id: BasicBlockId, @@ -80,10 +82,12 @@ fn value_list_with_types(function: &Function, values: &[ValueId]) -> String { .join(", ") } +/// Display each value separated by a comma fn value_list(function: &Function, values: &[ValueId]) -> String { vecmap(values, |id| value(function, *id)).join(", ") } +/// Display a terminator instruction pub(crate) fn display_terminator( function: &Function, terminator: Option<&TerminatorInstruction>, @@ -109,6 +113,7 @@ pub(crate) fn display_terminator( } } +/// Display an arbitrary instruction pub(crate) fn display_instruction( function: &Function, instruction: InstructionId, diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs index 8a0f825a117..e00c25a257c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs @@ -30,18 +30,22 @@ pub(crate) enum Type { } impl Type { + /// Create a new signed integer type with the given amount of bits. pub(crate) fn signed(bit_size: u32) -> Type { Type::Numeric(NumericType::Signed { bit_size }) } + /// Create a new unsigned integer type with the given amount of bits. pub(crate) fn unsigned(bit_size: u32) -> Type { Type::Numeric(NumericType::Unsigned { bit_size }) } + /// Creates the boolean type, represented as u1. pub(crate) fn bool() -> Type { Type::unsigned(1) } + /// Creates the native field type. pub(crate) fn field() -> Type { Type::Numeric(NumericType::NativeField) } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs index 868aee2199e..f8197b06c8a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs @@ -47,6 +47,7 @@ pub(crate) enum Value { } impl Value { + /// Retrieves the type of this Value pub(crate) fn get_type(&self) -> Type { match self { Value::Instruction { typ, .. } => *typ, diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs index df80799c28a..aa67cbed583 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -27,6 +27,10 @@ pub(crate) struct FunctionBuilder { } impl FunctionBuilder { + /// Creates a new FunctionBuilder to build the function with the given FunctionId. + /// + /// This creates the new function internally so there is no need to call .new_function() + /// right after constructing a new FunctionBuilder. pub(crate) fn new(function_name: String, function_id: FunctionId) -> Self { let new_function = Function::new(function_name, function_id); let current_block = new_function.entry_block(); @@ -34,7 +38,11 @@ impl FunctionBuilder { Self { current_function: new_function, current_block, finished_functions: Vec::new() } } - /// Finish the current function and create a new function + /// Finish the current function and create a new function. + /// + /// A FunctionBuilder can always only work on one function at a time, so care + /// should be taken not to finish a function that is still in progress by calling + /// new_function before the current function is finished. pub(crate) fn new_function(&mut self, name: String, function_id: FunctionId) { let new_function = Function::new(name, function_id); self.current_block = new_function.entry_block(); @@ -43,11 +51,14 @@ impl FunctionBuilder { self.finished_functions.push(old_function); } + /// Consume the FunctionBuilder returning all the functions it has generated. pub(crate) fn finish(mut self) -> Ssa { self.finished_functions.push(self.current_function); Ssa::new(self.finished_functions) } + /// Add a parameter to the current function with the given parameter type. + /// Returns the newly-added parameter. pub(crate) fn add_parameter(&mut self, typ: Type) -> ValueId { let entry = self.current_function.entry_block(); self.current_function.dfg.add_block_parameter(entry, typ) @@ -67,14 +78,19 @@ impl FunctionBuilder { self.numeric_constant(value.into(), Type::field()) } + /// Returns the type of the given value. pub(crate) fn type_of_value(&self, value: ValueId) -> Type { self.current_function.dfg.type_of_value(value) } + /// Insert a new block into the current function and return it. + /// Note that this block is unreachable until another block is set to jump to it. pub(crate) fn insert_block(&mut self) -> BasicBlockId { self.current_function.dfg.make_block() } + /// Adds a parameter with the given type to the given block. + /// Returns the newly-added parameter. pub(crate) fn add_block_parameter(&mut self, block: BasicBlockId, typ: Type) -> ValueId { self.current_function.dfg.add_block_parameter(block, typ) } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index df54d5bd079..78c64f9fad8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -16,9 +16,17 @@ use crate::ssa_refactor::ssa_builder::FunctionBuilder; use super::value::{Tree, Value, Values}; -// TODO: Make this a threadsafe queue so we can compile functions in parallel -type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; - +/// The FunctionContext is the main context object for translating a +/// function into SSA form during the SSA-gen pass. +/// +/// This context can be used to build any amount of functions, +/// so long as it is cleared out in between each function via +/// calling self.new_function(). +/// +/// If compiling many functions across multiple threads, there should +/// be a separate FunctionContext for each thread. Each FunctionContext +/// can communicate via the SharedContext field which as its name suggests +/// is the only part of the context that needs to be shared between threads. pub(super) struct FunctionContext<'a> { definitions: HashMap, @@ -26,16 +34,50 @@ pub(super) struct FunctionContext<'a> { shared_context: &'a SharedContext, } -/// Shared context for all functions during ssa codegen +/// Shared context for all functions during ssa codegen. This is the only +/// object that is shared across all threads when generating ssa in multiple threads. +/// +/// The main job of the SharedContext is to remember which functions are already +/// compiled, what their IDs are, and keep a queue of which functions still need to +/// be compiled. +/// +/// SSA can be generated by continuously popping from this function_queue and using +/// FunctionContext to generate from the popped function id. Once the queue is empty, +/// no other functions are reachable and the SSA generation is finished. pub(super) struct SharedContext { + /// All currently known functions which have already been assigned function ids. + /// These functions are all either currently having their SSA generated or are + /// already finished. functions: RwLock>, + + /// Queue of which functions still need to be compiled. + /// + /// The queue is currently Last-in First-out (LIFO) but this is an + /// implementation detail that can be trivially changed and should + /// not impact the resulting SSA besides changing which IDs are assigned + /// to which functions. function_queue: Mutex, + + /// Shared counter used to assign the ID of the next function function_counter: AtomicCounter, + /// The entire monomorphized source program pub(super) program: Program, } +/// The queue of functions remaining to compile +type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; + impl<'a> FunctionContext<'a> { + /// Create a new FunctionContext to compile the first function in the shared_context's + /// function queue. + /// + /// This will pop from the function queue, so it is expected the shared_context's function + /// queue is non-empty at the time of calling this function. This can be ensured by calling + /// `shared_context.get_or_queue_function(function_to_queue)` before calling this constructor. + /// + /// `function_name` and `parameters` are expected to be the name and parameters of the function + /// this constructor will pop from the function queue. pub(super) fn new( function_name: String, parameters: &Parameters, @@ -52,6 +94,11 @@ impl<'a> FunctionContext<'a> { this } + /// Finish building the current function and switch to building a new function with the + /// given name, id, and parameters. + /// + /// Note that the previous function cannot be resumed after calling this. Developers should + /// avoid calling new_function until the previous function is completely finished with ssa-gen. pub(super) fn new_function(&mut self, id: IrFunctionId, name: String, parameters: &Parameters) { self.definitions.clear(); self.builder.new_function(name, id); @@ -127,6 +174,10 @@ impl<'a> FunctionContext<'a> { Self::map_type_helper(typ, &mut |x| x) } + /// Converts a non-tuple type into an SSA type. Panics if a tuple type is passed. + /// + /// This function is needed since this SSA IR has no concept of tuples and thus no type for + /// them. Use `convert_type` if tuple types need to be handled correctly. pub(super) fn convert_non_tuple_type(typ: &ast::Type) -> Type { match typ { ast::Type::Field => Type::field(), @@ -305,6 +356,7 @@ fn convert_operator(op: noirc_frontend::BinaryOpKind) -> BinaryOp { } impl SharedContext { + /// Create a new SharedContext for the given monomorphized program. pub(super) fn new(program: Program) -> Self { Self { functions: Default::default(), @@ -314,6 +366,7 @@ impl SharedContext { } } + /// Pops the next function from the shared function queue, returning None if the queue is empty. pub(super) fn pop_next_function_in_queue(&self) -> Option<(ast::FuncId, IrFunctionId)> { self.function_queue.lock().expect("Failed to lock function_queue").pop() } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 4b93a7e1185..d6c5731e147 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -16,6 +16,9 @@ use self::{ use super::ir::{instruction::BinaryOp, types::Type, value::ValueId}; +/// Generates SSA for the given monomorphized program. +/// +/// This function will generate the SSA but does not perform any optimizations on it. pub fn generate_ssa(program: Program) -> Ssa { let context = SharedContext::new(program); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/program.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/program.rs index 03eb76dec50..99d49456210 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/program.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/program.rs @@ -2,12 +2,13 @@ use std::fmt::Display; use crate::ssa_refactor::ir::function::Function; -/// Contains the entire Ssa representation of the program +/// Contains the entire SSA representation of the program. pub struct Ssa { functions: Vec, } impl Ssa { + /// Create a new Ssa object from the given SSA functions pub fn new(functions: Vec) -> Self { Self { functions } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs index fa27e70ad9b..02011adbaa8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -5,12 +5,27 @@ use crate::ssa_refactor::ir::value::ValueId as IrValueId; use super::context::FunctionContext; +/// A general Tree structure which is used in the SSA generation pass +/// to represent both values and types which may be tuples. +/// +/// Since the underlying SSA intermediate representation (IR) does not +/// support tuples directly, they're instead represented as Tree::Branch +/// nodes. For example, a single ssa value may be a Tree::Leaf(Value), +/// while a tuple would be a Tree::Branch(values). #[derive(Debug, Clone)] pub(super) enum Tree { Branch(Vec>), Leaf(T), } +/// A single value in ssa form. This wrapper enum is needed mostly to enable +/// us to automatically create a Instruction::Load whenever a mutable variable +/// is referenced. +/// +/// Note that these values wrap the ValueIds +/// used internally by functions in the ssa ir and should thus be isolated +/// to a given function. If used outisde their function of origin, the IDs +/// would be invalid. #[derive(Debug, Copy, Clone)] pub(super) enum Value { Normal(IrValueId), @@ -43,9 +58,15 @@ impl Value { } } +/// A tree of values. +/// +/// Compared to Value alone, the addition of being able to represent structs/tuples as +/// a Tree::Branch means this type can hold any kind of value an frontend expression may return. +/// This is why it is used as the return type for every codegen_* function in ssa_gen/mod.rs. pub(super) type Values = Tree; impl Tree { + /// Flattens the tree into a vector of each leaf value pub(super) fn flatten(self) -> Vec { match self { Tree::Branch(values) => values.into_iter().flat_map(Tree::flatten).collect(), @@ -53,6 +74,7 @@ impl Tree { } } + /// Returns the total amount of leaves in this tree pub(super) fn count_leaves(&self) -> usize { match self { Tree::Branch(trees) => trees.iter().map(|tree| tree.count_leaves()).sum(), @@ -72,6 +94,7 @@ impl Tree { } } + /// Map mutably over this tree, mutating each leaf value within using the given function pub(super) fn map_mut(&mut self, mut f: impl FnMut(&T) -> Tree) { self.map_mut_helper(&mut f); } @@ -83,6 +106,10 @@ impl Tree { } } + /// Calls the given function on each leaf node, mapping this tree into a new one. + /// + /// Because the given function returns a Tree rather than a U, it is possible + /// to use this function to turn Leaf nodes into either other Leaf nodes or even Branch nodes. pub(super) fn map(self, mut f: impl FnMut(T) -> Tree) -> Tree { self.map_helper(&mut f) }