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
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ impl Ssa {

let entry_block_id = callee.entry_block();
let entry_block = &callee.dfg[entry_block_id];
let instructions = entry_block.instructions();

// Only inline functions with a single block
if entry_block.successors().next().is_some() {
return false;
}

// Only inline functions with 0 or 1 instructions
if entry_block.instructions().len() > 1 {
if instructions.len() > 1 {
return false;
}

let instructions = callee.dfg[entry_block_id].instructions();
Comment thread
michaeljklein marked this conversation as resolved.
// Inline zero instructions
if instructions.is_empty() {
return true;
}
Expand All @@ -45,7 +46,7 @@ impl Ssa {
// This special check is done here to avoid performing the entire inline info computation.
// The inline info computation contains extra logic and requires passing over every function.
// which we can avoid in when inlining simple functions.
let only_instruction = callee.dfg[entry_block_id].instructions()[0];
let only_instruction = instructions[0];
if let Instruction::Call { func, .. } = callee.dfg[only_instruction] {
let Value::Function(func_id) = callee.dfg[func] else {
return true;
Expand All @@ -72,6 +73,12 @@ mod test {
ssa::{Ssa, opt::assert_normalized_ssa_equals},
};

fn assert_does_not_inline(src: &str) {
let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.inline_functions_with_at_most_one_instruction();
assert_normalized_ssa_equals(ssa, src);
}

#[test]
fn inline_functions_with_zero_instructions() {
let src = "
Expand Down Expand Up @@ -104,6 +111,70 @@ mod test {
");
}

/// This test is here to make clear that this SSA pass does not attempt multiple passes.
#[test]
fn does_not_inline_functions_that_require_multiple_passes() {
let src = "
acir(inline) fn main f0 {
b0(v0: Field):
v1 = call f2(v0) -> Field
return v1
}

acir(inline) fn foo f1 {
b0(v0: Field):
return v0
}

acir(inline) fn bar f2 {
b0(v0: Field):
v1 = call f1(v0) -> Field
v2 = call f1(v0) -> Field
v3 = add v1, v2
return v3
}
";
let ssa = Ssa::from_str(src).unwrap();

// In the first pass it won't recognize that `main` could be simplified.
let mut ssa = ssa.inline_functions_with_at_most_one_instruction();
assert_ssa_snapshot!(&mut ssa, @r"
acir(inline) fn main f0 {
b0(v0: Field):
v2 = call f2(v0) -> Field
return v2
}
acir(inline) fn foo f1 {
b0(v0: Field):
return v0
}
acir(inline) fn bar f2 {
b0(v0: Field):
v1 = add v0, v0
return v1
}
");

// After `bar` has been simplified, it does `main` as well.
ssa = ssa.inline_functions_with_at_most_one_instruction();
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn main f0 {
b0(v0: Field):
v1 = add v0, v0
return v1
}
acir(inline) fn foo f1 {
b0(v0: Field):
return v0
}
acir(inline) fn bar f2 {
b0(v0: Field):
v1 = add v0, v0
return v1
}
");
}

#[test]
fn inline_functions_with_one_instruction() {
let src = "
Expand Down Expand Up @@ -155,9 +226,73 @@ mod test {
return v1
}
";
let ssa = Ssa::from_str(src).unwrap();
assert_does_not_inline(src);
}

let ssa = ssa.inline_functions_with_at_most_one_instruction();
assert_normalized_ssa_equals(ssa, src);
#[test]
fn does_not_inline_functions_with_no_predicates() {
let src = "
acir(inline) fn main f0 {
b0(v0: Field):
v2 = call f1(v0) -> Field
v3 = call f1(v0) -> Field
v4 = add v2, v3
return v4
}

acir(no_predicates) fn foo f1 {
b0(v0: Field):
v2 = add v0, Field 1
return v2
}
";
assert_does_not_inline(src);
}

#[test]
fn does_not_inline_function_with_multiple_instructions() {
let src = "
acir(inline) fn main f0 {
b0(v0: Field):
v1 = call f1(v0) -> Field
return v1
}

acir(inline) fn foo f1 {
b0(v0: Field):
v1 = add v0, Field 1
v2 = mul v1, Field 2
return v2
}
";
assert_does_not_inline(src);
}

#[test]
fn does_not_inline_function_with_multiple_blocks() {
let src = "
acir(inline) fn main f0 {
b0(v0: Field, v1: bool):
v2 = call f1(v0, v1) -> Field
return v2
}

acir(inline) fn foo f1 {
b0(v0: Field, v1: bool):
jmpif v1 then: b1, else: b2

b1():
v3 = add v0, Field 1
jmp b3(v3)

b2():
v4 = mul v0, Field 2
jmp b3(v4)

b3(v5: Field):
return v5
}
";
assert_does_not_inline(src);
}
}
24 changes: 24 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,33 @@ pub(crate) fn assert_normalized_ssa_equals(mut ssa: super::Ssa, expected: &str)
similar_asserts::assert_eq!(expected_ssa, ssa);
}

/// Compare the textural representation of the SSA after normalizing its IDs to a snapshot.
///
/// # Example:
///
/// ```ignore
/// let ssa = todo!();
/// assert_ssa_snapshot!(ssa, @r"
/// acir(inline) fn main f0 {
/// b0(v0: Field):
/// return v0
/// }
/// ");
/// ```
/// Or without taking ownership:
/// ```ignore
/// let mut ssa = todo!();
/// assert_ssa_snapshot!(&mut ssa, @r"
/// acir(inline) fn main f0 {
/// b0(v0: Field):
/// return v0
/// }
/// ");
/// ```
#[macro_export]
macro_rules! assert_ssa_snapshot {
($ssa:expr, $($arg:tt)*) => {
#[allow(unused_mut)]
let mut mut_ssa = $ssa;
mut_ssa.normalize_ids();
insta::assert_snapshot!(mut_ssa, $($arg)*)
Expand Down
Loading