Skip to content

Commit

Permalink
chore: Initial SSA refactor module (#1113)
Browse files Browse the repository at this point in the history
* add ssa refactor module

* add more stub code

* move value to its own file

* add function

* Rollback to simpler case

* move types to types module

* review

* make types pub(crate)

* allow dead code

* add offline code

* remove cfg

* clean up

* remove changes to old code

* fix clippy

* remove builder.rs

* cargo fmt

* clippy
  • Loading branch information
kevaundray authored Apr 19, 2023
1 parent b799c8a commit ef07731
Show file tree
Hide file tree
Showing 12 changed files with 613 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/frontend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod variable;
23 changes: 23 additions & 0 deletions crates/noirc_evaluator/src/frontend/variable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// A variable in the SSA IR.
/// By definition, a variable can only be defined once.
///
/// As in Cranelift, we also allow variable use before definition.
/// This will produce side-effects which will need to be handled
/// before sealing a block.
pub struct Variable(u32);

impl From<u32> for Variable {
fn from(value: u32) -> Self {
Variable(value)
}
}
impl From<u16> for Variable {
fn from(value: u16) -> Self {
Variable(value as u32)
}
}
impl From<u8> for Variable {
fn from(value: u8) -> Self {
Variable(value as u32)
}
}
7 changes: 7 additions & 0 deletions crates/noirc_evaluator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
mod errors;
mod ssa;

// SSA code to create the SSA based IR
// for functions and execute different optimizations.
pub mod ssa_refactor;
// Frontend helper module to translate a different AST
// into the SSA IR.
pub mod frontend;

use acvm::{
acir::circuit::{opcodes::Opcode as AcirOpcode, Circuit, PublicInputs},
acir::native_types::{Expression, Witness},
Expand Down
13 changes: 13 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! SSA stands for Single Static Assignment
//! The IR presented in this module will already
//! be in SSA form and will be used to apply
//! conventional optimizations like Common Subexpression
//! elimination and constant folding.
//!
//! This module heavily borrows from Cranelift
#[allow(dead_code)]
mod basic_block;
#[allow(dead_code)]
mod dfg;
#[allow(dead_code)]
mod ir;
37 changes: 37 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/basic_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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<BlockArguments>,
/// Instructions in the basic block.
instructions: Vec<Instruction>,

/// 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;
186 changes: 186 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/dfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use super::{
basic_block::{BasicBlock, BasicBlockId},
ir::{
extfunc::{SigRef, Signature},
instruction::{Instruction, InstructionId, Instructions},
types::Typ,
value::{Value, ValueId},
},
};
use std::collections::HashMap;

#[derive(Debug, Default)]
/// A convenience wrapper to store `Value`s.
pub(crate) struct ValueList(Vec<ValueId>);

impl ValueList {
/// Inserts an element to the back of the list and
/// returns the `position`
pub(crate) fn push(&mut self, value: ValueId) -> usize {
self.0.push(value);
self.len() - 1
}
/// Returns the number of values in the list.
fn len(&self) -> usize {
self.0.len()
}

/// Removes all items from the list.
fn clear(&mut self) {
self.0.clear();
}
/// Returns the ValueId's as a slice.
pub(crate) fn as_slice(&self) -> &[ValueId] {
&self.0
}
}
#[derive(Debug, Default)]
pub(crate) struct DataFlowGraph {
/// All of the instructions in a function
instructions: Instructions,

/// Stores the results for a particular instruction.
///
/// An instruction may return multiple values
/// and for this, we will also use the cranelift strategy
/// to fetch them via indices.
///
/// 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<InstructionId, ValueList>,

/// Storage for all of the values defined in this
/// function.
values: HashMap<ValueId, Value>,

/// Function signatures of external methods
signatures: HashMap<SigRef, Signature>,

/// All blocks in a function
blocks: HashMap<BasicBlockId, BasicBlock>,
}

impl DataFlowGraph {
/// Creates a new `empty` basic block
pub(crate) fn new_block(&mut self) -> BasicBlockId {
todo!()
}

/// Inserts a new instruction into the DFG.
pub(crate) fn make_instruction(&mut self, instruction_data: Instruction) -> InstructionId {
let id = self.instructions.add_instruction(instruction_data);

// Create a new vector to store the potential results
// for the instruction.
self.results.insert(id, Default::default());

id
}

/// Attaches results to the instruction.
///
/// Returns the number of results that this instruction
/// produces.
pub(crate) fn make_instruction_results(
&mut self,
instruction_id: InstructionId,
ctrl_typevar: Typ,
) -> usize {
// 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();

// 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
}

/// Return the result types of this instruction.
///
/// For example, an addition instruction will return
/// one type which is the type of the operands involved.
/// This is the `ctrl_typevar` in this case.
fn instruction_result_types(
&self,
instruction_id: InstructionId,
ctrl_typevar: Typ,
) -> Vec<Typ> {
// Check if it is a call instruction. If so, we don't support that yet
let ins_data = self.instructions.get_instruction(instruction_id);
match ins_data {
Instruction::Call { .. } => todo!("function calls are not supported yet"),
ins => ins.return_types(ctrl_typevar),
}
}

/// Appends a result type to the instruction.
pub(crate) fn append_result(&mut self, instruction_id: InstructionId, typ: Typ) -> ValueId {
let next_value_id = self.next_value();

// Add value to the list of results for this instruction
let res_position = self.results.get_mut(&instruction_id).unwrap().push(next_value_id);

self.make_value(Value::Instruction {
typ,
position: res_position as u16,
instruction: instruction_id,
})
}

/// Stores a value and returns its `ValueId` reference.
fn make_value(&mut self, data: Value) -> ValueId {
let next_value = self.next_value();

self.values.insert(next_value, data);

next_value
}

/// Returns the next `ValueId`
fn next_value(&self) -> ValueId {
ValueId(self.values.len() as u32)
}

/// Returns the number of instructions
/// inserted into functions.
pub(crate) fn num_instructions(&self) -> usize {
self.instructions.num_instructions()
}

/// Returns all of result values which are attached to this instruction.
pub(crate) fn instruction_results(&self, instruction_id: InstructionId) -> &[ValueId] {
self.results.get(&instruction_id).expect("expected a list of Values").as_slice()
}
}

#[cfg(test)]
mod tests {
use super::DataFlowGraph;
use crate::ssa_refactor::ir::{
instruction::Instruction,
types::{NumericType, Typ},
};
use acvm::FieldElement;

#[test]
fn make_instruction() {
let mut dfg = DataFlowGraph::default();
let ins = Instruction::Immediate { value: FieldElement::from(0u128) };
let ins_id = dfg.make_instruction(ins);

let num_results =
dfg.make_instruction_results(ins_id, Typ::Numeric(NumericType::NativeField));

let results = dfg.instruction_results(ins_id);

assert_eq!(results.len(), num_results);
}
}
5 changes: 5 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub(crate) mod extfunc;
mod function;
pub(crate) mod instruction;
pub(crate) mod types;
pub(crate) mod value;
23 changes: 23 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Like Crane-lift all functions outside of the current function is seen as
//! external.
//! To reference external functions, one uses

use super::types::Typ;

#[derive(Debug, Default, Clone)]
pub(crate) struct Signature {
pub(crate) params: Vec<Typ>,
pub(crate) returns: Vec<Typ>,
}
/// Reference to a `Signature` in a map inside of
/// a functions DFG.
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct SigRef(pub(crate) u32);

#[test]
fn sign_smoke() {
let mut signature = Signature::default();

signature.params.push(Typ::Numeric(super::types::NumericType::NativeField));
signature.returns.push(Typ::Numeric(super::types::NumericType::Unsigned { bit_size: 32 }));
}
25 changes: 25 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::ssa_refactor::basic_block::{BasicBlock, BasicBlockId};

use super::instruction::Instruction;

use noirc_errors::Location;
use std::collections::HashMap;

/// A function holds a list of instructions.
/// These instructions are further grouped into
/// Basic blocks
#[derive(Debug)]
pub(crate) struct Function {
/// Basic blocks associated to this particular function
basic_blocks: HashMap<BasicBlockId, BasicBlock>,

/// Maps instructions to source locations
source_locations: HashMap<Instruction, Location>,

/// The first basic block in the function
entry_block: BasicBlockId,
}

/// FunctionId is a reference for a function
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub(crate) struct FunctionId(pub(crate) u32);
Loading

0 comments on commit ef07731

Please sign in to comment.