Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ssa refactor): Add DenseMap and SparseMap types #1184

Merged
merged 7 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 27 additions & 38 deletions crates/noirc_evaluator/src/ssa_refactor/dfg.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use super::{
basic_block::{BasicBlock, BasicBlockId},
ir::{
extfunc::{SigRef, Signature},
extfunc::Signature,
instruction::{Instruction, InstructionId, Instructions},
types::Typ,
map::{Id, SparseMap},
types::Type,
value::{Value, ValueId},
},
};
use std::collections::HashMap;

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

impl ValueList {
/// Inserts an element to the back of the list and
Expand All @@ -34,6 +35,7 @@ impl ValueList {
&self.0
}
}

#[derive(Debug, Default)]
pub(crate) struct DataFlowGraph {
/// All of the instructions in a function
Expand All @@ -52,13 +54,13 @@ pub(crate) struct DataFlowGraph {

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

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

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

impl DataFlowGraph {
Expand All @@ -69,12 +71,10 @@ impl DataFlowGraph {

/// 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);
let id = self.instructions.push(instruction_data);

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

id
}

Expand All @@ -85,7 +85,7 @@ impl DataFlowGraph {
pub(crate) fn make_instruction_results(
&mut self,
instruction_id: InstructionId,
ctrl_typevar: Typ,
ctrl_typevar: Type,
) -> usize {
// Clear all of the results instructions associated with this
// instruction.
Expand All @@ -111,48 +111,37 @@ impl DataFlowGraph {
fn instruction_result_types(
&self,
instruction_id: InstructionId,
ctrl_typevar: Typ,
) -> Vec<Typ> {
ctrl_typevar: Type,
) -> Vec<Type> {
// Check if it is a call instruction. If so, we don't support that yet
let ins_data = self.instructions.get_instruction(instruction_id);
let ins_data = &self.instructions[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();
pub(crate) fn append_result(&mut self, instruction_id: InstructionId, typ: Type) -> ValueId {
let results = self.results.get_mut(&instruction_id).unwrap();
let expected_res_position = results.len();

// 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 {
let value_id = self.values.push(Value::Instruction {
typ,
position: res_position as u16,
position: expected_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)
// Add value to the list of results for this instruction
let actual_res_position = results.push(value_id);
assert_eq!(actual_res_position, expected_res_position);
value_id
}

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

/// Returns all of result values which are attached to this instruction.
Expand All @@ -166,7 +155,7 @@ mod tests {
use super::DataFlowGraph;
use crate::ssa_refactor::ir::{
instruction::Instruction,
types::{NumericType, Typ},
types::{NumericType, Type},
};
use acvm::FieldElement;

Expand All @@ -177,7 +166,7 @@ mod tests {
let ins_id = dfg.make_instruction(ins);

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

let results = dfg.instruction_results(ins_id);

Expand Down
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod extfunc;
mod function;
pub(crate) mod instruction;
pub(crate) mod map;
pub(crate) mod types;
pub(crate) mod value;
17 changes: 7 additions & 10 deletions crates/noirc_evaluator/src/ssa_refactor/ir/extfunc.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
//! Like Crane-lift all functions outside of the current function is seen as
//! external.
//! To reference external functions, one uses
//! To reference external functions, one must first import the function signature
//! into the current function's context.

use super::types::Typ;
use super::types::Type;

#[derive(Debug, Default, Clone)]
pub(crate) struct Signature {
pub(crate) params: Vec<Typ>,
pub(crate) returns: Vec<Typ>,
pub(crate) params: Vec<Type>,
pub(crate) returns: Vec<Type>,
}
/// 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 }));
signature.params.push(Type::Numeric(super::types::NumericType::NativeField));
signature.returns.push(Type::Numeric(super::types::NumericType::Unsigned { bit_size: 32 }));
}
95 changes: 28 additions & 67 deletions crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
use std::collections::HashMap;

use acvm::FieldElement;

use super::{function::FunctionId, types::Typ, value::ValueId};
use super::{
function::FunctionId,
map::{Id, SparseMap},
types::Type,
value::ValueId,
};
use crate::ssa_refactor::basic_block::{BasicBlockId, BlockArguments};

/// Map of instructions.
/// This is similar to Arena.
#[derive(Debug, Default)]
pub(crate) struct Instructions(HashMap<InstructionId, Instruction>);

impl Instructions {
/// Adds an instruction to the map and returns a
/// reference to the instruction.
pub(crate) fn add_instruction(&mut self, ins: Instruction) -> InstructionId {
let id = InstructionId(self.0.len() as u32);
self.0.insert(id, ins);
id
}

/// Fetch the instruction corresponding to this
/// instruction id.
///
/// Panics if there is no such instruction, since instructions cannot be
/// deleted.
pub(crate) fn get_instruction(&self, ins_id: InstructionId) -> &Instruction {
self.0.get(&ins_id).expect("ICE: instructions cannot be deleted")
}
// Container for all Instructions, per-function
pub(crate) type Instructions = SparseMap<Instruction>;

/// Returns the number of instructions stored in the map.
pub(crate) fn num_instructions(&self) -> usize {
self.0.len()
}
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// Reference to an instruction
pub(crate) struct InstructionId(u32);
pub(crate) type InstructionId = Id<Instruction>;

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
/// These are similar to built-ins in other languages.
Expand All @@ -52,52 +28,35 @@ pub(crate) struct IntrinsicOpcodes;
/// Instructions are used to perform tasks.
/// The instructions that the IR is able to specify are listed below.
pub(crate) enum Instruction {
// Binary Operations
/// Binary Operations like +, -, *, /, ==, !=
Binary(Binary),

// Unary Operations
jfecher marked this conversation as resolved.
Show resolved Hide resolved
//
/// Converts `Value` into Typ
Cast(ValueId, Typ),
Cast(ValueId, Type),

/// Computes a bit wise not
Not(ValueId),

/// Truncates `value` to `bit_size`
Truncate {
value: ValueId,
bit_size: u32,
max_bit_size: u32,
},
Truncate { value: ValueId, bit_size: u32, max_bit_size: u32 },

/// Constrains a value to be equal to true
Constrain(ValueId),

/// Performs a function call with a list of its arguments.
Call {
func: FunctionId,
arguments: Vec<ValueId>,
},
Call { func: FunctionId, arguments: Vec<ValueId> },
/// Performs a call to an intrinsic function and stores the
/// results in `return_arguments`.
Intrinsic {
func: IntrinsicOpcodes,
arguments: Vec<ValueId>,
},
Intrinsic { func: IntrinsicOpcodes, arguments: Vec<ValueId> },

/// Loads a value from memory.
Load(ValueId),

/// Writes a value to memory.
Store {
destination: ValueId,
value: ValueId,
},
Store { destination: ValueId, value: ValueId },

/// Stores an Immediate value
Immediate {
value: FieldElement,
},
Immediate { value: FieldElement },
}

impl Instruction {
Expand All @@ -106,7 +65,7 @@ impl Instruction {
pub(crate) fn num_fixed_results(&self) -> usize {
match self {
Instruction::Binary(_) => 1,
Instruction::Cast(_, _) => 0,
Instruction::Cast(..) => 0,
Instruction::Not(_) => 1,
Instruction::Truncate { .. } => 1,
Instruction::Constrain(_) => 0,
Expand All @@ -125,7 +84,7 @@ impl Instruction {
pub(crate) fn num_fixed_arguments(&self) -> usize {
match self {
Instruction::Binary(_) => 2,
Instruction::Cast(_, _) => 1,
Instruction::Cast(..) => 1,
Instruction::Not(_) => 1,
Instruction::Truncate { .. } => 1,
Instruction::Constrain(_) => 1,
Expand All @@ -141,7 +100,7 @@ impl Instruction {
}

/// Returns the types that this instruction will return.
pub(crate) fn return_types(&self, ctrl_typevar: Typ) -> Vec<Typ> {
pub(crate) fn return_types(&self, ctrl_typevar: Type) -> Vec<Type> {
match self {
Instruction::Binary(_) => vec![ctrl_typevar],
Instruction::Cast(_, typ) => vec![*typ],
Expand Down Expand Up @@ -221,14 +180,16 @@ pub(crate) enum BinaryOp {

#[test]
fn smoke_instructions_map_duplicate() {
let ins = Instruction::Cast(ValueId(0), Typ::Unit);
let same_ins = Instruction::Cast(ValueId(0), Typ::Unit);
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.add_instruction(ins);
let id_same_ins = ins_map.add_instruction(same_ins);
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.
Expand All @@ -241,9 +202,9 @@ fn num_instructions_smoke() {

let mut ins_map = Instructions::default();
for i in 0..n {
let ins = Instruction::Cast(ValueId(i as u32), Typ::Unit);
ins_map.add_instruction(ins);
let ins = Instruction::Not(Id::test_new(i));
ins_map.push(ins);
}

assert_eq!(n, ins_map.num_instructions())
assert_eq!(n, ins_map.len())
}
Loading