diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index ce63bdc7238..851b86e511f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -1,7 +1,9 @@ pub(crate) mod basic_block; +pub(crate) mod constant; pub(crate) mod dfg; pub(crate) mod function; pub(crate) mod instruction; pub(crate) mod map; +pub(crate) mod printer; pub(crate) mod types; pub(crate) mod value; 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 431f1647863..13d1b3ca6f8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -52,7 +52,29 @@ impl BasicBlock { self.instructions.push(instruction); } + pub(crate) fn instructions(&self) -> &[InstructionId] { + &self.instructions + } + pub(crate) fn set_terminator(&mut self, terminator: TerminatorInstruction) { self.terminator = Some(terminator); } + + pub(crate) fn terminator(&self) -> Option<&TerminatorInstruction> { + self.terminator.as_ref() + } + + /// Iterate over all the successors of the currently block, as determined by + /// the blocks jumped to in the terminator instruction. If there is no terminator + /// instruction yet, this will iterate 0 times. + pub(crate) fn successors(&self) -> impl ExactSizeIterator { + match &self.terminator { + Some(TerminatorInstruction::Jmp { destination, .. }) => vec![*destination].into_iter(), + Some(TerminatorInstruction::JmpIf { then_destination, else_destination, .. }) => { + vec![*then_destination, *else_destination].into_iter() + } + Some(TerminatorInstruction::Return { .. }) => vec![].into_iter(), + None => vec![].into_iter(), + } + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs new file mode 100644 index 00000000000..6d5538d3410 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs @@ -0,0 +1,56 @@ +use acvm::FieldElement; + +use super::map::Id; + +/// 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 +/// we can use Id without it getting confused +/// with a possible future use of Id. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub(crate) struct NumericConstant(FieldElement); + +impl NumericConstant { + pub(crate) fn new(value: FieldElement) -> Self { + Self(value) + } + + pub(crate) fn value(&self) -> &FieldElement { + &self.0 + } +} + +pub(crate) type NumericConstantId = Id; + +impl std::ops::Add for NumericConstant { + type Output = NumericConstant; + + fn add(self, rhs: Self) -> Self::Output { + Self::new(self.0 + rhs.0) + } +} + +impl std::ops::Sub for NumericConstant { + type Output = NumericConstant; + + fn sub(self, rhs: Self) -> Self::Output { + Self::new(self.0 - rhs.0) + } +} + +impl std::ops::Mul for NumericConstant { + type Output = NumericConstant; + + fn mul(self, rhs: Self) -> Self::Output { + Self::new(self.0 * rhs.0) + } +} + +impl std::ops::Div for NumericConstant { + type Output = NumericConstant; + + fn div(self, rhs: Self) -> Self::Output { + Self::new(self.0 / rhs.0) + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index b456fd08ee4..f92cae79b75 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -1,12 +1,14 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, + constant::{NumericConstant, NumericConstantId}, function::Signature, instruction::{Instruction, InstructionId}, - map::{DenseMap, Id, SecondaryMap}, + map::{DenseMap, Id, SecondaryMap, TwoWayMap}, types::Type, value::{Value, ValueId}, }; +use acvm::FieldElement; use iter_extended::vecmap; #[derive(Debug, Default)] @@ -20,6 +22,7 @@ impl ValueList { self.0.push(value); self.len() - 1 } + /// Returns the number of values in the list. fn len(&self) -> usize { self.0.len() @@ -29,6 +32,7 @@ impl ValueList { fn clear(&mut self) { self.0.clear(); } + /// Returns the ValueId's as a slice. pub(crate) fn as_slice(&self) -> &[ValueId] { &self.0 @@ -55,6 +59,11 @@ pub(crate) struct DataFlowGraph { /// function. values: DenseMap, + /// Storage for all constants used within a function. + /// Each constant is unique, attempting to insert the same constant + /// twice will return the same ConstantId. + constants: TwoWayMap, + /// Function signatures of external methods signatures: DenseMap, @@ -91,27 +100,35 @@ impl DataFlowGraph { } /// Inserts a new instruction into the DFG. + /// This does not add the instruction to the block or populate the instruction's result list pub(crate) fn make_instruction(&mut self, instruction_data: Instruction) -> InstructionId { let id = self.instructions.insert(instruction_data); - // Create a new vector to store the potential results for the instruction. self.results.insert(id, Default::default()); id } + /// Insert a value into the dfg's storage and return an id to reference it. + /// Until the value is used in an instruction it is unreachable. pub(crate) fn make_value(&mut self, value: Value) -> ValueId { self.values.insert(value) } - /// Attaches results to the instruction. + /// Creates a new constant value, or returns the Id to an existing one if + /// one already exists. + pub(crate) fn make_constant(&mut self, value: FieldElement, typ: Type) -> ValueId { + let constant = self.constants.insert(NumericConstant::new(value)); + self.values.insert(Value::NumericConstant { constant, typ }) + } + + /// Attaches results to the instruction, clearing any previous results. /// - /// Returns the number of results that this instruction - /// produces. + /// Returns the results of the instruction pub(crate) fn make_instruction_results( &mut self, instruction_id: InstructionId, ctrl_typevar: Type, - ) -> usize { + ) -> &[ValueId] { // Clear all of the results instructions associated with this // instruction. self.results.get_mut(&instruction_id).expect("all instructions should have a `result` allocation when instruction was added to the DFG").clear(); @@ -119,13 +136,14 @@ impl DataFlowGraph { // Get all of the types that this instruction produces // and append them as results. let typs = self.instruction_result_types(instruction_id, ctrl_typevar); - let num_typs = typs.len(); for typ in typs { self.append_result(instruction_id, typ); } - num_typs + self.results.get_mut(&instruction_id) + .expect("all instructions should have a `result` allocation when instruction was added to the DFG") + .as_slice() } /// Return the result types of this instruction. @@ -181,6 +199,42 @@ impl DataFlowGraph { block.add_parameter(parameter); parameter } + + pub(crate) fn insert_instruction_in_block( + &mut self, + block: BasicBlockId, + instruction: InstructionId, + ) { + self.blocks[block].insert_instruction(instruction); + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = Instruction; + fn index(&self, id: InstructionId) -> &Self::Output { + &self.instructions[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = Value; + fn index(&self, id: ValueId) -> &Self::Output { + &self.values[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = NumericConstant; + fn index(&self, id: NumericConstantId) -> &Self::Output { + &self.constants[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = BasicBlock; + fn index(&self, id: BasicBlockId) -> &Self::Output { + &self.blocks[id] + } } #[cfg(test)] @@ -190,19 +244,17 @@ mod tests { instruction::Instruction, types::{NumericType, Type}, }; - use acvm::FieldElement; #[test] fn make_instruction() { let mut dfg = DataFlowGraph::default(); - let ins = Instruction::Immediate { value: FieldElement::from(0u128) }; + let ins = Instruction::Allocate { size: 20 }; let ins_id = dfg.make_instruction(ins); let num_results = - dfg.make_instruction_results(ins_id, Type::Numeric(NumericType::NativeField)); + dfg.make_instruction_results(ins_id, Type::Numeric(NumericType::NativeField)).len(); let results = dfg.instruction_results(ins_id); - assert_eq!(results.len(), num_results); } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 1abd6c85367..63cd31142c4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -18,7 +18,10 @@ pub(crate) struct Function { source_locations: SecondaryMap, /// The first basic block in the function - entry_block: BasicBlockId, + pub(super) entry_block: BasicBlockId, + + /// Name of the function for debugging only + pub(super) name: String, pub(crate) dfg: DataFlowGraph, } @@ -27,10 +30,10 @@ 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() -> Self { + pub(crate) fn new(name: String) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.new_block(); - Self { source_locations: SecondaryMap::new(), entry_block, dfg } + Self { name, source_locations: SecondaryMap::new(), entry_block, dfg } } pub(crate) fn entry_block(&self) -> BasicBlockId { @@ -47,6 +50,12 @@ pub(crate) struct Signature { pub(crate) returns: Vec, } +impl std::fmt::Display for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + super::printer::display_function(self, f) + } +} + #[test] fn sign_smoke() { let mut signature = Signature::default(); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 81a28b8407c..442f1dbd47e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -1,5 +1,3 @@ -use acvm::FieldElement; - use super::{ basic_block::BasicBlockId, function::FunctionId, map::Id, types::Type, value::ValueId, }; @@ -17,6 +15,12 @@ pub(crate) type InstructionId = Id; /// of this is println. pub(crate) struct IntrinsicOpcodes; +impl std::fmt::Display for IntrinsicOpcodes { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!("intrinsics have no opcodes yet") + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] /// Instructions are used to perform tasks. /// The instructions that the IR is able to specify are listed below. @@ -38,18 +42,24 @@ pub(crate) enum Instruction { /// Performs a function call with a list of its arguments. Call { func: FunctionId, arguments: Vec }, + /// Performs a call to an intrinsic function and stores the /// results in `return_arguments`. Intrinsic { func: IntrinsicOpcodes, arguments: Vec }, + /// Allocates a region of memory. Note that this is not concerned with + /// the type of memory, the type of element is determined when loading this memory. + /// + /// `size` is the size of the region to be allocated by the number of FieldElements it + /// contains. Note that non-numeric types like Functions and References are counted as 1 field + /// each. + Allocate { size: u32 }, + /// Loads a value from memory. - Load(ValueId), + Load { address: ValueId }, /// Writes a value to memory. - Store { destination: ValueId, value: ValueId }, - - /// Stores an Immediate value - Immediate { value: FieldElement }, + Store { address: ValueId, value: ValueId }, } impl Instruction { @@ -67,28 +77,31 @@ impl Instruction { // This also returns 0, but we could get it a compile time, // since we know the signatures for the intrinsics Instruction::Intrinsic { .. } => 0, - Instruction::Load(_) => 1, + Instruction::Allocate { .. } => 1, + Instruction::Load { .. } => 1, Instruction::Store { .. } => 0, - Instruction::Immediate { .. } => 1, } } /// 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::Cast(_, _) => 1, Instruction::Not(_) => 1, - Instruction::Truncate { .. } => 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, // This also returns 0, but we could get it a compile time, // since we know the function definition for the intrinsics Instruction::Intrinsic { .. } => 0, - Instruction::Load(_) => 1, - Instruction::Store { .. } => 2, - Instruction::Immediate { .. } => 0, + Instruction::Allocate { size: _ } => 1, + Instruction::Load { address: _ } => 1, + Instruction::Store { address: _, value: _ } => 2, } } @@ -102,9 +115,9 @@ impl Instruction { Instruction::Constrain(_) => vec![], Instruction::Call { .. } => vec![], Instruction::Intrinsic { .. } => vec![], - Instruction::Load(_) => vec![ctrl_typevar], + Instruction::Allocate { .. } => vec![Type::Reference], + Instruction::Load { .. } => vec![ctrl_typevar], Instruction::Store { .. } => vec![], - Instruction::Immediate { .. } => vec![], } } } @@ -182,5 +195,18 @@ pub(crate) enum BinaryOp { /// Checks whether two types are equal. /// Returns true if the types were not equal and /// false otherwise. - Ne, + Neq, +} + +impl std::fmt::Display for BinaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinaryOp::Add => write!(f, "add"), + BinaryOp::Sub => write!(f, "sub"), + BinaryOp::Mul => write!(f, "mul"), + BinaryOp::Div => write!(f, "div"), + BinaryOp::Eq => write!(f, "eq"), + BinaryOp::Neq => write!(f, "neq"), + } + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index 53a7db3a5d5..bb526076e3b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + hash::Hash, sync::atomic::{AtomicUsize, Ordering}, }; @@ -68,6 +69,12 @@ impl std::fmt::Debug for Id { } } +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "${}", self.index) + } +} + /// A DenseMap is a Vec wrapper where each element corresponds /// to a unique ID that can be used to access the element. No direct /// access to indices is provided. Since IDs must be stable and correspond @@ -186,6 +193,53 @@ impl std::ops::IndexMut> for SparseMap { } } +/// A TwoWayMap is a map from both key to value and value to key. +/// This is accomplished by keeping the map bijective - for every +/// value there is exactly one key and vice-versa. Any duplicate values +/// are prevented in the call to insert. +#[derive(Debug)] +pub(crate) struct TwoWayMap { + key_to_value: HashMap, T>, + value_to_key: HashMap>, +} + +impl TwoWayMap { + /// Returns the number of elements in the map. + pub(crate) fn len(&self) -> usize { + self.key_to_value.len() + } + + /// Adds an element to the map. + /// Returns the identifier/reference to that element. + pub(crate) fn insert(&mut self, element: T) -> Id { + if let Some(existing) = self.value_to_key.get(&element) { + return *existing; + } + + let id = Id::new(self.key_to_value.len()); + self.key_to_value.insert(id, element.clone()); + self.value_to_key.insert(element, id); + id + } +} + +impl Default for TwoWayMap { + fn default() -> Self { + Self { key_to_value: HashMap::new(), value_to_key: HashMap::new() } + } +} + +// Note that there is no impl for IndexMut>, +// if we allowed mutable access to map elements they may be +// mutated such that elements are no longer unique +impl std::ops::Index> for TwoWayMap { + type Output = T; + + fn index(&self, id: Id) -> &Self::Output { + &self.key_to_value[&id] + } +} + /// A SecondaryMap is for storing secondary data for a given key. Since this /// map is for secondary data, it will not return fresh Ids for data, instead /// it expects users to provide these ids in order to associate existing ids with diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs new file mode 100644 index 00000000000..1a7737e97b0 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -0,0 +1,115 @@ +//! This file is for pretty-printing the SSA IR in a human-readable form for debugging. +use std::fmt::{Formatter, Result}; + +use iter_extended::vecmap; + +use super::{ + basic_block::BasicBlockId, + function::Function, + instruction::{Instruction, InstructionId, TerminatorInstruction}, + value::ValueId, +}; + +pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result { + writeln!(f, "fn {} {{", function.name)?; + display_block_with_successors(function, function.entry_block, f)?; + write!(f, "}}") +} + +pub(crate) fn display_block_with_successors( + function: &Function, + block_id: BasicBlockId, + f: &mut Formatter, +) -> Result { + display_block(function, block_id, f)?; + + for successor in function.dfg[block_id].successors() { + display_block(function, successor, f)?; + } + Ok(()) +} + +pub(crate) fn display_block( + function: &Function, + block_id: BasicBlockId, + f: &mut Formatter, +) -> Result { + let block = &function.dfg[block_id]; + + writeln!(f, "{}({}):", block_id, value_list(block.parameters()))?; + + for instruction in block.instructions() { + display_instruction(function, *instruction, f)?; + } + + display_terminator(block.terminator(), f) +} + +fn value_list(values: &[ValueId]) -> String { + vecmap(values, ToString::to_string).join(", ") +} + +pub(crate) fn display_terminator( + terminator: Option<&TerminatorInstruction>, + f: &mut Formatter, +) -> Result { + match terminator { + Some(TerminatorInstruction::Jmp { destination, arguments }) => { + writeln!(f, " jmp {}({})", destination, value_list(arguments)) + } + Some(TerminatorInstruction::JmpIf { + condition, + arguments, + then_destination, + else_destination, + }) => { + let args = value_list(arguments); + writeln!( + f, + " jmpif {}({}) then: {}, else: {}", + condition, args, then_destination, else_destination + ) + } + Some(TerminatorInstruction::Return { return_values }) => { + writeln!(f, " return {}", value_list(return_values)) + } + None => writeln!(f, " (no terminator instruction)"), + } +} + +pub(crate) fn display_instruction( + function: &Function, + instruction: InstructionId, + f: &mut Formatter, +) -> Result { + // instructions are always indented within a function + write!(f, " ")?; + + let results = function.dfg.instruction_results(instruction); + if !results.is_empty() { + write!(f, "{} = ", value_list(results))?; + } + + match &function.dfg[instruction] { + Instruction::Binary(binary) => { + writeln!(f, "{} {}, {}", binary.operator, binary.lhs, binary.rhs) + } + Instruction::Cast(value, typ) => writeln!(f, "cast {value} as {typ}"), + Instruction::Not(value) => writeln!(f, "not {value}"), + Instruction::Truncate { value, bit_size, max_bit_size } => { + writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}") + } + Instruction::Constrain(value) => { + writeln!(f, "constrain {value}") + } + Instruction::Call { func, arguments } => { + writeln!(f, "call {func}({})", value_list(arguments)) + } + Instruction::Intrinsic { func, arguments } => { + writeln!(f, "intrinsic {func}({})", value_list(arguments)) + } + Instruction::Allocate { size } => writeln!(f, "alloc {size} fields"), + Instruction::Load { address } => writeln!(f, "load {address}"), + Instruction::Store { address, value } => writeln!(f, "store {value} at {address}"), + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs index e1f8e8a74d2..888d7d128d1 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs @@ -42,3 +42,24 @@ impl Type { Type::Numeric(NumericType::NativeField) } } + +impl std::fmt::Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Numeric(numeric) => numeric.fmt(f), + Type::Reference => write!(f, "reference"), + Type::Function => write!(f, "function"), + Type::Unit => write!(f, "unit"), + } + } +} + +impl std::fmt::Display for NumericType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NumericType::Signed { bit_size } => write!(f, "i{bit_size}"), + NumericType::Unsigned { bit_size } => write!(f, "u{bit_size}"), + NumericType::NativeField => write!(f, "Field"), + } + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs index 38ca8b12c40..537eabb0cab 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs @@ -1,6 +1,6 @@ use crate::ssa_refactor::ir::basic_block::BasicBlockId; -use super::{instruction::InstructionId, map::Id, types::Type}; +use super::{constant::NumericConstantId, instruction::InstructionId, map::Id, types::Type}; pub(crate) type ValueId = Id; @@ -24,4 +24,7 @@ pub(crate) enum Value { /// /// position -- the index of this Value in the block parameters list Param { block: BasicBlockId, position: usize, typ: Type }, + + /// This Value originates from a numeric constant + NumericConstant { constant: NumericConstantId, typ: Type }, } 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 index 5e82226d3be..c76d2943abe 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -1,6 +1,9 @@ +use acvm::FieldElement; + use crate::ssa_refactor::ir::{ basic_block::BasicBlockId, function::{Function, FunctionId}, + instruction::{Binary, BinaryOp, Instruction, InstructionId}, types::Type, value::ValueId, }; @@ -26,8 +29,8 @@ pub(crate) struct FunctionBuilder<'ssa> { } impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(context: &'ssa SharedBuilderContext) -> Self { - let new_function = Function::new(); + 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 { @@ -40,8 +43,8 @@ impl<'ssa> FunctionBuilder<'ssa> { } /// Finish the current function and create a new function - pub(crate) fn new_function(&mut self) { - let new_function = Function::new(); + 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)); @@ -57,4 +60,52 @@ impl<'ssa> FunctionBuilder<'ssa> { 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: FieldElement, typ: Type) -> ValueId { + self.current_function.dfg.make_constant(value, 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()) + } + + fn insert_instruction(&mut self, instruction: Instruction) -> InstructionId { + let id = self.current_function.dfg.make_instruction(instruction); + self.current_function.dfg.insert_instruction_in_block(self.current_block, id); + id + } + + /// 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 { + let id = self.insert_instruction(Instruction::Allocate { size: size_to_allocate }); + self.current_function.dfg.make_instruction_results(id, Type::Reference)[0] + } + + /// Insert a Load instruction at the end of the current block, loading from 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. + /// Returns the element that was loaded. + pub(crate) fn insert_load(&mut self, address: ValueId, type_to_load: Type) -> ValueId { + let id = self.insert_instruction(Instruction::Load { address }); + self.current_function.dfg.make_instruction_results(id, 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 to a previous Allocate instruction. + pub(crate) fn insert_store(&mut self, address: ValueId, value: ValueId) { + self.insert_instruction(Instruction::Store { address, value }); + } + + /// Insert a Store instruction at the end of the current block, storing the given element + /// at the given address. Expects that the address points to a previous Allocate instruction. + /// Returns the result of the add instruction. + pub(crate) fn insert_add(&mut self, lhs: ValueId, rhs: ValueId, typ: Type) -> ValueId { + let operator = BinaryOp::Add; + let id = self.insert_instruction(Instruction::Binary(Binary { lhs, rhs, operator })); + self.current_function.dfg.make_instruction_results(id, typ)[0] + } } 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 02bfee8a87f..32133feea13 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -19,7 +19,7 @@ type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; pub(super) struct FunctionContext<'a> { definitions: HashMap, - function_builder: FunctionBuilder<'a>, + pub(super) builder: FunctionBuilder<'a>, shared_context: &'a SharedContext, } @@ -32,22 +32,23 @@ pub(super) struct SharedContext { impl<'a> FunctionContext<'a> { pub(super) fn new( + function_name: String, parameters: &Parameters, shared_context: &'a SharedContext, shared_builder_context: &'a SharedBuilderContext, ) -> Self { let mut this = Self { definitions: HashMap::new(), - function_builder: FunctionBuilder::new(shared_builder_context), + builder: FunctionBuilder::new(function_name, shared_builder_context), shared_context, }; this.add_parameters_to_scope(parameters); this } - pub(super) fn new_function(&mut self, parameters: &Parameters) { + pub(super) fn new_function(&mut self, name: String, parameters: &Parameters) { self.definitions.clear(); - self.function_builder.new_function(); + self.builder.new_function(name); self.add_parameters_to_scope(parameters); } @@ -67,8 +68,8 @@ impl<'a> FunctionContext<'a> { /// into a new parameter for each field recursively. 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.function_builder.add_parameter(typ).into()); + let parameter_value = + self.map_type(parameter_type, |this, typ| this.builder.add_parameter(typ).into()); self.definitions.insert(parameter_id, parameter_value); } @@ -82,24 +83,28 @@ impl<'a> FunctionContext<'a> { typ: &ast::Type, mut f: impl FnMut(&mut Self, Type) -> T, ) -> Tree { - self.map_type_helper(typ, &mut f) + Self::map_type_helper(typ, &mut |typ| f(self, typ)) } // This helper is needed because we need to take f by mutable reference, // otherwise we cannot move it multiple times each loop of vecmap. - fn map_type_helper( - &mut self, - typ: &ast::Type, - f: &mut impl FnMut(&mut Self, Type) -> T, - ) -> Tree { + fn map_type_helper(typ: &ast::Type, f: &mut impl FnMut(Type) -> T) -> Tree { match typ { ast::Type::Tuple(fields) => { - Tree::Branch(vecmap(fields, |field| self.map_type_helper(field, f))) + Tree::Branch(vecmap(fields, |field| Self::map_type_helper(field, f))) } - other => Tree::Leaf(f(self, Self::convert_non_tuple_type(other))), + other => Tree::Leaf(f(Self::convert_non_tuple_type(other))), } } + /// Convert a monomorphized type to an SSA type, preserving the structure + /// of any tuples within. + pub(super) fn convert_type(typ: &ast::Type) -> Tree { + // Do nothing in the closure here - map_type_helper already calls + // convert_non_tuple_type internally. + Self::map_type_helper(typ, &mut |x| x) + } + pub(super) fn convert_non_tuple_type(typ: &ast::Type) -> Type { match typ { ast::Type::Field => Type::field(), 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 c340b45eb9b..2f9c6646282 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -1,27 +1,33 @@ mod context; mod value; +use acvm::FieldElement; use context::SharedContext; +use iter_extended::vecmap; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{self, Expression, Program}; -use self::{context::FunctionContext, value::Values}; +use self::{ + context::FunctionContext, + value::{Tree, Values}, +}; -use super::ssa_builder::SharedBuilderContext; +use super::{ir::types::Type, ssa_builder::SharedBuilderContext}; 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 mut function_context = FunctionContext::new(&main.parameters, &context, &builder_context); function_context.codegen_expression(&main.body); while let Some((src_function_id, _new_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.parameters); + function_context.new_function(function.name.clone(), &function.parameters); function_context.codegen_expression(&function.body); } } @@ -56,8 +62,55 @@ impl<'a> FunctionContext<'a> { todo!() } - fn codegen_literal(&mut self, _literal: &ast::Literal) -> Values { - todo!() + fn codegen_literal(&mut self, literal: &ast::Literal) -> Values { + match literal { + ast::Literal::Array(array) => { + let elements = vecmap(&array.contents, |element| self.codegen_expression(element)); + let element_type = Self::convert_type(&array.element_type); + self.codegen_array(elements, element_type) + } + ast::Literal::Integer(value, typ) => { + let typ = Self::convert_non_tuple_type(typ); + self.builder.numeric_constant(*value, typ).into() + } + ast::Literal::Bool(value) => { + // Booleans are represented as u1s with 0 = false, 1 = true + let typ = Type::unsigned(1); + let value = FieldElement::from(*value as u128); + self.builder.numeric_constant(value, typ).into() + } + ast::Literal::Str(string) => { + let elements = vecmap(string.as_bytes(), |byte| { + let value = FieldElement::from(*byte as u128); + self.builder.numeric_constant(value, Type::field()).into() + }); + self.codegen_array(elements, Tree::Leaf(Type::field())) + } + } + } + + fn codegen_array(&mut self, elements: Vec, element_type: Tree) -> Values { + let size = element_type.size_of_type() * elements.len(); + let array = self.builder.insert_allocate(size.try_into().unwrap_or_else(|_| { + panic!("Cannot allocate {size} bytes for array, it does not fit into a u32") + })); + + // Now we must manually store all the elements into the array + let mut i = 0; + for element in elements { + element.for_each(|value| { + let address = if i == 0 { + array + } else { + let offset = self.builder.numeric_constant((i as u128).into(), Type::field()); + self.builder.insert_add(array, offset, Type::field()) + }; + self.builder.insert_store(address, value.eval()); + i += 1; + }); + } + + array.into() } fn codegen_block(&mut self, _block: &[Expression]) -> Values { 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 4b41c6ae102..c3911d367c1 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -1,4 +1,5 @@ use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; +use crate::ssa_refactor::ir::types::Type; use crate::ssa_refactor::ir::value::ValueId as IrValueId; pub(super) enum Tree { @@ -10,10 +11,18 @@ pub(super) enum Tree { pub(super) enum Value { Normal(IrValueId), Function(IrFunctionId), +} - /// Lazily inserting unit values helps prevent cluttering the IR with too many - /// unit literals. - Unit, +impl Value { + /// Evaluate a value, returning an IrValue from it. + /// This has no effect on Value::Normal, but any variables will be updated with their latest + /// use. + pub(super) fn eval(self) -> IrValueId { + match self { + Value::Normal(value) => value, + Value::Function(_) => panic!("Tried to evaluate a function value"), + } + } } pub(super) type Values = Tree; @@ -25,6 +34,25 @@ impl Tree { Tree::Leaf(value) => vec![value], } } + + pub(super) fn count_leaves(&self) -> usize { + match self { + Tree::Branch(trees) => trees.iter().map(|tree| tree.count_leaves()).sum(), + Tree::Leaf(_) => 1, + } + } + + /// Iterates over each Leaf node, calling f on each value within. + pub(super) fn for_each(self, mut f: impl FnMut(T)) { + self.for_each_helper(&mut f); + } + + fn for_each_helper(self, f: &mut impl FnMut(T)) { + match self { + Tree::Branch(trees) => trees.into_iter().for_each(|tree| tree.for_each_helper(f)), + Tree::Leaf(value) => f(value), + } + } } impl From for Values { @@ -38,3 +66,12 @@ impl From for Value { Value::Normal(id) } } + +// Specialize this impl just to give a better name for this function +impl Tree { + /// Returns the size of the type in terms of the number of FieldElements it contains. + /// Non-field types like functions and references are also counted as 1 FieldElement. + pub(super) fn size_of_type(&self) -> usize { + self.count_leaves() + } +}