diff --git a/compiler/noirc_evaluator/src/ssa/opt/inline_functions_with_at_most_one_instruction.rs b/compiler/noirc_evaluator/src/ssa/opt/inline_functions_with_at_most_one_instruction.rs index 9b96c10c85b..6d88b6d1a7c 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inline_functions_with_at_most_one_instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inline_functions_with_at_most_one_instruction.rs @@ -25,6 +25,7 @@ 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() { @@ -32,11 +33,11 @@ impl Ssa { } // 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(); + // Inline zero instructions if instructions.is_empty() { return true; } @@ -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; @@ -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 = " @@ -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 = " @@ -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); } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 56f39211c09..f1f92e408d2 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -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)*)