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): Handle codegen for literals #1209

Merged
merged 13 commits into from
Apr 24, 2023
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,4 +1,5 @@
pub(crate) mod basic_block;
pub(crate) mod constant;
pub(crate) mod dfg;
pub(crate) mod function;
pub(crate) mod instruction;
Expand Down
56 changes: 56 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs
Original file line number Diff line number Diff line change
@@ -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<NumericConstant> without it getting confused
/// with a possible future use of Id<FieldElement>.
#[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<NumericConstant>;

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)
}
}
48 changes: 36 additions & 12 deletions crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -55,6 +59,11 @@ pub(crate) struct DataFlowGraph {
/// function.
values: DenseMap<Value>,

/// 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<NumericConstant>,

/// Function signatures of external methods
signatures: DenseMap<Signature>,

Expand Down Expand Up @@ -91,41 +100,50 @@ 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
}

jfecher marked this conversation as resolved.
Show resolved Hide resolved
/// 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)
}

/// Attaches results to the instruction.
/// 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 })
}

/// 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();

// 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.
Expand Down Expand Up @@ -181,6 +199,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)]
Expand All @@ -190,19 +216,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);
}
}
39 changes: 23 additions & 16 deletions crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use acvm::FieldElement;

use super::{
basic_block::BasicBlockId, function::FunctionId, map::Id, types::Type, value::ValueId,
};
Expand Down Expand Up @@ -38,18 +36,24 @@ pub(crate) enum Instruction {

/// Performs a function call with a list of its arguments.
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> },

/// 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 },
kevaundray marked this conversation as resolved.
Show resolved Hide resolved

/// 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 },
jfecher marked this conversation as resolved.
Show resolved Hide resolved
Store { address: ValueId, value: ValueId },
}

impl Instruction {
Expand All @@ -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,
}
}

Expand All @@ -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![],
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/map.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
collections::HashMap,
hash::Hash,
sync::atomic::{AtomicUsize, Ordering},
};

Expand Down Expand Up @@ -186,6 +187,53 @@ impl<T> std::ops::IndexMut<Id<T>> for SparseMap<T> {
}
}

/// 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<T> {
key_to_value: HashMap<Id<T>, T>,
value_to_key: HashMap<T, Id<T>>,
}

impl<T: Clone + Hash + Eq> TwoWayMap<T> {
/// 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<T> {
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<T> Default for TwoWayMap<T> {
fn default() -> Self {
Self { key_to_value: HashMap::new(), value_to_key: HashMap::new() }
}
}

// Note that there is no impl for IndexMut<Id<T>>,
// if we allowed mutable access to map elements they may be
// mutated such that elements are no longer unique
impl<T> std::ops::Index<Id<T>> for TwoWayMap<T> {
type Output = T;

fn index(&self, id: Id<T>) -> &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
Expand Down
5 changes: 4 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/value.rs
Original file line number Diff line number Diff line change
@@ -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<Value>;

Expand All @@ -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 },
}
Loading