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 6 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
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
13 changes: 9 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 @@ -92,7 +97,7 @@ impl BasicBlock {
/// Removes the given instruction from this block if present or panics otherwise.
pub(crate) fn remove_instruction(&mut self, instruction: InstructionId) {
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
49 changes: 32 additions & 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 super::{
};

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 Down Expand Up @@ -69,22 +68,6 @@ impl DataFlowGraph {
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 @@ impl DataFlowGraph {
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 @@ -279,6 +263,37 @@ impl DataFlowGraph {
) {
self.blocks[block].set_terminator(terminator);
}

/// Splits the given block in two at the given instruction, returning the Id of the new block.
/// This will remove the given instruction and place every instruction after it into a new block
/// with the same terminator as the old block. The old block is modified to stop
/// before the instruction to remove and to unconditionally branch to the new block.
/// This function is useful during function inlining to remove the call instruction
/// while opening a spot at the end of the current block to insert instructions into.
///
/// Example (before):
/// block1: a; b; c; d; e; jmp block5
///
/// After self.split_block_at(block1, c):
/// block1: a; b; jmp block2
/// block2: d; e; jmp block5
pub(crate) fn split_block_at(
&mut self,
block: BasicBlockId,
instruction_to_remove: InstructionId,
) -> BasicBlockId {
let split_block = &mut self.blocks[block];

let mut instructions = split_block.instructions().iter();
let index = instructions.position(|id| *id == instruction_to_remove).unwrap_or_else(|| {
panic!("No instruction found with id {instruction_to_remove:?} in block {block:?}")
});

let instructions = split_block.instructions_mut().drain(index..).collect();
split_block.remove_instruction(instruction_to_remove);

self.blocks.insert(BasicBlock::new(instructions))
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved
}

impl std::ops::Index<InstructionId> for DataFlowGraph {
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
Loading