From 71efa70ad2d7894065c0df26de8f1a6dbb8b8f1d Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 20 Apr 2023 16:07:36 -0500 Subject: [PATCH 01/10] Add Context structs and start ssa gen pass --- crates/noirc_evaluator/src/lib.rs | 2 +- crates/noirc_evaluator/src/ssa_refactor.rs | 9 +- .../src/ssa_refactor/basic_block.rs | 37 ------ crates/noirc_evaluator/src/ssa_refactor/ir.rs | 5 +- .../src/ssa_refactor/ir/basic_block.rs | 43 +++++++ .../src/ssa_refactor/{ => ir}/dfg.rs | 29 ++--- .../src/ssa_refactor/ir/extfunc.rs | 20 --- .../src/ssa_refactor/ir/function.rs | 56 +++++++-- .../src/ssa_refactor/ir/instruction.rs | 44 +------ .../src/ssa_refactor/ir/map.rs | 48 ++++++- .../src/ssa_refactor/ir/value.rs | 10 +- .../ssa_builder/function_builder.rs | 54 ++++++++ .../src/ssa_refactor/ssa_builder/mod.rs | 2 + .../ssa_refactor/ssa_builder/ssa_builder.rs | 18 +++ .../src/ssa_refactor/ssa_gen/context.rs | 60 +++++++++ .../src/ssa_refactor/ssa_gen/mod.rs | 119 ++++++++++++++++++ .../src/ssa_refactor/ssa_gen/value.rs | 14 +++ .../src/monomorphization/ast.rs | 8 +- 18 files changed, 441 insertions(+), 137 deletions(-) delete mode 100644 crates/noirc_evaluator/src/ssa_refactor/basic_block.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs rename crates/noirc_evaluator/src/ssa_refactor/{ => ir}/dfg.rs (89%) delete mode 100644 crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs diff --git a/crates/noirc_evaluator/src/lib.rs b/crates/noirc_evaluator/src/lib.rs index 166c2d58239..8b3cbb009a9 100644 --- a/crates/noirc_evaluator/src/lib.rs +++ b/crates/noirc_evaluator/src/lib.rs @@ -304,7 +304,7 @@ impl Evaluator { // u8 and arrays are assumed to be private // This is not a short-coming of the ABI, but of the grammar // The new grammar has been conceived, and will be implemented. - let main = ir_gen.program.main(); + let main = ir_gen.program.main_mut(); let main_params = std::mem::take(&mut main.parameters); let abi_params = std::mem::take(&mut ir_gen.program.main_function_signature.0); diff --git a/crates/noirc_evaluator/src/ssa_refactor.rs b/crates/noirc_evaluator/src/ssa_refactor.rs index 073b54cbf10..37f1ead2b07 100644 --- a/crates/noirc_evaluator/src/ssa_refactor.rs +++ b/crates/noirc_evaluator/src/ssa_refactor.rs @@ -5,9 +5,8 @@ //! elimination and constant folding. //! //! This module heavily borrows from Cranelift -#[allow(dead_code)] -mod basic_block; -#[allow(dead_code)] -mod dfg; -#[allow(dead_code)] +#![allow(dead_code)] + mod ir; +mod ssa_builder; +mod ssa_gen; diff --git a/crates/noirc_evaluator/src/ssa_refactor/basic_block.rs b/crates/noirc_evaluator/src/ssa_refactor/basic_block.rs deleted file mode 100644 index d6c2198b4a0..00000000000 --- a/crates/noirc_evaluator/src/ssa_refactor/basic_block.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::ir::instruction::{Instruction, TerminatorInstruction}; - -/// A Basic block is a maximal collection of instructions -/// such that there are only jumps at the end of block -/// and one can only enter the block from the beginning. -/// -/// This means that if one instruction is executed in a basic -/// block, then all instructions are executed. ie single-entry single-exit. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) struct BasicBlock { - /// Arguments to the basic block. - phi_nodes: Vec, - /// 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. - terminator: TerminatorInstruction, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -/// An identifier for a Basic Block. -pub(crate) struct BasicBlockId; - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -/// Arguments to the basic block. -/// We use the modern Crane-lift strategy -/// of representing phi nodes as basic block -/// arguments. -pub(crate) struct BlockArguments; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index bdb722cd456..ce63bdc7238 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -1,5 +1,6 @@ -pub(crate) mod extfunc; -mod function; +pub(crate) mod basic_block; +pub(crate) mod dfg; +pub(crate) mod function; pub(crate) mod instruction; pub(crate) mod map; pub(crate) mod types; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs new file mode 100644 index 00000000000..8470c1af579 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -0,0 +1,43 @@ +use super::{ + instruction::{Instruction, InstructionId, TerminatorInstruction}, + map::Id, +}; + +/// A Basic block is a maximal collection of instructions +/// such that there are only jumps at the end of block +/// and one can only enter the block from the beginning. +/// +/// This means that if one instruction is executed in a basic +/// block, then all instructions are executed. ie single-entry single-exit. +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub(crate) struct BasicBlock { + /// Parameters to the basic block. + /// The relevant values can be created with this block's id + /// and the index of the parameter, so we only need to remember + /// the number of parameters here. + parameter_count: usize, + + /// 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 + /// None if the block is still being constructed. + terminator: Option, +} + +/// An identifier for a Basic Block. +pub(crate) type BasicBlockId = Id; + +impl BasicBlock { + pub(super) fn new(parameter_count: usize) -> Self { + Self { parameter_count, instructions: Vec::new(), is_sealed: false, terminator: None } + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs similarity index 89% rename from crates/noirc_evaluator/src/ssa_refactor/dfg.rs rename to crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 6dcee5212e2..e167c6aaef2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -1,14 +1,11 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, - ir::{ - extfunc::Signature, - instruction::{Instruction, InstructionId, Instructions}, - map::{Id, SparseMap}, - types::Type, - value::{Value, ValueId}, - }, + function::Signature, + instruction::{Instruction, InstructionId}, + map::{DenseMap, Id, SecondaryMap, SparseMap}, + types::Type, + value::{Value, ValueId}, }; -use std::collections::HashMap; #[derive(Debug, Default)] /// A convenience wrapper to store `Value`s. @@ -39,7 +36,7 @@ impl ValueList { #[derive(Debug, Default)] pub(crate) struct DataFlowGraph { /// All of the instructions in a function - instructions: Instructions, + instructions: DenseMap, /// Stores the results for a particular instruction. /// @@ -50,17 +47,17 @@ 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: SecondaryMap, /// Storage for all of the values defined in this /// function. - values: SparseMap, + values: DenseMap, /// Function signatures of external methods - signatures: SparseMap, + signatures: DenseMap, /// All blocks in a function - blocks: SparseMap, + blocks: DenseMap, } impl DataFlowGraph { @@ -71,7 +68,7 @@ impl DataFlowGraph { /// Inserts a new instruction into the DFG. pub(crate) fn make_instruction(&mut self, instruction_data: Instruction) -> InstructionId { - let id = self.instructions.push(instruction_data); + 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()); @@ -126,9 +123,9 @@ impl DataFlowGraph { let results = self.results.get_mut(&instruction_id).unwrap(); let expected_res_position = results.len(); - let value_id = self.values.push(Value::Instruction { + let value_id = self.values.insert(Value::Instruction { typ, - position: expected_res_position as u16, + position: expected_res_position, instruction: instruction_id, }); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs deleted file mode 100644 index 0ec7d6f5fc0..00000000000 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! 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. - -use super::types::Type; - -#[derive(Debug, Default, Clone)] -pub(crate) struct Signature { - pub(crate) params: Vec, - pub(crate) returns: Vec, -} - -#[test] -fn sign_smoke() { - let mut signature = Signature::default(); - - signature.params.push(Type::Numeric(super::types::NumericType::NativeField)); - signature.returns.push(Type::Numeric(super::types::NumericType::Unsigned { bit_size: 32 })); -} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 331c0d656d6..c08989f6fd2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -1,25 +1,63 @@ -use crate::ssa_refactor::basic_block::{BasicBlock, BasicBlockId}; - -use super::instruction::Instruction; +use super::basic_block::{BasicBlock, BasicBlockId}; +use super::dfg::DataFlowGraph; +use super::instruction::{Instruction, InstructionId}; +use super::map::{DenseMap, Id, SecondaryMap}; +use super::types::Type; use noirc_errors::Location; use std::collections::HashMap; /// A function holds a list of instructions. -/// These instructions are further grouped into -/// Basic blocks +/// 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. #[derive(Debug)] pub(crate) struct Function { /// Basic blocks associated to this particular function - basic_blocks: HashMap, + basic_blocks: DenseMap, /// Maps instructions to source locations - source_locations: HashMap, + source_locations: SecondaryMap, /// The first basic block in the function entry_block: BasicBlockId, + + dfg: DataFlowGraph, +} + +impl Function { + pub(crate) fn new(parameter_count: usize) -> Self { + let mut basic_blocks = DenseMap::default(); + let entry_block = basic_blocks.insert(BasicBlock::new(parameter_count)); + + Self { + basic_blocks, + source_locations: SecondaryMap::new(), + entry_block, + dfg: DataFlowGraph::default(), + } + } + + pub(crate) fn entry_block(&self) -> BasicBlockId { + self.entry_block + } } /// FunctionId is a reference for a function -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub(crate) struct FunctionId(pub(crate) u32); +pub(crate) type FunctionId = Id; + +#[derive(Debug, Default, Clone)] +pub(crate) struct Signature { + pub(crate) params: Vec, + pub(crate) returns: Vec, +} + +#[test] +fn sign_smoke() { + let mut signature = Signature::default(); + + signature.params.push(Type::Numeric(super::types::NumericType::NativeField)); + signature.returns.push(Type::Numeric(super::types::NumericType::Unsigned { bit_size: 32 })); +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 33715b67293..f12c90abe06 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -1,15 +1,8 @@ use acvm::FieldElement; use super::{ - function::FunctionId, - map::{Id, SparseMap}, - types::Type, - value::ValueId, + basic_block::BasicBlockId, function::FunctionId, map::Id, types::Type, value::ValueId, }; -use crate::ssa_refactor::basic_block::{BasicBlockId, BlockArguments}; - -// Container for all Instructions, per-function -pub(crate) type Instructions = SparseMap; /// Reference to an instruction pub(crate) type InstructionId = Id; @@ -131,11 +124,11 @@ pub(crate) enum TerminatorInstruction { /// Jumps to the specified `destination` with /// arguments, if the condition /// if the condition is true. - JmpIf { condition: ValueId, destination: BasicBlockId, arguments: BlockArguments }, + JmpIf { condition: ValueId, destination: BasicBlockId, arguments: Vec }, /// Unconditional Jump /// /// Jumps to specified `destination` with `arguments` - Jmp { destination: BasicBlockId, arguments: BlockArguments }, + Jmp { destination: BasicBlockId, arguments: Vec }, } /// A binary instruction in the IR. @@ -177,34 +170,3 @@ pub(crate) enum BinaryOp { /// false otherwise. Ne, } - -#[test] -fn smoke_instructions_map_duplicate() { - let id = Id::test_new(0); - - let ins = Instruction::Not(id); - let same_ins = Instruction::Not(id); - - let mut ins_map = Instructions::default(); - - // Document what happens when we insert the same instruction twice - let id = ins_map.push(ins); - let id_same_ins = ins_map.push(same_ins); - - // The map is quite naive and does not check if the instruction has ben inserted - // before. We simply assign a different Id. - assert_ne!(id, id_same_ins) -} - -#[test] -fn num_instructions_smoke() { - let n = 100; - - let mut ins_map = Instructions::default(); - for i in 0..n { - let ins = Instruction::Not(Id::test_new(i)); - ins_map.push(ins); - } - - assert_eq!(n, ins_map.len()) -} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index 6c7511b5bdd..e89061d7d83 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + sync::atomic::{AtomicUsize, Ordering}, +}; /// A unique ID corresponding to a value of type T. /// This type can be used to retrieve a value of type T from @@ -80,9 +83,10 @@ impl DenseMap { pub(crate) fn len(&self) -> usize { self.storage.len() } + /// Adds an element to the map. /// Returns the identifier/reference to that element. - pub(crate) fn push(&mut self, element: T) -> Id { + pub(crate) fn insert(&mut self, element: T) -> Id { let id = Id::new(self.storage.len()); self.storage.push(element); id @@ -132,7 +136,7 @@ impl SparseMap { /// Adds an element to the map. /// Returns the identifier/reference to that element. - pub(crate) fn push(&mut self, element: T) -> Id { + pub(crate) fn insert(&mut self, element: T) -> Id { let id = Id::new(self.storage.len()); self.storage.insert(id, element); id @@ -165,3 +169,41 @@ impl std::ops::IndexMut> for SparseMap { self.storage.get_mut(&id).expect("Invalid id used in SparseMap::index_mut") } } + +/// 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 +/// additional data. +/// +/// Unlike SecondaryMap in cranelift, this version is sparse and thus +/// does not require inserting default elements for each key in between +/// the desired key and the previous length of the map. +/// +/// There is no expectation that there is always secondary data for all relevant +/// Ids of a given type, so unlike the other Map types, it is possible for +/// a call to .get(id) to return None. +pub(crate) type SecondaryMap = HashMap, V>; + +/// A simple counter to create fresh Ids without any storage. +/// Useful for assigning ids before the storage is created or assigning ids +/// for types that have no single owner. +/// +/// This type wraps an AtomicUsize so it can safely be used across threads. +#[derive(Debug)] +pub(crate) struct AtomicCounter { + next: AtomicUsize, + _marker: std::marker::PhantomData, +} + +impl AtomicCounter { + /// Return the next fresh id + pub(crate) fn next(&self) -> Id { + Id::new(self.next.fetch_add(1, Ordering::Relaxed)) + } +} + +impl Default for AtomicCounter { + fn default() -> Self { + Self { next: Default::default(), _marker: Default::default() } + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs index ddd00efb38f..38ca8b12c40 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs @@ -1,3 +1,5 @@ +use crate::ssa_refactor::ir::basic_block::BasicBlockId; + use super::{instruction::InstructionId, map::Id, types::Type}; pub(crate) type ValueId = Id; @@ -15,5 +17,11 @@ pub(crate) enum Value { /// Example, if you add two numbers together, then the resulting /// value would have position `0`, the typ would be the type /// of the operands, and the instruction would map to an add instruction. - Instruction { typ: Type, position: u16, instruction: InstructionId }, + Instruction { instruction: InstructionId, position: usize, typ: Type }, + + /// This Value originates from a block parameter. Since function parameters + /// are also represented as block parameters, this includes function parameters as well. + /// + /// position -- the index of this Value in the block parameters list + Param { block: BasicBlockId, position: usize, 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 new file mode 100644 index 00000000000..9ddc56f8377 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -0,0 +1,54 @@ +use crate::ssa_refactor::ir::{ + basic_block::BasicBlockId, + function::{Function, FunctionId}, +}; + +use super::ssa_builder::SsaBuilder; + +/// 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 SsaBuilder, + + current_function: Function, + current_function_id: FunctionId, + + current_block: BasicBlockId, + + finished_functions: Vec<(FunctionId, Function)>, +} + +impl<'ssa> FunctionBuilder<'ssa> { + pub(crate) fn new(parameters: usize, context: &'ssa SsaBuilder) -> Self { + let new_function = Function::new(parameters); + 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, parameters: usize) { + let new_function = Function::new(parameters); + 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 + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs new file mode 100644 index 00000000000..ea08c1821b5 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod function_builder; +pub(crate) mod ssa_builder; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs new file mode 100644 index 00000000000..550e5e65f6d --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs @@ -0,0 +1,18 @@ +use crate::ssa_refactor::ir::{ + function::{Function, FunctionId}, + map::AtomicCounter, +}; + +/// The global context while building the ssa representation. +/// Because this may be shared across threads, it is synchronized as necessary. +#[derive(Default)] + +pub(crate) struct SsaBuilder { + function_count: AtomicCounter, +} + +impl SsaBuilder { + pub(super) fn next_function(&self) -> FunctionId { + self.function_count.next() + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs new file mode 100644 index 00000000000..f6be5e806c8 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; +use std::sync::{Mutex, RwLock}; + +use noirc_frontend::monomorphization::ast::{self, LocalId}; +use noirc_frontend::monomorphization::ast::{FuncId, Program}; + +use crate::ssa_refactor::ssa_builder::ssa_builder::SsaBuilder; +use crate::ssa_refactor::{ + ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, +}; + +use super::value::Value; + +// TODO: Make this a threadsafe queue so we can compile functions in parallel +type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; + +pub(super) struct FunctionContext<'a> { + definitions: HashMap, + function_builder: FunctionBuilder<'a>, + shared_context: &'a Context, +} + +/// Shared context for all functions during ssa codegen +pub(super) struct Context { + functions: RwLock>, + function_queue: Mutex, + pub(super) program: Program, +} + +impl<'a> FunctionContext<'a> { + pub(super) fn new( + parameter_count: usize, + shared_context: &'a Context, + shared_builder_context: &'a SsaBuilder, + ) -> Self { + Self { + definitions: HashMap::new(), + function_builder: FunctionBuilder::new(parameter_count, shared_builder_context), + shared_context, + } + } + + pub(super) fn new_function(&mut self, parameters: impl ExactSizeIterator) { + self.function_builder.new_function(parameters.len()); + + for (i, parameter) in parameters.enumerate() { + todo!("Add block param to definitions") + } + } +} + +impl Context { + pub(super) fn new(program: Program) -> Self { + Self { functions: Default::default(), function_queue: 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() + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs new file mode 100644 index 00000000000..73014c3b64a --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -0,0 +1,119 @@ +mod context; +mod value; + +use context::Context; +use noirc_errors::Location; +use noirc_frontend::monomorphization::ast::{self, Expression, Program}; + +use self::{context::FunctionContext, value::Value}; + +use super::ssa_builder::ssa_builder::SsaBuilder; + +pub fn generate_ssa(mut program: Program) { + let context = Context::new(program); + let builder_context = SsaBuilder::default(); + + let main = context.program.main(); + // TODO struct parameter counting + let parameter_count = main.parameters.len(); + + let mut function_context = FunctionContext::new(parameter_count, &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]; + function_context.new_function(function.parameters.iter().map(|(id, ..)| *id)); + function_context.codegen_expression(&function.body); + } +} + +impl<'a> FunctionContext<'a> { + fn codegen_expression(&mut self, expr: &Expression) -> Value { + match expr { + Expression::Ident(ident) => self.codegen_ident(ident), + Expression::Literal(literal) => self.codegen_literal(literal), + Expression::Block(block) => self.codegen_block(block), + Expression::Unary(unary) => self.codegen_unary(unary), + Expression::Binary(binary) => self.codegen_binary(binary), + Expression::Index(index) => self.codegen_index(index), + Expression::Cast(cast) => self.codegen_cast(cast), + Expression::For(for_expr) => self.codegen_for(for_expr), + Expression::If(if_expr) => self.codegen_if(if_expr), + Expression::Tuple(tuple) => self.codegen_tuple(tuple), + Expression::ExtractTupleField(tuple, index) => { + self.codegen_extract_tuple_field(tuple, *index) + } + Expression::Call(call) => self.codegen_call(call), + Expression::Let(let_expr) => self.codegen_let(let_expr), + Expression::Constrain(constrain, location) => { + self.codegen_constrain(constrain, *location) + } + Expression::Assign(assign) => self.codegen_assign(assign), + Expression::Semi(semi) => self.codegen_semi(semi), + } + } + + fn codegen_ident(&mut self, ident: &ast::Ident) -> Value { + todo!() + } + + fn codegen_literal(&mut self, literal: &ast::Literal) -> Value { + todo!() + } + + fn codegen_block(&mut self, block: &[Expression]) -> Value { + todo!() + } + + fn codegen_unary(&mut self, unary: &ast::Unary) -> Value { + todo!() + } + + fn codegen_binary(&mut self, binary: &ast::Binary) -> Value { + todo!() + } + + fn codegen_index(&mut self, index: &ast::Index) -> Value { + todo!() + } + + fn codegen_cast(&mut self, cast: &ast::Cast) -> Value { + todo!() + } + + fn codegen_for(&mut self, for_expr: &ast::For) -> Value { + todo!() + } + + fn codegen_if(&mut self, if_expr: &ast::If) -> Value { + todo!() + } + + fn codegen_tuple(&mut self, tuple: &[Expression]) -> Value { + todo!() + } + + fn codegen_extract_tuple_field(&mut self, tuple: &Expression, index: usize) -> Value { + todo!() + } + + fn codegen_call(&mut self, call: &ast::Call) -> Value { + todo!() + } + + fn codegen_let(&mut self, let_expr: &ast::Let) -> Value { + todo!() + } + + fn codegen_constrain(&mut self, constrain: &Expression, location: Location) -> Value { + todo!() + } + + fn codegen_assign(&mut self, assign: &ast::Assign) -> Value { + todo!() + } + + fn codegen_semi(&mut self, semi: &Expression) -> Value { + todo!() + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs new file mode 100644 index 00000000000..1d4db3d2086 --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -0,0 +1,14 @@ +use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; +use crate::ssa_refactor::ir::value::ValueId; + +#[derive(Debug, Clone)] +pub(super) enum Value { + Normal(ValueId), + Function(IrFunctionId), + Tuple(Vec), + + // MutableVariable(Variable, Type), + /// Lazily inserting unit values helps prevent cluttering the IR with too many + /// unit literals. + Unit, +} diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index 938a937405c..6a2b97ae19d 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -226,8 +226,12 @@ impl Program { Program { functions, main_function_signature } } - pub fn main(&mut self) -> &mut Function { - &mut self.functions[0] + pub fn main(&self) -> &Function { + &self[Self::main_id()] + } + + pub fn main_mut(&mut self) -> &mut Function { + &mut self[Self::main_id()] } pub fn main_id() -> FuncId { From 7dc56ed27e5fb26f821700861915baa457f5fedc Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 10:46:34 -0500 Subject: [PATCH 02/10] Fix block arguments --- .../src/ssa_refactor/ir/basic_block.rs | 12 +++--- .../src/ssa_refactor/ir/dfg.rs | 6 ++- .../src/ssa_refactor/ir/function.rs | 18 +++++++-- .../src/ssa_refactor/ir/map.rs | 16 ++++++++ .../src/ssa_refactor/ssa_gen/context.rs | 2 +- .../src/ssa_refactor/ssa_gen/mod.rs | 37 ++++++++++--------- 6 files changed, 61 insertions(+), 30 deletions(-) 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 8470c1af579..38620d0e1c9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -1,6 +1,7 @@ use super::{ - instruction::{Instruction, InstructionId, TerminatorInstruction}, + instruction::{InstructionId, TerminatorInstruction}, map::Id, + value::ValueId, }; /// A Basic block is a maximal collection of instructions @@ -12,10 +13,7 @@ use super::{ #[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] pub(crate) struct BasicBlock { /// Parameters to the basic block. - /// The relevant values can be created with this block's id - /// and the index of the parameter, so we only need to remember - /// the number of parameters here. - parameter_count: usize, + parameters: Vec, /// Instructions in the basic block. instructions: Vec, @@ -37,7 +35,7 @@ pub(crate) struct BasicBlock { pub(crate) type BasicBlockId = Id; impl BasicBlock { - pub(super) fn new(parameter_count: usize) -> Self { - Self { parameter_count, instructions: Vec::new(), is_sealed: false, terminator: None } + pub(super) fn new(parameters: Vec) -> Self { + Self { parameters, instructions: Vec::new(), is_sealed: false, terminator: None } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index e167c6aaef2..ad6d614fec0 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -2,7 +2,7 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, function::Signature, instruction::{Instruction, InstructionId}, - map::{DenseMap, Id, SecondaryMap, SparseMap}, + map::{DenseMap, Id, SecondaryMap}, types::Type, value::{Value, ValueId}, }; @@ -75,6 +75,10 @@ impl DataFlowGraph { id } + pub(crate) fn make_value(&mut self, value: Value) -> ValueId { + self.values.insert(value) + } + /// Attaches results to the instruction. /// /// Returns the number of results that this instruction diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index c08989f6fd2..a4f8152805a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -1,11 +1,12 @@ use super::basic_block::{BasicBlock, BasicBlockId}; use super::dfg::DataFlowGraph; -use super::instruction::{Instruction, InstructionId}; +use super::instruction::Instruction; use super::map::{DenseMap, Id, SecondaryMap}; use super::types::Type; +use super::value::Value; +use iter_extended::vecmap; use noirc_errors::Location; -use std::collections::HashMap; /// A function holds a list of instructions. /// These instructions are further grouped into Basic blocks @@ -29,8 +30,19 @@ pub(crate) struct Function { impl Function { pub(crate) fn new(parameter_count: usize) -> Self { + let mut dfg = DataFlowGraph::default(); let mut basic_blocks = DenseMap::default(); - let entry_block = basic_blocks.insert(BasicBlock::new(parameter_count)); + + // The parameters for each function are stored as the block parameters + // of the function's entry block + let entry_block = basic_blocks.insert_with_id(|entry_block| { + // TODO: Give each parameter its correct type + let parameters = vecmap(0..parameter_count, |i| { + dfg.make_value(Value::Param { block: entry_block, position: i, typ: Type::Unit }) + }); + + BasicBlock::new(parameters) + }); Self { basic_blocks, diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index e89061d7d83..53a7db3a5d5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -91,6 +91,14 @@ impl DenseMap { self.storage.push(element); id } + + /// Given the Id of the element being created, adds the element + /// returned by the given function to the map + pub(crate) fn insert_with_id(&mut self, f: impl FnOnce(Id) -> T) -> Id { + let id = Id::new(self.storage.len()); + self.storage.push(f(id)); + id + } } impl Default for DenseMap { @@ -142,6 +150,14 @@ impl SparseMap { id } + /// Given the Id of the element being created, adds the element + /// returned by the given function to the map + pub(crate) fn insert_with_id(&mut self, f: impl FnOnce(Id) -> T) -> Id { + let id = Id::new(self.storage.len()); + self.storage.insert(id, f(id)); + id + } + /// Remove an element from the map and return it. /// This may return None if the element was already /// previously removed from the map. 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 f6be5e806c8..22197e8a885 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -43,7 +43,7 @@ impl<'a> FunctionContext<'a> { pub(super) fn new_function(&mut self, parameters: impl ExactSizeIterator) { self.function_builder.new_function(parameters.len()); - for (i, parameter) in parameters.enumerate() { + for (_i, _parameter) in parameters.enumerate() { todo!("Add block param to definitions") } } 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 73014c3b64a..f566c06e0b7 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -9,7 +9,7 @@ use self::{context::FunctionContext, value::Value}; use super::ssa_builder::ssa_builder::SsaBuilder; -pub fn generate_ssa(mut program: Program) { +pub(crate) fn generate_ssa(program: Program) { let context = Context::new(program); let builder_context = SsaBuilder::default(); @@ -20,8 +20,9 @@ pub fn generate_ssa(mut program: Program) { let mut function_context = FunctionContext::new(parameter_count, &context, &builder_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, _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.iter().map(|(id, ..)| *id)); function_context.codegen_expression(&function.body); } @@ -53,67 +54,67 @@ impl<'a> FunctionContext<'a> { } } - fn codegen_ident(&mut self, ident: &ast::Ident) -> Value { + fn codegen_ident(&mut self, _ident: &ast::Ident) -> Value { todo!() } - fn codegen_literal(&mut self, literal: &ast::Literal) -> Value { + fn codegen_literal(&mut self, _literal: &ast::Literal) -> Value { todo!() } - fn codegen_block(&mut self, block: &[Expression]) -> Value { + fn codegen_block(&mut self, _block: &[Expression]) -> Value { todo!() } - fn codegen_unary(&mut self, unary: &ast::Unary) -> Value { + fn codegen_unary(&mut self, _unary: &ast::Unary) -> Value { todo!() } - fn codegen_binary(&mut self, binary: &ast::Binary) -> Value { + fn codegen_binary(&mut self, _binary: &ast::Binary) -> Value { todo!() } - fn codegen_index(&mut self, index: &ast::Index) -> Value { + fn codegen_index(&mut self, _index: &ast::Index) -> Value { todo!() } - fn codegen_cast(&mut self, cast: &ast::Cast) -> Value { + fn codegen_cast(&mut self, _cast: &ast::Cast) -> Value { todo!() } - fn codegen_for(&mut self, for_expr: &ast::For) -> Value { + fn codegen_for(&mut self, _for_expr: &ast::For) -> Value { todo!() } - fn codegen_if(&mut self, if_expr: &ast::If) -> Value { + fn codegen_if(&mut self, _if_expr: &ast::If) -> Value { todo!() } - fn codegen_tuple(&mut self, tuple: &[Expression]) -> Value { + fn codegen_tuple(&mut self, _tuple: &[Expression]) -> Value { todo!() } - fn codegen_extract_tuple_field(&mut self, tuple: &Expression, index: usize) -> Value { + fn codegen_extract_tuple_field(&mut self, _tuple: &Expression, _index: usize) -> Value { todo!() } - fn codegen_call(&mut self, call: &ast::Call) -> Value { + fn codegen_call(&mut self, _call: &ast::Call) -> Value { todo!() } - fn codegen_let(&mut self, let_expr: &ast::Let) -> Value { + fn codegen_let(&mut self, _let_expr: &ast::Let) -> Value { todo!() } - fn codegen_constrain(&mut self, constrain: &Expression, location: Location) -> Value { + fn codegen_constrain(&mut self, _constrain: &Expression, _location: Location) -> Value { todo!() } - fn codegen_assign(&mut self, assign: &ast::Assign) -> Value { + fn codegen_assign(&mut self, _assign: &ast::Assign) -> Value { todo!() } - fn codegen_semi(&mut self, semi: &Expression) -> Value { + fn codegen_semi(&mut self, _semi: &Expression) -> Value { todo!() } } From d63f5f591e9dd5d3889f0732cedba73f19c417a2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 11:17:00 -0500 Subject: [PATCH 03/10] Fix clippy lint --- .../src/ssa_refactor/ir/basic_block.rs | 2 +- .../ssa_builder/function_builder.rs | 6 +++--- .../src/ssa_refactor/ssa_builder/mod.rs | 19 ++++++++++++++++++- .../ssa_refactor/ssa_builder/ssa_builder.rs | 18 ------------------ .../src/ssa_refactor/ssa_gen/context.rs | 4 ++-- .../src/ssa_refactor/ssa_gen/mod.rs | 4 ++-- 6 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs 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 38620d0e1c9..b11c4dc3f1c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -10,7 +10,7 @@ use super::{ /// /// This means that if one instruction is executed in a basic /// block, then all instructions are executed. ie single-entry single-exit. -#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) struct BasicBlock { /// Parameters to the basic block. parameters: Vec, 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 9ddc56f8377..f024b04eca9 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 @@ -3,7 +3,7 @@ use crate::ssa_refactor::ir::{ function::{Function, FunctionId}, }; -use super::ssa_builder::SsaBuilder; +use super::Builder; /// The per-function context for each ssa function being generated. /// @@ -13,7 +13,7 @@ use super::ssa_builder::SsaBuilder; /// 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 SsaBuilder, + global_context: &'ssa Builder, current_function: Function, current_function_id: FunctionId, @@ -24,7 +24,7 @@ pub(crate) struct FunctionBuilder<'ssa> { } impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(parameters: usize, context: &'ssa SsaBuilder) -> Self { + pub(crate) fn new(parameters: usize, context: &'ssa Builder) -> Self { let new_function = Function::new(parameters); let current_block = new_function.entry_block(); 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 ea08c1821b5..b1d49d5ad69 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -1,2 +1,19 @@ pub(crate) mod function_builder; -pub(crate) mod ssa_builder; + +use crate::ssa_refactor::ir::{ + function::{Function, FunctionId}, + map::AtomicCounter, +}; + +/// 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 Builder { + function_count: AtomicCounter, +} + +impl Builder { + pub(super) fn next_function(&self) -> FunctionId { + self.function_count.next() + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs deleted file mode 100644 index 550e5e65f6d..00000000000 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/ssa_builder.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::ssa_refactor::ir::{ - function::{Function, FunctionId}, - map::AtomicCounter, -}; - -/// The global context while building the ssa representation. -/// Because this may be shared across threads, it is synchronized as necessary. -#[derive(Default)] - -pub(crate) struct SsaBuilder { - function_count: AtomicCounter, -} - -impl SsaBuilder { - pub(super) fn next_function(&self) -> FunctionId { - self.function_count.next() - } -} 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 22197e8a885..42cc000d73f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -4,7 +4,7 @@ use std::sync::{Mutex, RwLock}; use noirc_frontend::monomorphization::ast::{self, LocalId}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; -use crate::ssa_refactor::ssa_builder::ssa_builder::SsaBuilder; +use crate::ssa_refactor::ssa_builder::Builder; use crate::ssa_refactor::{ ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, }; @@ -31,7 +31,7 @@ impl<'a> FunctionContext<'a> { pub(super) fn new( parameter_count: usize, shared_context: &'a Context, - shared_builder_context: &'a SsaBuilder, + shared_builder_context: &'a Builder, ) -> Self { Self { definitions: HashMap::new(), 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 f566c06e0b7..007374e1557 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -7,11 +7,11 @@ use noirc_frontend::monomorphization::ast::{self, Expression, Program}; use self::{context::FunctionContext, value::Value}; -use super::ssa_builder::ssa_builder::SsaBuilder; +use super::ssa_builder::Builder; pub(crate) fn generate_ssa(program: Program) { let context = Context::new(program); - let builder_context = SsaBuilder::default(); + let builder_context = Builder::default(); let main = context.program.main(); // TODO struct parameter counting From 282fd1871f19bd8af7d86aad4fd4126e3a8efc04 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 12:07:57 -0500 Subject: [PATCH 04/10] Use the correct dfg --- crates/noirc_evaluator/src/ssa_refactor/ir/function.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index a4f8152805a..2509a85f435 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -44,12 +44,7 @@ impl Function { BasicBlock::new(parameters) }); - Self { - basic_blocks, - source_locations: SecondaryMap::new(), - entry_block, - dfg: DataFlowGraph::default(), - } + Self { basic_blocks, source_locations: SecondaryMap::new(), entry_block, dfg } } pub(crate) fn entry_block(&self) -> BasicBlockId { From 750e1e0e78191e1b00a84d6a984ee4e6835d6beb Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 12:10:37 -0500 Subject: [PATCH 05/10] Rename contexts to highlight the inner contexts are shared rather than used directly --- .../src/ssa_refactor/ssa_builder/function_builder.rs | 6 +++--- .../src/ssa_refactor/ssa_builder/mod.rs | 4 ++-- .../src/ssa_refactor/ssa_gen/context.rs | 12 ++++++------ .../noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) 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 f024b04eca9..8d90a95332e 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 @@ -3,7 +3,7 @@ use crate::ssa_refactor::ir::{ function::{Function, FunctionId}, }; -use super::Builder; +use super::SharedBuilderContext; /// The per-function context for each ssa function being generated. /// @@ -13,7 +13,7 @@ use super::Builder; /// 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 Builder, + global_context: &'ssa SharedBuilderContext, current_function: Function, current_function_id: FunctionId, @@ -24,7 +24,7 @@ pub(crate) struct FunctionBuilder<'ssa> { } impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(parameters: usize, context: &'ssa Builder) -> Self { + pub(crate) fn new(parameters: usize, context: &'ssa SharedBuilderContext) -> Self { let new_function = Function::new(parameters); let current_block = new_function.entry_block(); 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 b1d49d5ad69..8f9ceed800e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -8,11 +8,11 @@ use crate::ssa_refactor::ir::{ /// 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 Builder { +pub(crate) struct SharedBuilderContext { function_count: AtomicCounter, } -impl Builder { +impl SharedBuilderContext { pub(super) fn next_function(&self) -> FunctionId { self.function_count.next() } 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 42cc000d73f..94fedb7b4cf 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -4,7 +4,7 @@ use std::sync::{Mutex, RwLock}; use noirc_frontend::monomorphization::ast::{self, LocalId}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; -use crate::ssa_refactor::ssa_builder::Builder; +use crate::ssa_refactor::ssa_builder::SharedBuilderContext; use crate::ssa_refactor::{ ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, }; @@ -17,11 +17,11 @@ type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; pub(super) struct FunctionContext<'a> { definitions: HashMap, function_builder: FunctionBuilder<'a>, - shared_context: &'a Context, + shared_context: &'a SharedContext, } /// Shared context for all functions during ssa codegen -pub(super) struct Context { +pub(super) struct SharedContext { functions: RwLock>, function_queue: Mutex, pub(super) program: Program, @@ -30,8 +30,8 @@ pub(super) struct Context { impl<'a> FunctionContext<'a> { pub(super) fn new( parameter_count: usize, - shared_context: &'a Context, - shared_builder_context: &'a Builder, + shared_context: &'a SharedContext, + shared_builder_context: &'a SharedBuilderContext, ) -> Self { Self { definitions: HashMap::new(), @@ -49,7 +49,7 @@ impl<'a> FunctionContext<'a> { } } -impl Context { +impl SharedContext { pub(super) fn new(program: Program) -> Self { Self { functions: Default::default(), function_queue: Default::default(), program } } 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 007374e1557..1da65fafd48 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -1,17 +1,17 @@ mod context; mod value; -use context::Context; +use context::SharedContext; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{self, Expression, Program}; use self::{context::FunctionContext, value::Value}; -use super::ssa_builder::Builder; +use super::ssa_builder::SharedBuilderContext; pub(crate) fn generate_ssa(program: Program) { - let context = Context::new(program); - let builder_context = Builder::default(); + let context = SharedContext::new(program); + let builder_context = SharedBuilderContext::default(); let main = context.program.main(); // TODO struct parameter counting From 6de78543bff8358941275ca8489890b5076a6068 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 15:12:42 -0500 Subject: [PATCH 06/10] Correctly handle function parameters --- .../src/ssa_refactor/ir/basic_block.rs | 19 ++++- .../src/ssa_refactor/ir/dfg.rs | 36 +++++++- .../src/ssa_refactor/ir/function.rs | 32 ++----- .../src/ssa_refactor/ir/instruction.rs | 10 +++ .../src/ssa_refactor/ir/types.rs | 21 +++++ .../ssa_builder/function_builder.rs | 16 ++-- .../src/ssa_refactor/ssa_gen/context.rs | 83 +++++++++++++++++-- .../src/ssa_refactor/ssa_gen/mod.rs | 6 +- .../src/ssa_refactor/ssa_gen/value.rs | 36 ++++++-- .../src/monomorphization/ast.rs | 4 +- 10 files changed, 213 insertions(+), 50 deletions(-) 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 b11c4dc3f1c..431f1647863 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -35,7 +35,24 @@ pub(crate) struct BasicBlock { pub(crate) type BasicBlockId = Id; impl BasicBlock { - pub(super) fn new(parameters: Vec) -> Self { + pub(crate) fn new(parameters: Vec) -> Self { Self { parameters, instructions: Vec::new(), is_sealed: false, terminator: None } } + + pub(crate) fn parameters(&self) -> &[ValueId] { + &self.parameters + } + + pub(crate) fn add_parameter(&mut self, parameter: ValueId) { + self.parameters.push(parameter); + } + + /// Insert an instruction at the end of this block + pub(crate) fn insert_instruction(&mut self, instruction: InstructionId) { + self.instructions.push(instruction); + } + + pub(crate) fn set_terminator(&mut self, terminator: TerminatorInstruction) { + self.terminator = Some(terminator); + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index ad6d614fec0..b456fd08ee4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -7,6 +7,8 @@ use super::{ value::{Value, ValueId}, }; +use iter_extended::vecmap; + #[derive(Debug, Default)] /// A convenience wrapper to store `Value`s. pub(crate) struct ValueList(Vec>); @@ -61,9 +63,31 @@ pub(crate) struct DataFlowGraph { } impl DataFlowGraph { - /// Creates a new `empty` basic block + /// Creates a new basic block with no parameters. + /// After being created, the block is unreachable in the current function + /// until another block is made to jump to it. pub(crate) fn new_block(&mut self) -> BasicBlockId { - todo!() + self.blocks.insert(BasicBlock::new(Vec::new())) + } + + /// Creates a new basic block with the given parameters. + /// After being created, the block is unreachable in the current function + /// until another block is made to jump to it. + pub(crate) fn new_block_with_parameters( + &mut self, + parameter_types: impl Iterator, + ) -> BasicBlockId { + self.blocks.insert_with_id(|entry_block| { + let parameters = vecmap(parameter_types.enumerate(), |(position, typ)| { + self.values.insert(Value::Param { block: entry_block, position, typ }) + }); + + BasicBlock::new(parameters) + }) + } + + pub(crate) fn block_parameters(&self, block: BasicBlockId) -> &[ValueId] { + self.blocks[block].parameters() } /// Inserts a new instruction into the DFG. @@ -149,6 +173,14 @@ impl DataFlowGraph { pub(crate) fn instruction_results(&self, instruction_id: InstructionId) -> &[ValueId] { self.results.get(&instruction_id).expect("expected a list of Values").as_slice() } + + 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(); + let parameter = self.values.insert(Value::Param { block: block_id, position, typ }); + block.add_parameter(parameter); + parameter + } } #[cfg(test)] diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 2509a85f435..1abd6c85367 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -1,11 +1,9 @@ -use super::basic_block::{BasicBlock, BasicBlockId}; +use super::basic_block::BasicBlockId; use super::dfg::DataFlowGraph; use super::instruction::Instruction; -use super::map::{DenseMap, Id, SecondaryMap}; +use super::map::{Id, SecondaryMap}; use super::types::Type; -use super::value::Value; -use iter_extended::vecmap; use noirc_errors::Location; /// A function holds a list of instructions. @@ -16,35 +14,23 @@ use noirc_errors::Location; /// into the current function's context. #[derive(Debug)] pub(crate) struct Function { - /// Basic blocks associated to this particular function - basic_blocks: DenseMap, - /// Maps instructions to source locations source_locations: SecondaryMap, /// The first basic block in the function entry_block: BasicBlockId, - dfg: DataFlowGraph, + pub(crate) dfg: DataFlowGraph, } impl Function { - pub(crate) fn new(parameter_count: usize) -> Self { + /// 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 { let mut dfg = DataFlowGraph::default(); - let mut basic_blocks = DenseMap::default(); - - // The parameters for each function are stored as the block parameters - // of the function's entry block - let entry_block = basic_blocks.insert_with_id(|entry_block| { - // TODO: Give each parameter its correct type - let parameters = vecmap(0..parameter_count, |i| { - dfg.make_value(Value::Param { block: entry_block, position: i, typ: Type::Unit }) - }); - - BasicBlock::new(parameters) - }); - - Self { basic_blocks, source_locations: SecondaryMap::new(), entry_block, dfg } + let entry_block = dfg.new_block(); + Self { source_locations: SecondaryMap::new(), entry_block, dfg } } pub(crate) fn entry_block(&self) -> BasicBlockId { diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 1d5089179d5..81a28b8407c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -129,10 +129,20 @@ pub(crate) enum TerminatorInstruction { else_destination: BasicBlockId, arguments: Vec, }, + /// Unconditional Jump /// /// Jumps to specified `destination` with `arguments` Jmp { destination: BasicBlockId, arguments: Vec }, + + /// Return from the current function with the given return values. + /// + /// All finished functions should have exactly 1 return instruction. + /// Functions with early returns should instead be structured to + /// unconditionally jump to a single exit block with the return values + /// as the block arguments. Then the exit block can terminate in a return + /// instruction returning these values. + Return { return_values: Vec }, } /// A binary instruction in the IR. diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs index f2797423e30..e1f8e8a74d2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs @@ -18,6 +18,27 @@ pub(crate) enum NumericType { pub(crate) enum Type { /// Represents numeric types in the IR, including field elements Numeric(NumericType), + + /// A reference to some value, such as an array + Reference, + + /// A function that may be called directly + Function, + /// The Unit type with a single value Unit, } + +impl Type { + pub(crate) fn signed(bit_size: u32) -> Type { + Type::Numeric(NumericType::Signed { bit_size }) + } + + pub(crate) fn unsigned(bit_size: u32) -> Type { + Type::Numeric(NumericType::Unsigned { bit_size }) + } + + pub(crate) fn field() -> Type { + Type::Numeric(NumericType::NativeField) + } +} 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 8d90a95332e..5e82226d3be 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,8 @@ use crate::ssa_refactor::ir::{ basic_block::BasicBlockId, function::{Function, FunctionId}, + types::Type, + value::ValueId, }; use super::SharedBuilderContext; @@ -24,8 +26,8 @@ pub(crate) struct FunctionBuilder<'ssa> { } impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(parameters: usize, context: &'ssa SharedBuilderContext) -> Self { - let new_function = Function::new(parameters); + pub(crate) fn new(context: &'ssa SharedBuilderContext) -> Self { + let new_function = Function::new(); let current_block = new_function.entry_block(); Self { @@ -38,12 +40,11 @@ impl<'ssa> FunctionBuilder<'ssa> { } /// Finish the current function and create a new function - pub(crate) fn new_function(&mut self, parameters: usize) { - let new_function = Function::new(parameters); + pub(crate) fn new_function(&mut self) { + let new_function = Function::new(); 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(); } @@ -51,4 +52,9 @@ impl<'ssa> FunctionBuilder<'ssa> { 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) + } } 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 94fedb7b4cf..6daf5c5c7f2 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -1,15 +1,18 @@ use std::collections::HashMap; use std::sync::{Mutex, RwLock}; -use noirc_frontend::monomorphization::ast::{self, LocalId}; +use iter_extended::vecmap; +use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; +use noirc_frontend::Signedness; +use crate::ssa_refactor::ir::types::Type; use crate::ssa_refactor::ssa_builder::SharedBuilderContext; use crate::ssa_refactor::{ ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, }; -use super::value::Value; +use super::value::{Nested, Value}; // TODO: Make this a threadsafe queue so we can compile functions in parallel type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; @@ -29,22 +32,84 @@ pub(super) struct SharedContext { impl<'a> FunctionContext<'a> { pub(super) fn new( - parameter_count: usize, + parameters: &Parameters, shared_context: &'a SharedContext, shared_builder_context: &'a SharedBuilderContext, ) -> Self { - Self { + let mut this = Self { definitions: HashMap::new(), - function_builder: FunctionBuilder::new(parameter_count, shared_builder_context), + function_builder: FunctionBuilder::new(shared_builder_context), shared_context, + }; + this.add_parameters_to_scope(parameters); + this + } + + pub(super) fn new_function(&mut self, parameters: &Parameters) { + self.definitions.clear(); + self.function_builder.new_function(); + self.add_parameters_to_scope(parameters); + } + + /// Add each parameter to the current scope, and return the list of parameter types. + /// + /// The returned parameter type list will be flattened, so any struct parameters will + /// be returned as one entry for each field (recursively). + fn add_parameters_to_scope(&mut self, parameters: &Parameters) { + for (id, _, _, typ) in parameters { + self.add_parameter_to_scope(*id, typ); + } + } + + /// Adds a "single" parameter to scope. + /// + /// Single is in quotes here because in the case of tuple parameters, the tuple is flattened + /// 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()); + + self.definitions.insert(parameter_id, parameter_value); + } + + pub(super) fn map_type( + &mut self, + typ: &ast::Type, + mut f: impl FnMut(&mut Self, Type) -> T, + ) -> Nested { + self.map_type_helper(typ, &mut f) + } + + fn map_type_helper( + &mut self, + typ: &ast::Type, + f: &mut impl FnMut(&mut Self, Type) -> T, + ) -> Nested { + match typ { + ast::Type::Tuple(fields) => { + Nested::Tuple(vecmap(fields, |field| self.map_type_helper(field, f))) + } + other => Nested::Single(f(self, Self::convert_non_tuple_type(other))), } } - pub(super) fn new_function(&mut self, parameters: impl ExactSizeIterator) { - self.function_builder.new_function(parameters.len()); + pub(super) fn convert_non_tuple_type(typ: &ast::Type) -> Type { + match typ { + ast::Type::Field => Type::field(), + ast::Type::Array(_, _) => Type::Reference, + ast::Type::Integer(Signedness::Signed, bits) => Type::signed(*bits), + ast::Type::Integer(Signedness::Unsigned, bits) => Type::unsigned(*bits), + ast::Type::Bool => Type::unsigned(1), + ast::Type::String(_) => Type::Reference, + ast::Type::Unit => Type::Unit, + ast::Type::Tuple(_) => panic!("convert_non_tuple_type called on a tuple: {typ}"), + ast::Type::Function(_, _) => Type::Function, - for (_i, _parameter) in parameters.enumerate() { - todo!("Add block param to definitions") + // How should we represent Vecs? + // Are they a struct of array + length + capacity? + // Or are they just references? + ast::Type::Vec(_) => Type::Reference, } } } 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 1da65fafd48..371a4c395f9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -14,16 +14,14 @@ pub(crate) fn generate_ssa(program: Program) { let builder_context = SharedBuilderContext::default(); let main = context.program.main(); - // TODO struct parameter counting - let parameter_count = main.parameters.len(); - let mut function_context = FunctionContext::new(parameter_count, &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.iter().map(|(id, ..)| *id)); + function_context.new_function(&function.parameters); function_context.codegen_expression(&function.body); } } 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 1d4db3d2086..a89638ab95a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -1,14 +1,40 @@ use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; -use crate::ssa_refactor::ir::value::ValueId; +use crate::ssa_refactor::ir::value::ValueId as IrValueId; + +pub(super) enum Nested { + Tuple(Vec>), + Single(T), +} #[derive(Debug, Clone)] -pub(super) enum Value { - Normal(ValueId), +pub(super) enum SingleValue { + Normal(IrValueId), Function(IrFunctionId), - Tuple(Vec), - // MutableVariable(Variable, Type), /// Lazily inserting unit values helps prevent cluttering the IR with too many /// unit literals. Unit, } + +pub(super) type Value = Nested; + +impl Nested { + pub(super) fn flatten(self) -> Vec { + match self { + Nested::Tuple(values) => values.into_iter().flat_map(Nested::flatten).collect(), + Nested::Single(value) => vec![value], + } + } +} + +impl From for Nested { + fn from(id: IrValueId) -> Self { + Self::Single(SingleValue::Normal(id)) + } +} + +impl From for SingleValue { + fn from(id: IrValueId) -> Self { + SingleValue::Normal(id) + } +} diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index 6a2b97ae19d..e4339c8e367 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -175,12 +175,14 @@ pub enum LValue { MemberAccess { object: Box, field_index: usize }, } +pub type Parameters = Vec<(LocalId, /*mutable:*/ bool, /*name:*/ String, Type)>; + #[derive(Debug, Clone)] pub struct Function { pub id: FuncId, pub name: String, - pub parameters: Vec<(LocalId, /*mutable:*/ bool, /*name:*/ String, Type)>, + pub parameters: Parameters, pub body: Expression, pub return_type: Type, From 0bce52a9fce1f267aeca78cfeb3f95767a4f1aa2 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 21 Apr 2023 15:26:25 -0500 Subject: [PATCH 07/10] Rename Nested to Tree; add comment --- .../src/ssa_refactor/ssa_gen/context.rs | 18 ++++++---- .../src/ssa_refactor/ssa_gen/mod.rs | 36 +++++++++---------- .../src/ssa_refactor/ssa_gen/value.rs | 24 ++++++------- 3 files changed, 42 insertions(+), 36 deletions(-) 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 6daf5c5c7f2..02bfee8a87f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -12,13 +12,13 @@ use crate::ssa_refactor::{ ir::function::FunctionId as IrFunctionId, ssa_builder::function_builder::FunctionBuilder, }; -use super::value::{Nested, Value}; +use super::value::{Tree, Values}; // TODO: Make this a threadsafe queue so we can compile functions in parallel type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; pub(super) struct FunctionContext<'a> { - definitions: HashMap, + definitions: HashMap, function_builder: FunctionBuilder<'a>, shared_context: &'a SharedContext, } @@ -73,24 +73,30 @@ impl<'a> FunctionContext<'a> { self.definitions.insert(parameter_id, parameter_value); } + /// Maps the given type to a Tree of the result type. + /// + /// 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, - ) -> Nested { + ) -> Tree { self.map_type_helper(typ, &mut f) } + // 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, - ) -> Nested { + ) -> Tree { match typ { ast::Type::Tuple(fields) => { - Nested::Tuple(vecmap(fields, |field| self.map_type_helper(field, f))) + Tree::Branch(vecmap(fields, |field| self.map_type_helper(field, f))) } - other => Nested::Single(f(self, Self::convert_non_tuple_type(other))), + other => Tree::Leaf(f(self, Self::convert_non_tuple_type(other))), } } 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 371a4c395f9..c340b45eb9b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -5,7 +5,7 @@ use context::SharedContext; use noirc_errors::Location; use noirc_frontend::monomorphization::ast::{self, Expression, Program}; -use self::{context::FunctionContext, value::Value}; +use self::{context::FunctionContext, value::Values}; use super::ssa_builder::SharedBuilderContext; @@ -27,7 +27,7 @@ pub(crate) fn generate_ssa(program: Program) { } impl<'a> FunctionContext<'a> { - fn codegen_expression(&mut self, expr: &Expression) -> Value { + fn codegen_expression(&mut self, expr: &Expression) -> Values { match expr { Expression::Ident(ident) => self.codegen_ident(ident), Expression::Literal(literal) => self.codegen_literal(literal), @@ -52,67 +52,67 @@ impl<'a> FunctionContext<'a> { } } - fn codegen_ident(&mut self, _ident: &ast::Ident) -> Value { + fn codegen_ident(&mut self, _ident: &ast::Ident) -> Values { todo!() } - fn codegen_literal(&mut self, _literal: &ast::Literal) -> Value { + fn codegen_literal(&mut self, _literal: &ast::Literal) -> Values { todo!() } - fn codegen_block(&mut self, _block: &[Expression]) -> Value { + fn codegen_block(&mut self, _block: &[Expression]) -> Values { todo!() } - fn codegen_unary(&mut self, _unary: &ast::Unary) -> Value { + fn codegen_unary(&mut self, _unary: &ast::Unary) -> Values { todo!() } - fn codegen_binary(&mut self, _binary: &ast::Binary) -> Value { + fn codegen_binary(&mut self, _binary: &ast::Binary) -> Values { todo!() } - fn codegen_index(&mut self, _index: &ast::Index) -> Value { + fn codegen_index(&mut self, _index: &ast::Index) -> Values { todo!() } - fn codegen_cast(&mut self, _cast: &ast::Cast) -> Value { + fn codegen_cast(&mut self, _cast: &ast::Cast) -> Values { todo!() } - fn codegen_for(&mut self, _for_expr: &ast::For) -> Value { + fn codegen_for(&mut self, _for_expr: &ast::For) -> Values { todo!() } - fn codegen_if(&mut self, _if_expr: &ast::If) -> Value { + fn codegen_if(&mut self, _if_expr: &ast::If) -> Values { todo!() } - fn codegen_tuple(&mut self, _tuple: &[Expression]) -> Value { + fn codegen_tuple(&mut self, _tuple: &[Expression]) -> Values { todo!() } - fn codegen_extract_tuple_field(&mut self, _tuple: &Expression, _index: usize) -> Value { + fn codegen_extract_tuple_field(&mut self, _tuple: &Expression, _index: usize) -> Values { todo!() } - fn codegen_call(&mut self, _call: &ast::Call) -> Value { + fn codegen_call(&mut self, _call: &ast::Call) -> Values { todo!() } - fn codegen_let(&mut self, _let_expr: &ast::Let) -> Value { + fn codegen_let(&mut self, _let_expr: &ast::Let) -> Values { todo!() } - fn codegen_constrain(&mut self, _constrain: &Expression, _location: Location) -> Value { + fn codegen_constrain(&mut self, _constrain: &Expression, _location: Location) -> Values { todo!() } - fn codegen_assign(&mut self, _assign: &ast::Assign) -> Value { + fn codegen_assign(&mut self, _assign: &ast::Assign) -> Values { todo!() } - fn codegen_semi(&mut self, _semi: &Expression) -> Value { + fn codegen_semi(&mut self, _semi: &Expression) -> Values { todo!() } } 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 a89638ab95a..4b41c6ae102 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/value.rs @@ -1,13 +1,13 @@ use crate::ssa_refactor::ir::function::FunctionId as IrFunctionId; use crate::ssa_refactor::ir::value::ValueId as IrValueId; -pub(super) enum Nested { - Tuple(Vec>), - Single(T), +pub(super) enum Tree { + Branch(Vec>), + Leaf(T), } #[derive(Debug, Clone)] -pub(super) enum SingleValue { +pub(super) enum Value { Normal(IrValueId), Function(IrFunctionId), @@ -16,25 +16,25 @@ pub(super) enum SingleValue { Unit, } -pub(super) type Value = Nested; +pub(super) type Values = Tree; -impl Nested { +impl Tree { pub(super) fn flatten(self) -> Vec { match self { - Nested::Tuple(values) => values.into_iter().flat_map(Nested::flatten).collect(), - Nested::Single(value) => vec![value], + Tree::Branch(values) => values.into_iter().flat_map(Tree::flatten).collect(), + Tree::Leaf(value) => vec![value], } } } -impl From for Nested { +impl From for Values { fn from(id: IrValueId) -> Self { - Self::Single(SingleValue::Normal(id)) + Self::Leaf(Value::Normal(id)) } } -impl From for SingleValue { +impl From for Value { fn from(id: IrValueId) -> Self { - SingleValue::Normal(id) + Value::Normal(id) } } From 6f50c6b97384bc6b85642da7868aba798bc2aadd Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 24 Apr 2023 13:40:10 -0500 Subject: [PATCH 08/10] Add codegen for literals --- crates/noirc_evaluator/src/ssa_refactor/ir.rs | 1 + .../src/ssa_refactor/ir/constant.rs | 56 +++++++++++++++++ .../src/ssa_refactor/ir/dfg.rs | 50 ++++++++++++---- .../src/ssa_refactor/ir/instruction.rs | 39 +++++++----- .../src/ssa_refactor/ir/map.rs | 48 +++++++++++++++ .../src/ssa_refactor/ir/value.rs | 5 +- .../ssa_builder/function_builder.rs | 48 +++++++++++++++ .../src/ssa_refactor/ssa_gen/context.rs | 30 ++++++---- .../src/ssa_refactor/ssa_gen/mod.rs | 60 +++++++++++++++++-- .../src/ssa_refactor/ssa_gen/value.rs | 43 ++++++++++++- 10 files changed, 332 insertions(+), 48 deletions(-) create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index ce63bdc7238..96b00293b9c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -1,4 +1,5 @@ pub(crate) mod basic_block; +pub(crate) mod constant; pub(crate) mod dfg; pub(crate) mod function; pub(crate) mod instruction; 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..567306a6a54 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, 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,39 @@ 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 } + pub(crate) fn make_allocate(&mut self, size: u32) -> (InstructionId, ValueId) { + let id = self.make_instruction(Instruction::Allocate { size }); + self.make_instruction_results(id, Type::Reference); + (id, self.instruction_results(id)[0]) + } + pub(crate) fn make_value(&mut self, value: Value) -> ValueId { self.values.insert(value) } - /// Attaches results to the instruction. + pub(crate) fn 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 +140,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 +203,14 @@ 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); + } } #[cfg(test)] @@ -190,19 +220,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/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 81a28b8407c..86d59b3ed1a 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, }; @@ -38,18 +36,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 +71,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 +109,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![], } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index 53a7db3a5d5..e7e678552ae 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}, }; @@ -186,6 +187,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/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..7854c3b14a7 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, }; @@ -57,4 +60,49 @@ 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.constant(value, typ) + } + + /// Insert a numeric constant into the current function + 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. + 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. + 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. + 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..58de1dd5511 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, } @@ -38,7 +38,7 @@ impl<'a> FunctionContext<'a> { ) -> Self { let mut this = Self { definitions: HashMap::new(), - function_builder: FunctionBuilder::new(shared_builder_context), + builder: FunctionBuilder::new(shared_builder_context), shared_context, }; this.add_parameters_to_scope(parameters); @@ -47,7 +47,7 @@ impl<'a> FunctionContext<'a> { pub(super) fn new_function(&mut self, parameters: &Parameters) { self.definitions.clear(); - self.function_builder.new_function(); + self.builder.new_function(); self.add_parameters_to_scope(parameters); } @@ -67,8 +67,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 +82,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..d335d912be8 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -1,13 +1,18 @@ 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); @@ -56,8 +61,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() + } +} From b8d718cbe00c4d74ec9fcd525b5f8afcbb040faf Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 24 Apr 2023 16:05:48 -0500 Subject: [PATCH 09/10] PR feedback --- crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs | 14 +++++--------- .../ssa_refactor/ssa_builder/function_builder.rs | 9 ++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 567306a6a54..f96b4717d64 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -103,24 +103,20 @@ impl DataFlowGraph { /// 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 } - pub(crate) fn make_allocate(&mut self, size: u32) -> (InstructionId, ValueId) { - let id = self.make_instruction(Instruction::Allocate { size }); - self.make_instruction_results(id, Type::Reference); - (id, self.instruction_results(id)[0]) - } - + /// 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) } - pub(crate) fn constant(&mut self, value: FieldElement, typ: Type) -> ValueId { + /// 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 }) } 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 7854c3b14a7..98fdce4e119 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 @@ -63,10 +63,10 @@ impl<'ssa> FunctionBuilder<'ssa> { /// Insert a numeric constant into the current function pub(crate) fn numeric_constant(&mut self, value: FieldElement, typ: Type) -> ValueId { - self.current_function.dfg.constant(value, typ) + self.current_function.dfg.make_constant(value, typ) } - /// Insert a numeric constant into the current function + /// 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()) } @@ -78,7 +78,8 @@ impl<'ssa> FunctionBuilder<'ssa> { } /// Insert an allocate instruction at the end of the current block, allocating the - /// given amount of field elements. + /// 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] @@ -87,6 +88,7 @@ impl<'ssa> FunctionBuilder<'ssa> { /// 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] @@ -100,6 +102,7 @@ impl<'ssa> FunctionBuilder<'ssa> { /// 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 })); From 12691f7b81fb17adc1c029f80e0e5d4e591846e1 Mon Sep 17 00:00:00 2001 From: jfecher Date: Mon, 24 Apr 2023 17:09:55 -0400 Subject: [PATCH 10/10] chore(ssa refactor): Add debug printing for the new ssa ir (#1211) Implement debug printing for the new ssa ir --- crates/noirc_evaluator/src/ssa_refactor/ir.rs | 1 + .../src/ssa_refactor/ir/basic_block.rs | 22 ++++ .../src/ssa_refactor/ir/dfg.rs | 30 ++++- .../src/ssa_refactor/ir/function.rs | 15 ++- .../src/ssa_refactor/ir/instruction.rs | 21 +++- .../src/ssa_refactor/ir/map.rs | 6 + .../src/ssa_refactor/ir/printer.rs | 115 ++++++++++++++++++ .../src/ssa_refactor/ir/types.rs | 21 ++++ .../ssa_builder/function_builder.rs | 8 +- .../src/ssa_refactor/ssa_gen/context.rs | 7 +- .../src/ssa_refactor/ssa_gen/mod.rs | 5 +- 11 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index 96b00293b9c..851b86e511f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -4,5 +4,6 @@ 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/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index f96b4717d64..f92cae79b75 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -1,6 +1,6 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, - constant::NumericConstant, + constant::{NumericConstant, NumericConstantId}, function::Signature, instruction::{Instruction, InstructionId}, map::{DenseMap, Id, SecondaryMap, TwoWayMap}, @@ -209,6 +209,34 @@ impl DataFlowGraph { } } +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)] mod tests { use super::DataFlowGraph; 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 86d59b3ed1a..442f1dbd47e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -15,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. @@ -189,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 e7e678552ae..bb526076e3b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -69,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 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/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index 98fdce4e119..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 @@ -29,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 { @@ -43,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)); 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 58de1dd5511..32133feea13 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -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(), - 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.builder.new_function(); + self.builder.new_function(name); self.add_parameters_to_scope(parameters); } 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 d335d912be8..2f9c6646282 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -19,14 +19,15 @@ pub(crate) fn generate_ssa(program: 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); } }