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

feat(ssa): Immediately simplify away RefCount instructions in ACIR functions #6893

Merged
merged 17 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 13 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 compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,8 @@ impl<'a> Context<'a> {
unreachable!("Expected all load instructions to be removed before acir_gen")
}
Instruction::IncrementRc { .. } | Instruction::DecrementRc { .. } => {
// Do nothing. Only Brillig needs to worry about reference counted arrays
// Only Brillig needs to worry about reference counted arrays
unreachable!("Expected all Rc instructions to be removed before acir_gen")
}
Instruction::RangeCheck { value, max_bit_size, assert_message } => {
let acir_var = self.convert_numeric_value(*value, dfg)?;
Expand Down
58 changes: 53 additions & 5 deletions compiler/noirc_evaluator/src/ssa/ir/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ssa::{function_builder::data_bus::DataBus, ir::instruction::SimplifyR
use super::{
basic_block::{BasicBlock, BasicBlockId},
call_stack::{CallStack, CallStackHelper, CallStackId},
function::FunctionId,
function::{FunctionId, RuntimeType},
instruction::{
Instruction, InstructionId, InstructionResultType, Intrinsic, TerminatorInstruction,
},
Expand All @@ -26,8 +26,13 @@ use serde_with::DisplayFromStr;
/// owning most data in a function and handing out Ids to this data that can be
/// shared without worrying about ownership.
#[serde_as]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct DataFlowGraph {
/// Runtime of the [Function] that owns this [DataFlowGraph].
/// This might change during the `runtime_separation` pass where
/// ACIR functions are cloned as Brillig functions.
runtime: RuntimeType,

/// All of the instructions in a function
instructions: DenseMap<Instruction>,

Expand Down Expand Up @@ -100,6 +105,34 @@ pub(crate) struct DataFlowGraph {
}

impl DataFlowGraph {
pub(crate) fn new(runtime: RuntimeType) -> Self {
Self {
runtime,
instructions: Default::default(),
results: Default::default(),
values: Default::default(),
constants: Default::default(),
functions: Default::default(),
intrinsics: Default::default(),
foreign_functions: Default::default(),
blocks: Default::default(),
replaced_value_ids: Default::default(),
locations: Default::default(),
call_stack_data: Default::default(),
data_bus: Default::default(),
aakoshh marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Runtime type of the function.
pub(crate) fn runtime(&self) -> RuntimeType {
self.runtime
}

/// Set runtime type of the function.
pub(crate) fn set_runtime(&mut self, runtime: RuntimeType) {
self.runtime = runtime;
}

/// Creates a new basic block with no parameters.
/// After being created, the block is unreachable in the current function
/// until another block is made to jump to it.
Expand Down Expand Up @@ -164,6 +197,11 @@ impl DataFlowGraph {
id
}

/// Check if the function runtime would simply ignore this instruction.
pub(crate) fn is_handled_by_runtime(&self, instruction: &Instruction) -> bool {
!(self.runtime().is_acir() && instruction.is_brillig_only())
}

fn insert_instruction_without_simplification(
&mut self,
instruction_data: Instruction,
Expand All @@ -184,6 +222,10 @@ impl DataFlowGraph {
ctrl_typevars: Option<Vec<Type>>,
call_stack: CallStackId,
) -> InsertInstructionResult {
if !self.is_handled_by_runtime(&instruction_data) {
return InsertInstructionResult::InstructionRemoved;
}

let id = self.insert_instruction_without_simplification(
instruction_data,
block,
Expand All @@ -194,14 +236,18 @@ impl DataFlowGraph {
InsertInstructionResult::Results(id, self.instruction_results(id))
}

/// Inserts a new instruction at the end of the given block and returns its results
/// Simplifies a new instruction and inserts it at the end of the given block and returns its results.
/// If the instruction is not handled by the current runtime, `InstructionRemoved` is returned.
pub(crate) fn insert_instruction_and_results(
&mut self,
instruction: Instruction,
block: BasicBlockId,
ctrl_typevars: Option<Vec<Type>>,
call_stack: CallStackId,
) -> InsertInstructionResult {
if !self.is_handled_by_runtime(&instruction) {
return InsertInstructionResult::InstructionRemoved;
}
jfecher marked this conversation as resolved.
Show resolved Hide resolved
match instruction.simplify(self, block, ctrl_typevars.clone(), call_stack) {
SimplifyResult::SimplifiedTo(simplification) => {
InsertInstructionResult::SimplifiedTo(simplification)
Expand Down Expand Up @@ -679,12 +725,14 @@ impl<'dfg> std::ops::Index<usize> for InsertInstructionResult<'dfg> {

#[cfg(test)]
mod tests {
use noirc_frontend::monomorphization::ast::InlineType;

use super::DataFlowGraph;
use crate::ssa::ir::{instruction::Instruction, types::Type};
use crate::ssa::ir::{dfg::RuntimeType, instruction::Instruction, types::Type};

#[test]
fn make_instruction() {
let mut dfg = DataFlowGraph::default();
let mut dfg = DataFlowGraph::new(RuntimeType::Acir(InlineType::Inline));
let ins = Instruction::Allocate;
let ins_id = dfg.make_instruction(ins, Some(vec![Type::field()]));

Expand Down
14 changes: 6 additions & 8 deletions compiler/noirc_evaluator/src/ssa/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ pub(crate) struct Function {

id: FunctionId,

runtime: RuntimeType,

/// The DataFlowGraph holds the majority of data pertaining to the function
/// including its blocks, instructions, and values.
pub(crate) dfg: DataFlowGraph,
Expand All @@ -84,22 +82,22 @@ impl Function {
///
/// Note that any parameters or attributes of the function must be manually added later.
pub(crate) fn new(name: String, id: FunctionId) -> Self {
let mut dfg = DataFlowGraph::default();
let mut dfg = DataFlowGraph::new(RuntimeType::Acir(InlineType::default()));
let entry_block = dfg.make_block();
Self { name, id, entry_block, dfg, runtime: RuntimeType::Acir(InlineType::default()) }
Self { name, id, entry_block, dfg }
}

/// Creates a new function as a clone of the one passed in with the passed in id.
pub(crate) fn clone_with_id(id: FunctionId, another: &Function) -> Self {
let dfg = another.dfg.clone();
let entry_block = another.entry_block;
Self { name: another.name.clone(), id, entry_block, dfg, runtime: another.runtime }
Self { name: another.name.clone(), id, entry_block, dfg }
}

/// Takes the signature (function name & runtime) from a function but does not copy the body.
pub(crate) fn clone_signature(id: FunctionId, another: &Function) -> Self {
let mut new_function = Function::new(another.name.clone(), id);
new_function.runtime = another.runtime;
new_function.set_runtime(another.runtime());
new_function
}

Expand All @@ -116,12 +114,12 @@ impl Function {

/// Runtime type of the function.
pub(crate) fn runtime(&self) -> RuntimeType {
self.runtime
self.dfg.runtime()
}

/// Set runtime type of the function.
pub(crate) fn set_runtime(&mut self, runtime: RuntimeType) {
self.runtime = runtime;
self.dfg.set_runtime(runtime);
}

pub(crate) fn is_no_predicates(&self) -> bool {
Expand Down
6 changes: 6 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,12 @@ impl Instruction {
Instruction::Noop => Remove,
}
}

/// Some instructions are only to be used in Brillig and should be eliminated
/// after runtime separation, never to be be reintroduced in an ACIR runtime.
pub(crate) fn is_brillig_only(&self) -> bool {
matches!(self, Instruction::IncrementRc { .. } | Instruction::DecrementRc { .. })
}
}

/// Given a chain of operations like:
Expand Down
8 changes: 4 additions & 4 deletions compiler/noirc_evaluator/src/ssa/ir/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ pub(crate) type ValueId = Id<Value>;
pub(crate) enum Value {
/// This value was created due to an instruction
///
/// instruction -- This is the instruction which defined it
/// typ -- This is the `Type` of the instruction
/// position -- Returns the position in the results
/// vector that this `Value` is located.
/// * `instruction`: This is the instruction which defined it
/// * `typ`: This is the `Type` of the instruction
/// * `position`: Returns the position in the results vector that this `Value` is located.
///
/// Example, if you add two numbers together, then the resulting
/// value would have position `0`, the typ would be the type
/// of the operands, and the instruction would map to an add instruction.
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ impl Translator {
let main_function = parsed_ssa.functions.remove(0);
let main_id = FunctionId::test_new(0);
let mut builder = FunctionBuilder::new(main_function.external_name.clone(), main_id);
builder.simplify = simplify;
builder.set_runtime(main_function.runtime_type);
builder.simplify = simplify;

// Map function names to their IDs so calls can be resolved
let mut function_id_counter = 1;
Expand Down
Loading