Skip to content

Commit

Permalink
chore(ssa refactor): Add DenseMap and SparseMap types (#1184)
Browse files Browse the repository at this point in the history
* Add DenseMap and SparseMap

* Update crates/noirc_evaluator/src/ssa_refactor/ir/map.rs

Co-authored-by: kevaundray <kevtheappdev@gmail.com>

* Update crates/noirc_evaluator/src/ssa_refactor/ir/map.rs

Co-authored-by: kevaundray <kevtheappdev@gmail.com>

* Apply suggestions from code review

Co-authored-by: kevaundray <kevtheappdev@gmail.com>

* Apply cfg test suggestion

* Revert removal of Cast

* Fix typo

---------

Co-authored-by: kevaundray <kevtheappdev@gmail.com>
  • Loading branch information
jfecher and kevaundray committed Apr 20, 2023
1 parent 750ed77 commit e1ba4f8
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 126 deletions.
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
//
/// 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

0 comments on commit e1ba4f8

Please sign in to comment.