Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion compiler/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub enum RuntimeError {
range: String,
call_stack: CallStack,
},
#[error(
"Attempted to recurse more than {limit} times during inlining function '{function_name}'"
)]
RecursionLimit { limit: u32, function_name: String, call_stack: CallStack },
#[error("Expected array index to fit into a u64")]
TypeConversion { from: String, into: String, call_stack: CallStack },
#[error(
Expand Down Expand Up @@ -201,7 +205,8 @@ impl RuntimeError {
| RuntimeError::ReturnedFunctionFromDynamicIf { call_stack }
| RuntimeError::BreakOrContinue { call_stack }
| RuntimeError::DynamicIndexingWithReference { call_stack }
| RuntimeError::UnknownReference { call_stack } => call_stack,
| RuntimeError::UnknownReference { call_stack }
| RuntimeError::RecursionLimit { call_stack, .. } => call_stack,
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions compiler/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,14 @@ pub fn primary_passes(options: &SsaEvaluatorOptions) -> Vec<SsaPass> {
// https://github.com/AztecProtocol/aztec-packages/pull/11294#issuecomment-2622809518
//SsaPass::new(Ssa::mem2reg, "Mem2Reg (1st)"),
SsaPass::new(Ssa::remove_paired_rc, "Removing Paired rc_inc & rc_decs"),
SsaPass::new(
SsaPass::new_try(
move |ssa| ssa.preprocess_functions(options.inliner_aggressiveness),
"Preprocessing Functions",
),
SsaPass::new(move |ssa| ssa.inline_functions(options.inliner_aggressiveness), "Inlining"),
SsaPass::new_try(
move |ssa| ssa.inline_functions(options.inliner_aggressiveness),
"Inlining",
),
// Run mem2reg with the CFG separated into blocks
SsaPass::new(Ssa::mem2reg, "Mem2Reg"),
SsaPass::new(Ssa::simplify_cfg, "Simplifying"),
Expand All @@ -202,7 +205,7 @@ pub fn primary_passes(options: &SsaEvaluatorOptions) -> Vec<SsaPass> {
// Before flattening is run, we treat functions marked with the `InlineType::NoPredicates` as an entry point.
// This pass must come immediately following `mem2reg` as the succeeding passes
// may create an SSA which inlining fails to handle.
SsaPass::new(
SsaPass::new_try(
move |ssa| ssa.inline_functions_with_no_predicates(options.inliner_aggressiveness),
"Inlining",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ impl Ssa {
};

self.functions = btree_map(&self.functions, |(id, function)| {
(*id, function.inlined(&self, &should_inline_call))
(
*id,
function
.inlined(&self, &should_inline_call)
.expect("simple function should not be recursive"),
)
});

self
Expand Down
88 changes: 49 additions & 39 deletions compiler/noirc_evaluator/src/ssa/opt/inlining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
//! be a single function remaining when the pass finishes.
use std::collections::{HashSet, VecDeque};

use crate::errors::RuntimeError;
use acvm::acir::AcirField;
use im::HashMap;
use iter_extended::{btree_map, vecmap};
use iter_extended::vecmap;
use noirc_errors::call_stack::CallStackId;

use crate::ssa::{
Expand Down Expand Up @@ -50,14 +51,17 @@ impl Ssa {
///
/// This step should run after runtime separation, since it relies on the runtime of the called functions being final.
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn inline_functions(self, aggressiveness: i64) -> Ssa {
pub(crate) fn inline_functions(self, aggressiveness: i64) -> Result<Ssa, RuntimeError> {
let call_graph = CallGraph::from_ssa_weighted(&self);
let inline_infos = compute_inline_infos(&self, &call_graph, false, aggressiveness);
Self::inline_functions_inner(self, &inline_infos, false)
}

/// Run the inlining pass where functions marked with `InlineType::NoPredicates` as not entry points
pub(crate) fn inline_functions_with_no_predicates(self, aggressiveness: i64) -> Ssa {
pub(crate) fn inline_functions_with_no_predicates(
self,
aggressiveness: i64,
) -> Result<Ssa, RuntimeError> {
let call_graph = CallGraph::from_ssa_weighted(&self);
let inline_infos = compute_inline_infos(&self, &call_graph, true, aggressiveness);
Self::inline_functions_inner(self, &inline_infos, true)
Expand All @@ -67,7 +71,7 @@ impl Ssa {
mut self,
inline_infos: &InlineInfos,
inline_no_predicates_functions: bool,
) -> Ssa {
) -> Result<Ssa, RuntimeError> {
let inline_targets = inline_infos.iter().filter_map(|(id, info)| {
let dfg = &self.functions[id].dfg;
info.is_inline_target(dfg).then_some(*id)
Expand All @@ -91,13 +95,15 @@ impl Ssa {

// NOTE: Functions are processed independently of each other, with the final mapping replacing the original,
// instead of inlining the "leaf" functions, moving up towards the entry point.
self.functions = btree_map(inline_targets, |entry_point| {
let mut new_functions = std::collections::BTreeMap::new();
for entry_point in inline_targets {
let function = &self.functions[&entry_point];
let new_function = function.inlined(&self, &should_inline_call);
(entry_point, new_function)
});
let inlined = function.inlined(&self, &should_inline_call)?;
new_functions.insert(entry_point, inlined);
}
self.functions = new_functions;

self
Ok(self)
}
}

Expand All @@ -107,7 +113,7 @@ impl Function {
&self,
ssa: &Ssa,
should_inline_call: &impl Fn(&Function) -> bool,
) -> Function {
) -> Result<Function, RuntimeError> {
InlineContext::new(ssa, self.id()).inline_all(ssa, &should_inline_call)
}
}
Expand Down Expand Up @@ -180,7 +186,7 @@ impl InlineContext {
mut self,
ssa: &Ssa,
should_inline_call: &impl Fn(&Function) -> bool,
) -> Function {
) -> Result<Function, RuntimeError> {
let entry_point = &ssa.functions[&self.entry_point];

let globals = &entry_point.dfg.globals;
Expand All @@ -204,7 +210,7 @@ impl InlineContext {
}

context.blocks.insert(context.source_function.entry_block(), entry_block);
context.inline_blocks(ssa, should_inline_call);
context.inline_blocks(ssa, should_inline_call)?;
// translate databus values
let databus = entry_point.dfg.data_bus.map_values(|t| context.translate_value(t));

Expand All @@ -213,7 +219,7 @@ impl InlineContext {
assert_eq!(new_ssa.functions.len(), 1);
let mut new_func = new_ssa.functions.pop_first().unwrap().1;
new_func.dfg.data_bus = databus;
new_func
Ok(new_func)
}

/// Inlines a function into the current function and returns the translated return values
Expand All @@ -224,17 +230,16 @@ impl InlineContext {
id: FunctionId,
arguments: &[ValueId],
should_inline_call: &impl Fn(&Function) -> bool,
) -> Vec<ValueId> {
) -> Result<Vec<ValueId>, RuntimeError> {
self.recursion_level += 1;

let source_function = &ssa.functions[&id];

if self.recursion_level > RECURSION_LIMIT {
panic!(
"Attempted to recur more than {RECURSION_LIMIT} times during inlining function '{}':\n{}",
source_function.name(),
source_function
);
return Err(RuntimeError::RecursionLimit {
function_name: source_function.name().to_string(),
limit: RECURSION_LIMIT,
call_stack: self.builder.current_function.dfg.get_call_stack(self.call_stack),
});
}

let entry_point = &ssa.functions[&self.entry_point];
Expand All @@ -248,9 +253,9 @@ impl InlineContext {
let current_block = context.context.builder.current_block();
context.blocks.insert(source_function.entry_block(), current_block);

let return_values = context.inline_blocks(ssa, should_inline_call);
let return_values = context.inline_blocks(ssa, should_inline_call)?;
self.recursion_level -= 1;
return_values
Ok(return_values)
}
}

Expand Down Expand Up @@ -392,7 +397,7 @@ impl<'function> PerFunctionContext<'function> {
&mut self,
ssa: &Ssa,
should_inline_call: &impl Fn(&Function) -> bool,
) -> Vec<ValueId> {
) -> Result<Vec<ValueId>, RuntimeError> {
let mut seen_blocks = HashSet::new();
let mut block_queue = VecDeque::new();
block_queue.push_back(self.source_function.entry_block());
Expand All @@ -409,7 +414,7 @@ impl<'function> PerFunctionContext<'function> {
self.context.builder.switch_to_block(translated_block_id);

seen_blocks.insert(source_block_id);
self.inline_block_instructions(ssa, source_block_id, should_inline_call);
self.inline_block_instructions(ssa, source_block_id, should_inline_call)?;

if let Some((block, values)) =
self.handle_terminator_instruction(source_block_id, &mut block_queue)
Expand All @@ -418,7 +423,7 @@ impl<'function> PerFunctionContext<'function> {
}
}

self.handle_function_returns(function_returns)
Ok(self.handle_function_returns(function_returns))
}

/// Handle inlining a function's possibly multiple return instructions.
Expand Down Expand Up @@ -458,7 +463,7 @@ impl<'function> PerFunctionContext<'function> {
ssa: &Ssa,
block_id: BasicBlockId,
should_inline_call: &impl Fn(&Function) -> bool,
) {
) -> Result<(), RuntimeError> {
let mut side_effects_enabled: Option<ValueId> = None;

let block = &self.source_function.dfg[block_id];
Expand All @@ -474,7 +479,7 @@ impl<'function> PerFunctionContext<'function> {
func_id,
arguments,
should_inline_call,
);
)?;

// This is only relevant during handling functions with `InlineType::NoPredicates` as these
// can pollute the function they're being inlined into with `Instruction::EnabledSideEffects`,
Expand Down Expand Up @@ -503,6 +508,7 @@ impl<'function> PerFunctionContext<'function> {
_ => self.push_instruction(*id),
}
}
Ok(())
}

fn should_inline_call<'a>(
Expand Down Expand Up @@ -545,7 +551,7 @@ impl<'function> PerFunctionContext<'function> {
function: FunctionId,
arguments: &[ValueId],
should_inline_call: &impl Fn(&Function) -> bool,
) {
) -> Result<(), RuntimeError> {
let old_results = self.source_function.dfg.instruction_results(call_id);
let arguments = vecmap(arguments, |arg| self.translate_value(*arg));

Expand All @@ -561,7 +567,7 @@ impl<'function> PerFunctionContext<'function> {

self.context.call_stack = new_call_stack;
let new_results =
self.context.inline_function(ssa, function, &arguments, should_inline_call);
self.context.inline_function(ssa, function, &arguments, should_inline_call)?;
self.context.call_stack = self
.context
.builder
Expand All @@ -572,6 +578,7 @@ impl<'function> PerFunctionContext<'function> {

let new_results = InsertInstructionResult::Results(call_id, &new_results);
Self::insert_new_instruction_results(&mut self.values, old_results, new_results);
Ok(())
}

/// Push the given instruction from the source_function into the current block of the
Expand Down Expand Up @@ -759,7 +766,7 @@ mod test {
}
";
let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.inline_functions(i64::MAX);
let ssa = ssa.inline_functions(i64::MAX).unwrap();
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn foo f0 {
b0():
Expand Down Expand Up @@ -798,7 +805,7 @@ mod test {
";
let ssa = Ssa::from_str(src).unwrap();

let ssa = ssa.inline_functions(i64::MAX);
let ssa = ssa.inline_functions(i64::MAX).unwrap();
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn main f0 {
b0(v0: Field):
Expand Down Expand Up @@ -834,7 +841,7 @@ mod test {
";
let ssa = Ssa::from_str(src).unwrap();

let ssa = ssa.inline_functions(i64::MAX);
let ssa = ssa.inline_functions(i64::MAX).unwrap();
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn main f0 {
b0():
Expand Down Expand Up @@ -892,7 +899,7 @@ mod test {
";
let ssa = Ssa::from_str(src).unwrap();

let ssa = ssa.inline_functions(i64::MAX);
let ssa = ssa.inline_functions(i64::MAX).unwrap();
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn main f0 {
b0(v0: u1):
Expand All @@ -909,10 +916,9 @@ mod test {
}

#[test]
#[should_panic(
expected = "Attempted to recur more than 1000 times during inlining function 'foo':\nacir(inline) fn foo f1 {"
)]
fn unconditional_recursion() {
// f1 is calling itself, which results in an infinite recursion
// it is expected that inlining this program returns an error.
let src = "
acir(inline) fn main f0 {
b0():
Expand All @@ -928,7 +934,11 @@ mod test {
let ssa = Ssa::from_str(src).unwrap();
assert_eq!(ssa.functions.len(), 2);

let _ = ssa.inline_functions(i64::MAX);
let ssa = ssa.inline_functions(i64::MAX);
let Err(err) = ssa else {
panic!("inline_functions cannot inline recursive functions");
};
insta::assert_snapshot!(err.to_string(), @"Attempted to recurse more than 1000 times during inlining function 'foo'");
}

#[test]
Expand All @@ -946,7 +956,7 @@ mod test {
}
";
let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.inline_functions(i64::MIN);
let ssa = ssa.inline_functions(i64::MIN).unwrap();
// No inlining has happened
assert_normalized_ssa_equals(ssa, src);
}
Expand Down Expand Up @@ -976,7 +986,7 @@ mod test {
";
let ssa = Ssa::from_str(src).unwrap();

let ssa = ssa.inline_functions(0);
let ssa = ssa.inline_functions(0).unwrap();
// No inlining has happened in f0
assert_ssa_snapshot!(ssa, @r"
brillig(inline) fn foo f0 {
Expand Down
10 changes: 5 additions & 5 deletions compiler/noirc_evaluator/src/ssa/opt/preprocess_fns.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Pre-process functions before inlining them into others.

use crate::ssa::{
Ssa,
RuntimeError, Ssa,
ir::{
call_graph::CallGraph,
function::{Function, RuntimeType},
Expand All @@ -12,7 +12,7 @@ use super::inlining::{self, InlineInfo};

impl Ssa {
/// Run pre-processing steps on functions in isolation.
pub(crate) fn preprocess_functions(mut self, aggressiveness: i64) -> Ssa {
pub(crate) fn preprocess_functions(mut self, aggressiveness: i64) -> Result<Ssa, RuntimeError> {
let call_graph = CallGraph::from_ssa_weighted(&self);
// Bottom-up order, starting with the "leaf" functions, so we inline already optimized code into the ones that call them.
let bottom_up = inlining::inline_info::compute_bottom_up_order(&self, &call_graph);
Expand Down Expand Up @@ -53,7 +53,7 @@ impl Ssa {
}

// Start with an inline pass.
let mut function = function.inlined(&self, &should_inline_call);
let mut function = function.inlined(&self, &should_inline_call)?;
// Help unrolling determine bounds.
function.as_slice_optimization();
// Prepare for unrolling
Expand All @@ -73,7 +73,7 @@ impl Ssa {
// Remove any functions that have been inlined into others already.
let ssa = self.remove_unreachable_functions();
// Remove leftover instructions.
ssa.dead_instruction_elimination_pre_flattening()
Ok(ssa.dead_instruction_elimination_pre_flattening())
}
}

Expand Down Expand Up @@ -111,7 +111,7 @@ mod tests {
"#;

let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.preprocess_functions(i64::MAX);
let ssa = ssa.preprocess_functions(i64::MAX).unwrap();

assert_ssa_snapshot!(ssa, @r#"
acir(inline) fn main f0 {
Expand Down
Loading
Loading