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): Implement function inlining #1293

Merged
merged 7 commits into from
May 9, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ use self::acir_gen::Acir;

mod acir_gen;
mod ir;
mod opt;
mod ssa_builder;
pub mod ssa_gen;

/// Optimize the given program by converting it into SSA
/// form and performing optimizations there. When finished,
/// convert the final SSA into ACIR and return it.
pub fn optimize_into_acir(program: Program) -> Acir {
ssa_gen::generate_ssa(program).into_acir()
ssa_gen::generate_ssa(program).inline_functions().into_acir()
}
/// Compiles the Program into ACIR and applies optimizations to the arithmetic gates
/// This is analogous to `ssa:create_circuit` and this method is called when one wants
Expand Down
15 changes: 11 additions & 4 deletions crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ pub(crate) struct BasicBlock {
pub(crate) type BasicBlockId = Id<BasicBlock>;

impl BasicBlock {
/// Create a new BasicBlock with the given parameters.
/// Create a new BasicBlock with the given instructions.
/// Parameters can also be added later via BasicBlock::add_parameter
pub(crate) fn new(parameters: Vec<ValueId>) -> Self {
Self { parameters, instructions: Vec::new(), terminator: None }
pub(crate) fn new(instructions: Vec<InstructionId>) -> Self {
Self { parameters: Vec::new(), instructions, terminator: None }
}

/// Returns the parameters of this block
Expand All @@ -57,6 +57,11 @@ impl BasicBlock {
&self.instructions
}

/// Retrieve a mutable reference to all instructions in this block.
pub(crate) fn instructions_mut(&mut self) -> &mut Vec<InstructionId> {
&mut self.instructions
}

/// Sets the terminator instruction of this block.
///
/// A properly-constructed block will always terminate with a TerminatorInstruction -
Expand Down Expand Up @@ -91,8 +96,10 @@ impl BasicBlock {

/// Removes the given instruction from this block if present or panics otherwise.
pub(crate) fn remove_instruction(&mut self, instruction: InstructionId) {
// Iterate in reverse here as an optimization since remove_instruction is most
// often called to remove instructions at the end of a block.
let index =
self.instructions.iter().position(|id| *id == instruction).unwrap_or_else(|| {
self.instructions.iter().rev().position(|id| *id == instruction).unwrap_or_else(|| {
jfecher marked this conversation as resolved.
Show resolved Hide resolved
panic!("remove_instruction: No such instruction {instruction:?} in block")
});
self.instructions.remove(index);
Expand Down
18 changes: 1 addition & 17 deletions crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
};

use acvm::FieldElement;
use iter_extended::vecmap;

/// The DataFlowGraph contains most of the actual data in a function including
/// its blocks, instructions, and values. This struct is largely responsible for
Expand All @@ -27,7 +26,7 @@
/// Stores the results for a particular instruction.
///
/// An instruction may return multiple values
/// and for this, we will also use the cranelift strategy

Check warning on line 29 in crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (cranelift)
/// to fetch them via indices.
///
/// Currently, we need to define them in a better way
Expand All @@ -45,7 +44,7 @@
constants: TwoWayMap<NumericConstant>,

/// Contains each function that has been imported into the current function.
/// Each function's Value::Function is uniqued here so any given FunctionId

Check warning on line 47 in crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (uniqued)
/// will always have the same ValueId within this function.
functions: HashMap<FunctionId, ValueId>,

Expand All @@ -69,22 +68,6 @@
self.blocks.insert(BasicBlock::new(Vec::new()))
}

/// Creates a new basic block with the given parameters.
/// After being created, the block is unreachable in the current function
/// until another block is made to jump to it.
pub(crate) fn make_block_with_parameters(
&mut self,
parameter_types: impl Iterator<Item = Type>,
) -> BasicBlockId {
self.blocks.insert_with_id(|entry_block| {
let parameters = vecmap(parameter_types.enumerate(), |(position, typ)| {
self.values.insert(Value::Param { block: entry_block, position, typ })
});

BasicBlock::new(parameters)
})
}

/// Get an iterator over references to each basic block within the dfg, paired with the basic
/// block's id.
///
Expand All @@ -95,6 +78,7 @@
self.blocks.iter()
}

/// Returns the parameters of the given block
pub(crate) fn block_parameters(&self, block: BasicBlockId) -> &[ValueId] {
self.blocks[block].parameters()
}
Expand Down Expand Up @@ -171,9 +155,9 @@

// Get all of the types that this instruction produces
// and append them as results.
let typs = self.instruction_result_types(instruction_id, ctrl_typevars);

Check warning on line 158 in crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typs)

for typ in typs {

Check warning on line 160 in crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs

View workflow job for this annotation

GitHub Actions / Spellcheck / Spellcheck

Unknown word (typs)
self.append_result(instruction_id, typ);
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ir/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ mod tests {
builder.terminate_with_return(vec![]);

let ssa = builder.finish();
let func = ssa.functions.first().unwrap();
let func = ssa.main();
let block0_id = func.entry_block();

let dt = DominatorTree::with_function(func);
Expand Down Expand Up @@ -383,7 +383,7 @@ mod tests {
builder.terminate_with_jmp(block1_id, vec![]);

let ssa = builder.finish();
let func = ssa.functions.first().unwrap();
let func = ssa.main();
let block0_id = func.entry_block();

let dt = DominatorTree::with_function(func);
Expand Down
9 changes: 8 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::basic_block::BasicBlockId;
use super::dfg::DataFlowGraph;
use super::map::Id;
use super::types::Type;
use super::value::ValueId;

/// A function holds a list of instructions.
/// These instructions are further grouped into Basic blocks
Expand All @@ -10,7 +11,7 @@ use super::types::Type;
/// To reference external functions its FunctionId can be used but this
/// cannot be checked for correctness until inlining is performed.
#[derive(Debug)]
pub struct Function {
pub(crate) struct Function {
/// The first basic block in the function
entry_block: BasicBlockId,

Expand Down Expand Up @@ -54,6 +55,12 @@ impl Function {
pub(crate) fn entry_block(&self) -> BasicBlockId {
self.entry_block
}

/// Returns the parameters of this function.
/// The parameters will always match that of this function's entry block.
pub(crate) fn parameters(&self) -> &[ValueId] {
self.dfg.block_parameters(self.entry_block)
}
}

/// FunctionId is a reference for a function
Expand Down
39 changes: 38 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use acvm::acir::BlackBoxFunc;
use iter_extended::vecmap;

use super::{basic_block::BasicBlockId, map::Id, types::Type, value::ValueId};

Expand Down Expand Up @@ -114,6 +115,42 @@ impl Instruction {
Instruction::Load { .. } | Instruction::Call { .. } => InstructionResultType::Unknown,
}
}

/// True if this instruction requires specifying the control type variables when
/// inserting this instruction into a DataFlowGraph.
pub(crate) fn requires_ctrl_typevars(&self) -> bool {
matches!(self.result_type(), InstructionResultType::Unknown)
}

/// Maps each ValueId inside this instruction to a new ValueId, returning the new instruction.
/// Note that the returned instruction is fresh and will not have an assigned InstructionId
/// until it is manually inserted in a DataFlowGraph later.
pub(crate) fn map_values(&self, mut f: impl FnMut(ValueId) -> ValueId) -> Instruction {
match self {
Instruction::Binary(binary) => Instruction::Binary(Binary {
lhs: f(binary.lhs),
rhs: f(binary.rhs),
operator: binary.operator,
}),
Instruction::Cast(value, typ) => Instruction::Cast(f(*value), *typ),
Instruction::Not(value) => Instruction::Not(f(*value)),
Instruction::Truncate { value, bit_size, max_bit_size } => Instruction::Truncate {
value: f(*value),
bit_size: *bit_size,
max_bit_size: *max_bit_size,
},
Instruction::Constrain(value) => Instruction::Constrain(f(*value)),
Instruction::Call { func, arguments } => Instruction::Call {
func: f(*func),
arguments: vecmap(arguments.iter().copied(), f),
},
Instruction::Allocate { size } => Instruction::Allocate { size: *size },
Instruction::Load { address } => Instruction::Load { address: f(*address) },
Instruction::Store { address, value } => {
Instruction::Store { address: f(*address), value: f(*value) }
}
}
}
}

/// The possible return values for Instruction::return_types
Expand Down Expand Up @@ -191,7 +228,7 @@ impl Binary {
/// All binary operators are also only for numeric types. To implement
/// e.g. equality for a compound type like a struct, one must add a
/// separate Eq operation for each field and combine them later with And.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub(crate) enum BinaryOp {
/// Addition of lhs + rhs.
Add,
Expand Down
18 changes: 18 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ impl<T> std::hash::Hash for Id<T> {
}
}

impl<T> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.index.partial_cmp(&other.index)
}
}

impl<T> Ord for Id<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.index.cmp(&other.index)
}
}

impl<T> Eq for Id<T> {}

impl<T> PartialEq for Id<T> {
Expand Down Expand Up @@ -272,6 +284,12 @@ pub(crate) struct AtomicCounter<T> {
}

impl<T> AtomicCounter<T> {
/// Create a new counter starting after the given Id.
/// Use AtomicCounter::default() to start at zero.
pub(crate) fn starting_after(id: Id<T>) -> Self {
Self { next: AtomicUsize::new(id.index + 1), _marker: Default::default() }
}

/// Return the next fresh id
pub(crate) fn next(&self) -> Id<T> {
Id::new(self.next.fetch_add(1, Ordering::Relaxed))
Expand Down