diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs index 3e469361c37..42a2cd573a1 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs @@ -115,13 +115,14 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { - use crate::ssa_refactor::ir::{instruction::TerminatorInstruction, types::Type}; + use crate::ssa_refactor::ir::{instruction::TerminatorInstruction, map::Id, types::Type}; use super::{super::function::Function, ControlFlowGraph}; #[test] fn empty() { - let mut func = Function::new("func".into()); + let func_id = Id::test_new(0); + let mut func = Function::new("func".into(), func_id); let block_id = func.entry_block(); func.dfg[block_id].set_terminator(TerminatorInstruction::Return { return_values: vec![] }); @@ -139,7 +140,8 @@ mod tests { // block2(): // return () // } - let mut func = Function::new("func".into()); + let func_id = Id::test_new(0); + let mut func = Function::new("func".into(), func_id); let block0_id = func.entry_block(); let cond = func.dfg.add_block_parameter(block0_id, Type::unsigned(1)); let block1_id = func.dfg.make_block(); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 1a735726029..ca486d0258a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -18,10 +18,12 @@ pub(crate) struct Function { source_locations: SecondaryMap, /// The first basic block in the function - pub(super) entry_block: BasicBlockId, + entry_block: BasicBlockId, /// Name of the function for debugging only - pub(super) name: String, + name: String, + + id: FunctionId, pub(crate) dfg: DataFlowGraph, } @@ -30,10 +32,18 @@ impl Function { /// Creates a new function with an automatically inserted entry block. /// /// Note that any parameters to the function must be manually added later. - pub(crate) fn new(name: String) -> Self { + pub(crate) fn new(name: String, id: FunctionId) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.make_block(); - Self { name, source_locations: SecondaryMap::new(), entry_block, dfg } + Self { name, source_locations: SecondaryMap::new(), id, entry_block, dfg } + } + + pub(crate) fn name(&self) -> &str { + &self.name + } + + pub(crate) fn id(&self) -> FunctionId { + self.id } pub(crate) fn entry_block(&self) -> BasicBlockId { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs index 57c573c7bd4..ff46b49b9b4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -14,8 +14,8 @@ use super::{ }; pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result { - writeln!(f, "fn {} {{", function.name)?; - display_block_with_successors(function, function.entry_block, &mut HashSet::new(), f)?; + writeln!(f, "fn {} {} {{", function.name(), function.id())?; + display_block_with_successors(function, function.entry_block(), &mut HashSet::new(), f)?; write!(f, "}}") } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs deleted file mode 100644 index d11e9a763cd..00000000000 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ /dev/null @@ -1,233 +0,0 @@ -use acvm::FieldElement; - -use crate::ssa_refactor::ir::{ - basic_block::BasicBlockId, - function::{Function, FunctionId}, - instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, - types::Type, - value::{Value, ValueId}, -}; - -use super::SharedBuilderContext; - -/// The per-function context for each ssa function being generated. -/// -/// This is split from the global SsaBuilder context to allow each function -/// to be potentially built concurrently. -/// -/// Contrary to the name, this struct has the capacity to build as many -/// functions as needed, although it is limited to one function at a time. -pub(crate) struct FunctionBuilder<'ssa> { - global_context: &'ssa SharedBuilderContext, - - current_function: Function, - current_function_id: FunctionId, - - current_block: BasicBlockId, - - finished_functions: Vec<(FunctionId, Function)>, -} - -impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(function_name: String, context: &'ssa SharedBuilderContext) -> Self { - let new_function = Function::new(function_name); - let current_block = new_function.entry_block(); - - Self { - global_context: context, - current_function: new_function, - current_function_id: context.next_function(), - current_block, - finished_functions: Vec::new(), - } - } - - /// Finish the current function and create a new function - pub(crate) fn new_function(&mut self, name: String) { - let new_function = Function::new(name); - let old_function = std::mem::replace(&mut self.current_function, new_function); - - self.finished_functions.push((self.current_function_id, old_function)); - self.current_function_id = self.global_context.next_function(); - } - - pub(crate) fn finish(mut self) -> Vec<(FunctionId, Function)> { - self.finished_functions.push((self.current_function_id, self.current_function)); - self.finished_functions - } - - 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) - } - - /// Insert a numeric constant into the current function - pub(crate) fn numeric_constant( - &mut self, - value: impl Into, - typ: Type, - ) -> ValueId { - self.current_function.dfg.make_constant(value.into(), typ) - } - - /// Insert a numeric constant into the current function of type Field - pub(crate) fn field_constant(&mut self, value: impl Into) -> ValueId { - self.numeric_constant(value.into(), Type::field()) - } - - pub(crate) fn type_of_value(&self, value: ValueId) -> Type { - self.current_function.dfg.type_of_value(value) - } - - pub(crate) fn insert_block(&mut self) -> BasicBlockId { - self.current_function.dfg.make_block() - } - - pub(crate) fn add_block_parameter(&mut self, block: BasicBlockId, typ: Type) -> ValueId { - self.current_function.dfg.add_block_parameter(block, typ) - } - - /// Inserts a new instruction at the end of the current block and returns its results - fn insert_instruction( - &mut self, - instruction: Instruction, - ctrl_typevars: Option>, - ) -> &[ValueId] { - let id = self.current_function.dfg.make_instruction(instruction, ctrl_typevars); - self.current_function.dfg.insert_instruction_in_block(self.current_block, id); - self.current_function.dfg.instruction_results(id) - } - - /// Switch to inserting instructions in the given block. - /// Expects the given block to be within the same function. If you want to insert - /// instructions into a new function, call new_function instead. - pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) { - self.current_block = block; - } - - /// Insert an allocate instruction at the end of the current block, allocating the - /// given amount of field elements. Returns the result of the allocate instruction, - /// which is always a Reference to the allocated data. - pub(crate) fn insert_allocate(&mut self, size_to_allocate: u32) -> ValueId { - self.insert_instruction(Instruction::Allocate { size: size_to_allocate }, None)[0] - } - - /// Insert a Load instruction at the end of the current block, loading from the given offset - /// of the given address which should point to a previous Allocate instruction. Note that - /// this is limited to loading a single value. Loading multiple values (such as a tuple) - /// will require multiple loads. - /// 'offset' is in units of FieldElements here. So loading the fourth FieldElement stored in - /// an array will have an offset of 3. - /// Returns the element that was loaded. - pub(crate) fn insert_load( - &mut self, - mut address: ValueId, - offset: ValueId, - type_to_load: Type, - ) -> ValueId { - if let Some(offset) = self.current_function.dfg.get_numeric_constant(offset) { - if !offset.is_zero() { - let offset = self.field_constant(offset); - address = self.insert_binary(address, BinaryOp::Add, offset); - } - }; - self.insert_instruction(Instruction::Load { address }, Some(vec![type_to_load]))[0] - } - - /// Insert a Store instruction at the end of the current block, storing the given element - /// at the given address. Expects that the address points somewhere - /// within a previous Allocate instruction. - pub(crate) fn insert_store(&mut self, address: ValueId, value: ValueId) { - self.insert_instruction(Instruction::Store { address, value }, None); - } - - /// Insert a binary instruction at the end of the current block. - /// Returns the result of the binary instruction. - pub(crate) fn insert_binary( - &mut self, - lhs: ValueId, - operator: BinaryOp, - rhs: ValueId, - ) -> ValueId { - let instruction = Instruction::Binary(Binary { lhs, rhs, operator }); - self.insert_instruction(instruction, None)[0] - } - - /// Insert a not instruction at the end of the current block. - /// Returns the result of the instruction. - pub(crate) fn insert_not(&mut self, rhs: ValueId) -> ValueId { - self.insert_instruction(Instruction::Not(rhs), None)[0] - } - - /// Insert a cast instruction at the end of the current block. - /// Returns the result of the cast instruction. - pub(crate) fn insert_cast(&mut self, value: ValueId, typ: Type) -> ValueId { - self.insert_instruction(Instruction::Cast(value, typ), None)[0] - } - - /// Insert a constrain instruction at the end of the current block. - pub(crate) fn insert_constrain(&mut self, boolean: ValueId) { - self.insert_instruction(Instruction::Constrain(boolean), None); - } - - /// Terminates the current block with the given terminator instruction - fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { - self.current_function.dfg.set_block_terminator(self.current_block, terminator); - } - - /// Terminate the current block with a jmp instruction to jmp to the given - /// block with the given arguments. - pub(crate) fn terminate_with_jmp( - &mut self, - destination: BasicBlockId, - arguments: Vec, - ) { - self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments }); - } - - /// Terminate the current block with a jmpif instruction to jmp with the given arguments - /// block with the given arguments. - pub(crate) fn terminate_with_jmpif( - &mut self, - condition: ValueId, - then_destination: BasicBlockId, - else_destination: BasicBlockId, - ) { - self.terminate_block_with(TerminatorInstruction::JmpIf { - condition, - then_destination, - else_destination, - }); - } - - /// Terminate the current block with a return instruction - pub(crate) fn terminate_with_return(&mut self, return_values: Vec) { - self.terminate_block_with(TerminatorInstruction::Return { return_values }); - } - - /// Mutates a load instruction into a store instruction. - /// - /// This function is used while generating ssa-form for assignments currently. - /// To re-use most of the expression infrastructure, the lvalue of an assignment - /// is compiled as an expression and to assign to it we replace the final load - /// (which should always be present to load a mutable value) with a store of the - /// assigned value. - pub(crate) fn mutate_load_into_store(&mut self, load_result: ValueId, value_to_store: ValueId) { - let (instruction, address) = match &self.current_function.dfg[load_result] { - Value::Instruction { instruction, .. } => { - match &self.current_function.dfg[*instruction] { - Instruction::Load { address } => (*instruction, *address), - other => { - panic!("mutate_load_into_store: Expected Load instruction, found {other:?}") - } - } - } - other => panic!("mutate_load_into_store: Expected Load instruction, found {other:?}"), - }; - - let store = Instruction::Store { address, value: value_to_store }; - self.current_function.dfg.replace_instruction(instruction, store); - // Clear the results of the previous load for safety - self.current_function.dfg.make_instruction_results(instruction, None); - } -} 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 8f9ceed800e..fdbaa36308b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -1,19 +1,230 @@ -pub(crate) mod function_builder; +use acvm::FieldElement; use crate::ssa_refactor::ir::{ + basic_block::BasicBlockId, function::{Function, FunctionId}, - map::AtomicCounter, + instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, + types::Type, + value::{Value, ValueId}, }; -/// The global context while building the ssa representation. -/// Because this may be shared across threads, it is synchronized internally as necessary. -#[derive(Default)] -pub(crate) struct SharedBuilderContext { - function_count: AtomicCounter, +/// The per-function context for each ssa function being generated. +/// +/// This is split from the global SsaBuilder context to allow each function +/// to be potentially built concurrently. +/// +/// Contrary to the name, this struct has the capacity to build as many +/// functions as needed, although it is limited to one function at a time. +pub(crate) struct FunctionBuilder { + current_function: Function, + current_block: BasicBlockId, + finished_functions: Vec<(FunctionId, Function)>, } -impl SharedBuilderContext { - pub(super) fn next_function(&self) -> FunctionId { - self.function_count.next() +impl 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(); + + Self { current_function: new_function, current_block, finished_functions: Vec::new() } + } + + /// Finish the current function and create a new function + pub(crate) fn new_function(&mut self, name: String, function_id: FunctionId) { + let new_function = Function::new(name, function_id); + let old_function = std::mem::replace(&mut self.current_function, new_function); + + self.finished_functions.push((self.current_function.id(), old_function)); + } + + pub(crate) fn finish(mut self) -> Vec<(FunctionId, Function)> { + self.finished_functions.push((self.current_function.id(), self.current_function)); + self.finished_functions + } + + 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) + } + + /// Insert a numeric constant into the current function + pub(crate) fn numeric_constant( + &mut self, + value: impl Into, + typ: Type, + ) -> ValueId { + self.current_function.dfg.make_constant(value.into(), typ) + } + + /// Insert a numeric constant into the current function of type Field + pub(crate) fn field_constant(&mut self, value: impl Into) -> ValueId { + self.numeric_constant(value.into(), Type::field()) + } + + pub(crate) fn type_of_value(&self, value: ValueId) -> Type { + self.current_function.dfg.type_of_value(value) + } + + pub(crate) fn insert_block(&mut self) -> BasicBlockId { + self.current_function.dfg.make_block() + } + + pub(crate) fn add_block_parameter(&mut self, block: BasicBlockId, typ: Type) -> ValueId { + self.current_function.dfg.add_block_parameter(block, typ) + } + + /// Inserts a new instruction at the end of the current block and returns its results + fn insert_instruction( + &mut self, + instruction: Instruction, + ctrl_typevars: Option>, + ) -> &[ValueId] { + let id = self.current_function.dfg.make_instruction(instruction, ctrl_typevars); + self.current_function.dfg.insert_instruction_in_block(self.current_block, id); + self.current_function.dfg.instruction_results(id) + } + + /// Switch to inserting instructions in the given block. + /// Expects the given block to be within the same function. If you want to insert + /// instructions into a new function, call new_function instead. + pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) { + self.current_block = block; + } + + /// Insert an allocate instruction at the end of the current block, allocating the + /// given amount of field elements. Returns the result of the allocate instruction, + /// which is always a Reference to the allocated data. + pub(crate) fn insert_allocate(&mut self, size_to_allocate: u32) -> ValueId { + self.insert_instruction(Instruction::Allocate { size: size_to_allocate }, None)[0] + } + + /// Insert a Load instruction at the end of the current block, loading from the given offset + /// of the given address which should point to a previous Allocate instruction. Note that + /// this is limited to loading a single value. Loading multiple values (such as a tuple) + /// will require multiple loads. + /// 'offset' is in units of FieldElements here. So loading the fourth FieldElement stored in + /// an array will have an offset of 3. + /// Returns the element that was loaded. + pub(crate) fn insert_load( + &mut self, + mut address: ValueId, + offset: ValueId, + type_to_load: Type, + ) -> ValueId { + if let Some(offset) = self.current_function.dfg.get_numeric_constant(offset) { + if !offset.is_zero() { + let offset = self.field_constant(offset); + address = self.insert_binary(address, BinaryOp::Add, offset); + } + }; + self.insert_instruction(Instruction::Load { address }, Some(vec![type_to_load]))[0] + } + + /// Insert a Store instruction at the end of the current block, storing the given element + /// at the given address. Expects that the address points somewhere + /// within a previous Allocate instruction. + pub(crate) fn insert_store(&mut self, address: ValueId, value: ValueId) { + self.insert_instruction(Instruction::Store { address, value }, None); + } + + /// Insert a binary instruction at the end of the current block. + /// Returns the result of the binary instruction. + pub(crate) fn insert_binary( + &mut self, + lhs: ValueId, + operator: BinaryOp, + rhs: ValueId, + ) -> ValueId { + let instruction = Instruction::Binary(Binary { lhs, rhs, operator }); + self.insert_instruction(instruction, None)[0] + } + + /// Insert a not instruction at the end of the current block. + /// Returns the result of the instruction. + pub(crate) fn insert_not(&mut self, rhs: ValueId) -> ValueId { + self.insert_instruction(Instruction::Not(rhs), None)[0] + } + + /// Insert a cast instruction at the end of the current block. + /// Returns the result of the cast instruction. + pub(crate) fn insert_cast(&mut self, value: ValueId, typ: Type) -> ValueId { + self.insert_instruction(Instruction::Cast(value, typ), None)[0] + } + + /// Insert a constrain instruction at the end of the current block. + pub(crate) fn insert_constrain(&mut self, boolean: ValueId) { + self.insert_instruction(Instruction::Constrain(boolean), None); + } + + /// Insert a call instruction a the end of the current block and return + /// the results of the call. + pub(crate) fn insert_call( + &mut self, + func: FunctionId, + arguments: Vec, + result_types: Vec, + ) -> &[ValueId] { + self.insert_instruction(Instruction::Call { func, arguments }, Some(result_types)) + } + + /// Terminates the current block with the given terminator instruction + fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { + self.current_function.dfg.set_block_terminator(self.current_block, terminator); + } + + /// Terminate the current block with a jmp instruction to jmp to the given + /// block with the given arguments. + pub(crate) fn terminate_with_jmp( + &mut self, + destination: BasicBlockId, + arguments: Vec, + ) { + self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments }); + } + + /// Terminate the current block with a jmpif instruction to jmp with the given arguments + /// block with the given arguments. + pub(crate) fn terminate_with_jmpif( + &mut self, + condition: ValueId, + then_destination: BasicBlockId, + else_destination: BasicBlockId, + ) { + self.terminate_block_with(TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + }); + } + + /// Terminate the current block with a return instruction + pub(crate) fn terminate_with_return(&mut self, return_values: Vec) { + self.terminate_block_with(TerminatorInstruction::Return { return_values }); + } + + /// Mutates a load instruction into a store instruction. + /// + /// This function is used while generating ssa-form for assignments currently. + /// To re-use most of the expression infrastructure, the lvalue of an assignment + /// is compiled as an expression and to assign to it we replace the final load + /// (which should always be present to load a mutable value) with a store of the + /// assigned value. + pub(crate) fn mutate_load_into_store(&mut self, load_result: ValueId, value_to_store: ValueId) { + let (instruction, address) = match &self.current_function.dfg[load_result] { + Value::Instruction { instruction, .. } => { + match &self.current_function.dfg[*instruction] { + Instruction::Load { address } => (*instruction, *address), + other => { + panic!("mutate_load_into_store: Expected Load instruction, found {other:?}") + } + } + } + other => panic!("mutate_load_into_store: Expected Load instruction, found {other:?}"), + }; + + let store = Instruction::Store { address, value: value_to_store }; + self.current_function.dfg.replace_instruction(instruction, store); + // Clear the results of the previous load for safety + self.current_function.dfg.make_instruction_results(instruction, None); } } 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 10206e28c2d..df5329fed92 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -6,13 +6,13 @@ use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; use noirc_frontend::Signedness; +use crate::ssa_refactor::ir::function::Function; +use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; use crate::ssa_refactor::ir::instruction::BinaryOp; +use crate::ssa_refactor::ir::map::AtomicCounter; use crate::ssa_refactor::ir::types::Type; use crate::ssa_refactor::ir::value::ValueId; -use crate::ssa_refactor::ssa_builder::SharedBuilderContext; -use crate::ssa_refactor::{ - ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, -}; +use crate::ssa_refactor::ssa_builder::FunctionBuilder; use super::value::{Tree, Values}; @@ -22,7 +22,7 @@ type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; pub(super) struct FunctionContext<'a> { definitions: HashMap, - pub(super) builder: FunctionBuilder<'a>, + pub(super) builder: FunctionBuilder, shared_context: &'a SharedContext, } @@ -30,28 +30,32 @@ pub(super) struct FunctionContext<'a> { pub(super) struct SharedContext { functions: RwLock>, function_queue: Mutex, + function_counter: AtomicCounter, + pub(super) program: Program, } impl<'a> FunctionContext<'a> { pub(super) fn new( + function_id: FuncId, function_name: String, parameters: &Parameters, shared_context: &'a SharedContext, - shared_builder_context: &'a SharedBuilderContext, ) -> Self { + let new_id = shared_context.get_or_queue_function(function_id); + let mut this = Self { definitions: HashMap::new(), - builder: FunctionBuilder::new(function_name, shared_builder_context), + builder: FunctionBuilder::new(function_name, new_id), shared_context, }; this.add_parameters_to_scope(parameters); this } - pub(super) fn new_function(&mut self, name: String, parameters: &Parameters) { + pub(super) fn new_function(&mut self, id: IrFunctionId, name: String, parameters: &Parameters) { self.definitions.clear(); - self.builder.new_function(name); + self.builder.new_function(name, id); self.add_parameters_to_scope(parameters); } @@ -72,7 +76,7 @@ impl<'a> FunctionContext<'a> { fn add_parameter_to_scope(&mut self, parameter_id: LocalId, parameter_type: &ast::Type) { // Add a separate parameter for each field type in 'parameter_type' let parameter_value = - self.map_type(parameter_type, |this, typ| this.builder.add_parameter(typ).into()); + Self::map_type(parameter_type, |typ| self.builder.add_parameter(typ).into()); self.definitions.insert(parameter_id, parameter_value); } @@ -81,12 +85,8 @@ impl<'a> FunctionContext<'a> { /// /// This can be used to (for example) flatten a tuple type, creating /// and returning a new parameter for each field type. - pub(super) fn map_type( - &mut self, - typ: &ast::Type, - mut f: impl FnMut(&mut Self, Type) -> T, - ) -> Tree { - Self::map_type_helper(typ, &mut |typ| f(self, typ)) + pub(super) fn map_type(typ: &ast::Type, mut f: impl FnMut(Type) -> T) -> Tree { + Self::map_type_helper(typ, &mut f) } // This helper is needed because we need to take f by mutable reference, @@ -157,6 +157,30 @@ impl<'a> FunctionContext<'a> { result.into() } + /// Inserts a call instruction at the end of the current block and returns the results + /// of the call. + /// + /// Compared to self.builder.insert_call, this version will reshape the returned Vec + /// back into a Values tree of the proper shape. + pub(super) fn insert_call( + &mut self, + function: IrFunctionId, + arguments: Vec, + result_type: &ast::Type, + ) -> Values { + let result_types = Self::convert_type(result_type).flatten(); + let results = self.builder.insert_call(function, arguments, result_types); + + let mut i = 0; + let reshaped_return_values = Self::map_type(result_type, |_| { + let result = results[i].into(); + i += 1; + result + }); + assert_eq!(i, results.len()); + reshaped_return_values + } + /// Create a const offset of an address for an array load or store pub(super) fn make_offset(&mut self, mut address: ValueId, offset: u128) -> ValueId { if offset != 0 { @@ -215,6 +239,13 @@ impl<'a> FunctionContext<'a> { } } } + + /// Retrieves the given function, adding it to the function queue + /// if it is not yet compiled. + pub(super) fn get_or_queue_function(&self, id: FuncId) -> Values { + let function = self.shared_context.get_or_queue_function(id); + Values::Leaf(super::value::Value::Function(function)) + } } /// True if the given operator cannot be encoded directly and needs @@ -260,10 +291,38 @@ fn convert_operator(op: noirc_frontend::BinaryOpKind) -> BinaryOp { impl SharedContext { pub(super) fn new(program: Program) -> Self { - Self { functions: Default::default(), function_queue: Default::default(), program } + Self { + functions: Default::default(), + function_queue: Default::default(), + function_counter: Default::default(), + program, + } } pub(super) fn pop_next_function_in_queue(&self) -> Option<(ast::FuncId, IrFunctionId)> { self.function_queue.lock().expect("Failed to lock function_queue").pop() } + + /// Return the matching id for the given function if known. If it is not known this + /// will add the function to the queue of functions to compile, assign it a new id, + /// and return this new id. + pub(super) fn get_or_queue_function(&self, id: ast::FuncId) -> IrFunctionId { + // Start a new block to guarantee the destructor for the map lock is released + // before map needs to be aquired again in self.functions.write() below + { + let map = self.functions.read().expect("Failed to read self.functions"); + if let Some(existing_id) = map.get(&id) { + return *existing_id; + } + } + + let next_id = self.function_counter.next(); + + let mut queue = self.function_queue.lock().expect("Failed to lock function queue"); + queue.push((id, next_id)); + + self.functions.write().expect("Failed to write to self.functions").insert(id, next_id); + + next_id + } } 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 a7880032d42..4aad2aafec1 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -11,25 +11,21 @@ use self::{ value::{Tree, Values}, }; -use super::{ - ir::{instruction::BinaryOp, types::Type, value::ValueId}, - ssa_builder::SharedBuilderContext, -}; +use super::ir::{function::FunctionId, instruction::BinaryOp, types::Type, value::ValueId}; pub(crate) fn generate_ssa(program: Program) { let context = SharedContext::new(program); - let builder_context = SharedBuilderContext::default(); let main = context.program.main(); - let mut function_context = - FunctionContext::new(main.name.clone(), &main.parameters, &context, &builder_context); + let main_id = Program::main_id(); + let main_name = main.name.clone(); + let mut function_context = FunctionContext::new(main_id, main_name, &main.parameters, &context); function_context.codegen_expression(&main.body); - while let Some((src_function_id, _new_id)) = context.pop_next_function_in_queue() { + while let Some((src_function_id, dest_id)) = context.pop_next_function_in_queue() { let function = &context.program[src_function_id]; - // TODO: Need to ensure/assert the new function's id == new_id - function_context.new_function(function.name.clone(), &function.parameters); + function_context.new_function(dest_id, function.name.clone(), &function.parameters); function_context.codegen_expression(&function.body); } } @@ -69,7 +65,7 @@ impl<'a> FunctionContext<'a> { fn codegen_ident(&mut self, ident: &ast::Ident) -> Values { match &ident.definition { ast::Definition::Local(id) => self.lookup(*id).map(|value| value.eval(self).into()), - ast::Definition::Function(_) => todo!(), + ast::Definition::Function(id) => self.get_or_queue_function(*id), ast::Definition::Builtin(_) => todo!(), ast::Definition::LowLevel(_) => todo!(), } @@ -165,10 +161,10 @@ impl<'a> FunctionContext<'a> { let base_index = self.builder.insert_binary(base_offset, BinaryOp::Mul, type_size); let mut field_index = 0u128; - self.map_type(element_type, |ctx, typ| { - let offset = ctx.make_offset(base_index, field_index); + Self::map_type(element_type, |typ| { + let offset = self.make_offset(base_index, field_index); field_index += 1; - ctx.builder.insert_load(array, offset, typ).into() + self.builder.insert_load(array, offset, typ).into() }) } @@ -229,8 +225,8 @@ impl<'a> FunctionContext<'a> { // Create block arguments for the end block as needed to branch to // with our then and else value. - result = self.map_type(&if_expr.typ, |ctx, typ| { - ctx.builder.add_block_parameter(end_block, typ).into() + result = Self::map_type(&if_expr.typ, |typ| { + self.builder.add_block_parameter(end_block, typ).into() }); let else_values = else_value.into_value_list(self); @@ -259,8 +255,26 @@ impl<'a> FunctionContext<'a> { Self::get_field(tuple, field_index) } - fn codegen_call(&mut self, _call: &ast::Call) -> Values { - todo!() + fn codegen_function(&mut self, function: &Expression) -> FunctionId { + use crate::ssa_refactor::ssa_gen::value::Value; + match self.codegen_expression(function) { + Tree::Leaf(Value::Function(id)) => id, + other => { + panic!("codegen_function: expected function value, found {other:?}") + } + } + } + + fn codegen_call(&mut self, call: &ast::Call) -> Values { + let function = self.codegen_function(&call.func); + + let arguments = call + .arguments + .iter() + .flat_map(|argument| self.codegen_expression(argument).into_value_list(self)) + .collect(); + + self.insert_call(function, arguments, &call.return_type) } fn codegen_let(&mut self, let_expr: &ast::Let) -> Values {