Skip to content

Commit ef07731

Browse files
authored
chore: Initial SSA refactor module (#1113)
* 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
1 parent b799c8a commit ef07731

File tree

12 files changed

+613
-0
lines changed

12 files changed

+613
-0
lines changed
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod variable;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// A variable in the SSA IR.
2+
/// By definition, a variable can only be defined once.
3+
///
4+
/// As in Cranelift, we also allow variable use before definition.
5+
/// This will produce side-effects which will need to be handled
6+
/// before sealing a block.
7+
pub struct Variable(u32);
8+
9+
impl From<u32> for Variable {
10+
fn from(value: u32) -> Self {
11+
Variable(value)
12+
}
13+
}
14+
impl From<u16> for Variable {
15+
fn from(value: u16) -> Self {
16+
Variable(value as u32)
17+
}
18+
}
19+
impl From<u8> for Variable {
20+
fn from(value: u8) -> Self {
21+
Variable(value as u32)
22+
}
23+
}

crates/noirc_evaluator/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
mod errors;
77
mod ssa;
88

9+
// SSA code to create the SSA based IR
10+
// for functions and execute different optimizations.
11+
pub mod ssa_refactor;
12+
// Frontend helper module to translate a different AST
13+
// into the SSA IR.
14+
pub mod frontend;
15+
916
use acvm::{
1017
acir::circuit::{opcodes::Opcode as AcirOpcode, Circuit, PublicInputs},
1118
acir::native_types::{Expression, Witness},
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! SSA stands for Single Static Assignment
2+
//! The IR presented in this module will already
3+
//! be in SSA form and will be used to apply
4+
//! conventional optimizations like Common Subexpression
5+
//! elimination and constant folding.
6+
//!
7+
//! This module heavily borrows from Cranelift
8+
#[allow(dead_code)]
9+
mod basic_block;
10+
#[allow(dead_code)]
11+
mod dfg;
12+
#[allow(dead_code)]
13+
mod ir;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use super::ir::instruction::{Instruction, TerminatorInstruction};
2+
3+
/// A Basic block is a maximal collection of instructions
4+
/// such that there are only jumps at the end of block
5+
/// and one can only enter the block from the beginning.
6+
///
7+
/// This means that if one instruction is executed in a basic
8+
/// block, then all instructions are executed. ie single-entry single-exit.
9+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
10+
pub(crate) struct BasicBlock {
11+
/// Arguments to the basic block.
12+
phi_nodes: Vec<BlockArguments>,
13+
/// Instructions in the basic block.
14+
instructions: Vec<Instruction>,
15+
16+
/// A basic block is considered sealed
17+
/// if no further predecessors will be added to it.
18+
/// Since only filled blocks can have successors,
19+
/// predecessors are always filled.
20+
is_sealed: bool,
21+
22+
/// The terminating instruction for the basic block.
23+
///
24+
/// This will be a control flow instruction.
25+
terminator: TerminatorInstruction,
26+
}
27+
28+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
29+
/// An identifier for a Basic Block.
30+
pub(crate) struct BasicBlockId;
31+
32+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
33+
/// Arguments to the basic block.
34+
/// We use the modern Crane-lift strategy
35+
/// of representing phi nodes as basic block
36+
/// arguments.
37+
pub(crate) struct BlockArguments;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use super::{
2+
basic_block::{BasicBlock, BasicBlockId},
3+
ir::{
4+
extfunc::{SigRef, Signature},
5+
instruction::{Instruction, InstructionId, Instructions},
6+
types::Typ,
7+
value::{Value, ValueId},
8+
},
9+
};
10+
use std::collections::HashMap;
11+
12+
#[derive(Debug, Default)]
13+
/// A convenience wrapper to store `Value`s.
14+
pub(crate) struct ValueList(Vec<ValueId>);
15+
16+
impl ValueList {
17+
/// Inserts an element to the back of the list and
18+
/// returns the `position`
19+
pub(crate) fn push(&mut self, value: ValueId) -> usize {
20+
self.0.push(value);
21+
self.len() - 1
22+
}
23+
/// Returns the number of values in the list.
24+
fn len(&self) -> usize {
25+
self.0.len()
26+
}
27+
28+
/// Removes all items from the list.
29+
fn clear(&mut self) {
30+
self.0.clear();
31+
}
32+
/// Returns the ValueId's as a slice.
33+
pub(crate) fn as_slice(&self) -> &[ValueId] {
34+
&self.0
35+
}
36+
}
37+
#[derive(Debug, Default)]
38+
pub(crate) struct DataFlowGraph {
39+
/// All of the instructions in a function
40+
instructions: Instructions,
41+
42+
/// Stores the results for a particular instruction.
43+
///
44+
/// An instruction may return multiple values
45+
/// and for this, we will also use the cranelift strategy
46+
/// to fetch them via indices.
47+
///
48+
/// Currently, we need to define them in a better way
49+
/// Call instructions require the func signature, but
50+
/// other instructions may need some more reading on my part
51+
results: HashMap<InstructionId, ValueList>,
52+
53+
/// Storage for all of the values defined in this
54+
/// function.
55+
values: HashMap<ValueId, Value>,
56+
57+
/// Function signatures of external methods
58+
signatures: HashMap<SigRef, Signature>,
59+
60+
/// All blocks in a function
61+
blocks: HashMap<BasicBlockId, BasicBlock>,
62+
}
63+
64+
impl DataFlowGraph {
65+
/// Creates a new `empty` basic block
66+
pub(crate) fn new_block(&mut self) -> BasicBlockId {
67+
todo!()
68+
}
69+
70+
/// Inserts a new instruction into the DFG.
71+
pub(crate) fn make_instruction(&mut self, instruction_data: Instruction) -> InstructionId {
72+
let id = self.instructions.add_instruction(instruction_data);
73+
74+
// Create a new vector to store the potential results
75+
// for the instruction.
76+
self.results.insert(id, Default::default());
77+
78+
id
79+
}
80+
81+
/// Attaches results to the instruction.
82+
///
83+
/// Returns the number of results that this instruction
84+
/// produces.
85+
pub(crate) fn make_instruction_results(
86+
&mut self,
87+
instruction_id: InstructionId,
88+
ctrl_typevar: Typ,
89+
) -> usize {
90+
// Clear all of the results instructions associated with this
91+
// instruction.
92+
self.results.get_mut(&instruction_id).expect("all instructions should have a `result` allocation when instruction was added to the DFG").clear();
93+
94+
// Get all of the types that this instruction produces
95+
// and append them as results.
96+
let typs = self.instruction_result_types(instruction_id, ctrl_typevar);
97+
let num_typs = typs.len();
98+
99+
for typ in typs {
100+
self.append_result(instruction_id, typ);
101+
}
102+
103+
num_typs
104+
}
105+
106+
/// Return the result types of this instruction.
107+
///
108+
/// For example, an addition instruction will return
109+
/// one type which is the type of the operands involved.
110+
/// This is the `ctrl_typevar` in this case.
111+
fn instruction_result_types(
112+
&self,
113+
instruction_id: InstructionId,
114+
ctrl_typevar: Typ,
115+
) -> Vec<Typ> {
116+
// Check if it is a call instruction. If so, we don't support that yet
117+
let ins_data = self.instructions.get_instruction(instruction_id);
118+
match ins_data {
119+
Instruction::Call { .. } => todo!("function calls are not supported yet"),
120+
ins => ins.return_types(ctrl_typevar),
121+
}
122+
}
123+
124+
/// Appends a result type to the instruction.
125+
pub(crate) fn append_result(&mut self, instruction_id: InstructionId, typ: Typ) -> ValueId {
126+
let next_value_id = self.next_value();
127+
128+
// Add value to the list of results for this instruction
129+
let res_position = self.results.get_mut(&instruction_id).unwrap().push(next_value_id);
130+
131+
self.make_value(Value::Instruction {
132+
typ,
133+
position: res_position as u16,
134+
instruction: instruction_id,
135+
})
136+
}
137+
138+
/// Stores a value and returns its `ValueId` reference.
139+
fn make_value(&mut self, data: Value) -> ValueId {
140+
let next_value = self.next_value();
141+
142+
self.values.insert(next_value, data);
143+
144+
next_value
145+
}
146+
147+
/// Returns the next `ValueId`
148+
fn next_value(&self) -> ValueId {
149+
ValueId(self.values.len() as u32)
150+
}
151+
152+
/// Returns the number of instructions
153+
/// inserted into functions.
154+
pub(crate) fn num_instructions(&self) -> usize {
155+
self.instructions.num_instructions()
156+
}
157+
158+
/// Returns all of result values which are attached to this instruction.
159+
pub(crate) fn instruction_results(&self, instruction_id: InstructionId) -> &[ValueId] {
160+
self.results.get(&instruction_id).expect("expected a list of Values").as_slice()
161+
}
162+
}
163+
164+
#[cfg(test)]
165+
mod tests {
166+
use super::DataFlowGraph;
167+
use crate::ssa_refactor::ir::{
168+
instruction::Instruction,
169+
types::{NumericType, Typ},
170+
};
171+
use acvm::FieldElement;
172+
173+
#[test]
174+
fn make_instruction() {
175+
let mut dfg = DataFlowGraph::default();
176+
let ins = Instruction::Immediate { value: FieldElement::from(0u128) };
177+
let ins_id = dfg.make_instruction(ins);
178+
179+
let num_results =
180+
dfg.make_instruction_results(ins_id, Typ::Numeric(NumericType::NativeField));
181+
182+
let results = dfg.instruction_results(ins_id);
183+
184+
assert_eq!(results.len(), num_results);
185+
}
186+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub(crate) mod extfunc;
2+
mod function;
3+
pub(crate) mod instruction;
4+
pub(crate) mod types;
5+
pub(crate) mod value;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Like Crane-lift all functions outside of the current function is seen as
2+
//! external.
3+
//! To reference external functions, one uses
4+
5+
use super::types::Typ;
6+
7+
#[derive(Debug, Default, Clone)]
8+
pub(crate) struct Signature {
9+
pub(crate) params: Vec<Typ>,
10+
pub(crate) returns: Vec<Typ>,
11+
}
12+
/// Reference to a `Signature` in a map inside of
13+
/// a functions DFG.
14+
#[derive(Debug, Default, Clone, Copy)]
15+
pub(crate) struct SigRef(pub(crate) u32);
16+
17+
#[test]
18+
fn sign_smoke() {
19+
let mut signature = Signature::default();
20+
21+
signature.params.push(Typ::Numeric(super::types::NumericType::NativeField));
22+
signature.returns.push(Typ::Numeric(super::types::NumericType::Unsigned { bit_size: 32 }));
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::ssa_refactor::basic_block::{BasicBlock, BasicBlockId};
2+
3+
use super::instruction::Instruction;
4+
5+
use noirc_errors::Location;
6+
use std::collections::HashMap;
7+
8+
/// A function holds a list of instructions.
9+
/// These instructions are further grouped into
10+
/// Basic blocks
11+
#[derive(Debug)]
12+
pub(crate) struct Function {
13+
/// Basic blocks associated to this particular function
14+
basic_blocks: HashMap<BasicBlockId, BasicBlock>,
15+
16+
/// Maps instructions to source locations
17+
source_locations: HashMap<Instruction, Location>,
18+
19+
/// The first basic block in the function
20+
entry_block: BasicBlockId,
21+
}
22+
23+
/// FunctionId is a reference for a function
24+
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
25+
pub(crate) struct FunctionId(pub(crate) u32);

0 commit comments

Comments
 (0)