diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index c2b02b709a4..5f8be903952 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -69,6 +69,10 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub show_ssa_pass: Vec, + /// Do not print an SSA pass if it didn't produce changes. + #[arg(long, hide = true)] + pub hide_unchanged_ssa: bool, + /// Emit source file locations when emitting debug information for the SSA IR to stdout. /// By default, source file locations won't be shown. #[arg(long, hide = true)] @@ -232,6 +236,7 @@ impl Default for CompileOptions { force_compile: false, show_ssa: false, show_ssa_pass: Vec::new(), + hide_unchanged_ssa: false, with_ssa_locations: false, show_contract_fn: None, skip_ssa_pass: Vec::new(), @@ -296,6 +301,7 @@ impl CompileOptions { max_bytecode_increase_percent: self.max_bytecode_increase_percent, force_unroll_threshold: self.force_unroll_threshold, skip_passes: self.skip_ssa_pass.clone(), + ssa_logging_hide_unchanged: self.hide_unchanged_ssa, } } } diff --git a/compiler/noirc_evaluator/src/ssa/builder.rs b/compiler/noirc_evaluator/src/ssa/builder.rs index b8d77a393a6..713f1347119 100644 --- a/compiler/noirc_evaluator/src/ssa/builder.rs +++ b/compiler/noirc_evaluator/src/ssa/builder.rs @@ -77,6 +77,11 @@ pub struct SsaBuilder<'local> { ssa: Ssa, /// Options to control which SSA passes to print. ssa_logging: SsaLogging, + /// Whether to skip printing an SSA pass if it didn't produce any changes. + ssa_logging_hide_unchanged: bool, + /// Records the last SSA printed. This is used to avoid printing the result of an + /// SSA pass if it didn't produce any changes compared to the previous SSA. + last_ssa_printed: Option, /// Whether to print the amount of time it took to run individual SSA passes. print_codegen_timings: bool, /// Counters indexed by the message in the SSA pass, so we can distinguish between multiple @@ -94,6 +99,7 @@ impl<'local> SsaBuilder<'local> { pub fn from_program( program: Program, ssa_logging: SsaLogging, + ssa_logging_hide_unchanged: bool, print_codegen_timings: bool, emit_ssa: &Option, files: Option<&'local fm::FileManager>, @@ -108,17 +114,27 @@ impl<'local> SsaBuilder<'local> { let ssa_path = emit_ssa.with_extension("ssa.json"); write_to_file(&serde_json::to_vec(&ssa).unwrap(), &ssa_path); } - Ok(Self::from_ssa(ssa, ssa_logging, print_codegen_timings, files).print("Initial SSA")) + Ok(Self::from_ssa( + ssa, + ssa_logging, + ssa_logging_hide_unchanged, + print_codegen_timings, + files, + ) + .print("Initial SSA")) } pub fn from_ssa( ssa: Ssa, ssa_logging: SsaLogging, + ssa_logging_hide_unchanged: bool, print_codegen_timings: bool, files: Option<&'local fm::FileManager>, ) -> Self { Self { ssa_logging, + ssa_logging_hide_unchanged, + last_ssa_printed: None, print_codegen_timings, ssa, files, @@ -193,7 +209,20 @@ impl<'local> SsaBuilder<'local> { } if print_ssa_pass { - println_to_stdout!("After {msg}:\n{}", self.ssa.print_with(self.files)); + let printed_ssa = format!("{}", self.ssa.print_with(self.files)); + let skip_print = self.ssa_logging_hide_unchanged + && self + .last_ssa_printed + .as_ref() + .is_some_and(|last_ssa_printed| last_ssa_printed == &printed_ssa); + + if !skip_print { + println_to_stdout!("After {msg}:\n{printed_ssa}"); + } + + if self.ssa_logging_hide_unchanged { + self.last_ssa_printed = Some(printed_ssa); + } } self } diff --git a/compiler/noirc_evaluator/src/ssa/mod.rs b/compiler/noirc_evaluator/src/ssa/mod.rs index b8ef4e459b1..ec5631b1fc6 100644 --- a/compiler/noirc_evaluator/src/ssa/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/mod.rs @@ -80,6 +80,9 @@ pub struct SsaEvaluatorOptions { /// Emit debug information for the intermediate SSA IR pub ssa_logging: SsaLogging, + /// Whether to skip printing an SSA pass if it didn't produce any changes. + pub ssa_logging_hide_unchanged: bool, + /// Options affecting Brillig code generation. pub brillig_options: BrilligOptions, @@ -376,6 +379,7 @@ pub fn optimize_into_acir( let builder = SsaBuilder::from_program( program, options.ssa_logging.clone(), + options.ssa_logging_hide_unchanged, options.print_codegen_timings, &options.emit_ssa, files, diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs index e61e0fc48da..2ca1348f93a 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs @@ -654,6 +654,7 @@ mod tests { max_bytecode_increase_percent: None, force_unroll_threshold: FORCE_UNROLL_THRESHOLD, skip_passes: Vec::new(), + ssa_logging_hide_unchanged: false, }; let pipeline = primary_passes(&options); for pass in pipeline { diff --git a/compiler/noirc_evaluator/src/ssa/opt/hint.rs b/compiler/noirc_evaluator/src/ssa/opt/hint.rs index 456a66a544b..99e7ff241da 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/hint.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/hint.rs @@ -29,9 +29,16 @@ mod tests { max_bytecode_increase_percent: None, force_unroll_threshold: FORCE_UNROLL_THRESHOLD, skip_passes: Default::default(), + ssa_logging_hide_unchanged: false, }; - let builder = SsaBuilder::from_ssa(ssa, options.ssa_logging.clone(), false, None); + let builder = SsaBuilder::from_ssa( + ssa, + options.ssa_logging.clone(), + options.ssa_logging_hide_unchanged, + false, + None, + ); Ok(builder.run_passes(&primary_passes(options))?.finish()) } diff --git a/tooling/ast_fuzzer/fuzz/src/lib.rs b/tooling/ast_fuzzer/fuzz/src/lib.rs index c039e1835f5..46098024d4f 100644 --- a/tooling/ast_fuzzer/fuzz/src/lib.rs +++ b/tooling/ast_fuzzer/fuzz/src/lib.rs @@ -41,6 +41,7 @@ pub fn default_ssa_options() -> SsaEvaluatorOptions { max_bytecode_increase_percent: None, force_unroll_threshold: FORCE_UNROLL_THRESHOLD, skip_passes: Default::default(), + ssa_logging_hide_unchanged: false, } } diff --git a/tooling/ast_fuzzer/tests/parser.rs b/tooling/ast_fuzzer/tests/parser.rs index 1afa666a96a..7110fbbed17 100644 --- a/tooling/ast_fuzzer/tests/parser.rs +++ b/tooling/ast_fuzzer/tests/parser.rs @@ -46,6 +46,7 @@ fn arb_ssa_roundtrip() { max_bytecode_increase_percent: None, force_unroll_threshold: FORCE_UNROLL_THRESHOLD, skip_passes: Default::default(), + ssa_logging_hide_unchanged: false, }; let pipeline = primary_passes(&options); let last_pass = u.choose_index(pipeline.len())?; diff --git a/tooling/ast_fuzzer/tests/smoke.rs b/tooling/ast_fuzzer/tests/smoke.rs index 7158df9bfe7..caba61a2268 100644 --- a/tooling/ast_fuzzer/tests/smoke.rs +++ b/tooling/ast_fuzzer/tests/smoke.rs @@ -51,6 +51,7 @@ fn arb_program_can_be_executed() { max_bytecode_increase_percent: None, force_unroll_threshold: FORCE_UNROLL_THRESHOLD, skip_passes: Default::default(), + ssa_logging_hide_unchanged: false, }; // Print the AST if something goes wrong, then panic. diff --git a/tooling/nargo_cli/src/cli/interpret_cmd.rs b/tooling/nargo_cli/src/cli/interpret_cmd.rs index ea543459542..5764e51d5eb 100644 --- a/tooling/nargo_cli/src/cli/interpret_cmd.rs +++ b/tooling/nargo_cli/src/cli/interpret_cmd.rs @@ -140,6 +140,7 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl }; let file_manager = if args.compile_options.with_ssa_locations { Some(&file_manager) } else { None }; + let mut last_ssa_printed: Option = None; is_ok &= print_and_interpret_ssa( ssa_options, @@ -150,6 +151,7 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl &ssa_return, interpreter_options, file_manager, + &mut last_ssa_printed, )?; // Run SSA passes in the pipeline and interpret the ones we are interested in. @@ -173,6 +175,7 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl &ssa_return, interpreter_options, file_manager, + &mut last_ssa_printed, )?; } } @@ -238,15 +241,41 @@ fn msg_matches(patterns: &[String], msg: &str) -> bool { patterns.iter().any(|p| msg.contains(&p.to_lowercase())) } -fn print_ssa(options: &SsaEvaluatorOptions, ssa: &mut Ssa, msg: &str, fm: Option<&FileManager>) { - let print = match options.ssa_logging { - SsaLogging::All => true, - SsaLogging::None => false, - SsaLogging::Contains(ref ps) => msg_matches(ps, msg), - }; - if print { +/// Prints the SSA (if it needs to be printed) and returns whether the SSA needs +/// to be interpreted afterwards (it doesn't need to if the SSA passes are being +/// printed and the user asked to skip SSA passes that don't produce changes). +fn print_ssa( + options: &SsaEvaluatorOptions, + ssa: &mut Ssa, + msg: &str, + files: Option<&FileManager>, + last_ssa_printed: &mut Option, +) -> bool { + let print_ssa_pass = options.ssa_logging.matches(msg); + + // Always normalize if we are going to print at least one of the passes + if !matches!(options.ssa_logging, SsaLogging::None) { ssa.normalize_ids(); - println!("After {msg}:\n{}", ssa.print_with(fm)); + } + + if print_ssa_pass { + let printed_ssa = format!("{}", ssa.print_with(files)); + let skip_print = options.ssa_logging_hide_unchanged + && last_ssa_printed + .as_ref() + .is_some_and(|last_ssa_printed| last_ssa_printed == &printed_ssa); + + if !skip_print { + println!("After {msg}:\n{printed_ssa}"); + } + + if options.ssa_logging_hide_unchanged { + *last_ssa_printed = Some(printed_ssa); + } + + !skip_print + } else { + true } } @@ -305,9 +334,14 @@ fn print_and_interpret_ssa( return_value: &Option>, interpreter_options: InterpreterOptions, fm: Option<&FileManager>, + last_ssa_printed: &mut Option, ) -> Result { - print_ssa(options, ssa, msg, fm); - interpret_ssa(passes_to_interpret, ssa, msg, args, return_value, interpreter_options) + let must_interpret = print_ssa(options, ssa, msg, fm, last_ssa_printed); + if must_interpret { + interpret_ssa(passes_to_interpret, ssa, msg, args, return_value, interpreter_options) + } else { + Ok(true) + } } fn flatten_databus_values(values: Vec) -> Vec { diff --git a/tooling/ssa_executor/src/compiler.rs b/tooling/ssa_executor/src/compiler.rs index 1cae902f825..5de259a3dc6 100644 --- a/tooling/ssa_executor/src/compiler.rs +++ b/tooling/ssa_executor/src/compiler.rs @@ -43,9 +43,11 @@ pub fn optimize_ssa_into_acir( })); let result = std::panic::catch_unwind(AssertUnwindSafe(|| { validate_ssa(&ssa); + let builder = SsaBuilder::from_ssa( ssa, options.ssa_logging.clone(), + options.ssa_logging_hide_unchanged, options.print_codegen_timings, None, ); diff --git a/tooling/ssa_verification/src/acir_instruction_builder.rs b/tooling/ssa_verification/src/acir_instruction_builder.rs index 94c5b52dd8f..87f6f4f3467 100644 --- a/tooling/ssa_verification/src/acir_instruction_builder.rs +++ b/tooling/ssa_verification/src/acir_instruction_builder.rs @@ -235,7 +235,16 @@ impl InstructionArtifacts { /// Converts SSA to ACIR program fn ssa_to_acir_program(ssa: Ssa) -> AcirProgram { // third brillig names, fourth errors - let builder = SsaBuilder::from_ssa(ssa, SsaLogging::None, false, None); + let ssa_logging_hide_unchanged = false; + let print_codegen_timings = false; + let files = None; + let builder = SsaBuilder::from_ssa( + ssa, + SsaLogging::None, + ssa_logging_hide_unchanged, + print_codegen_timings, + files, + ); let ssa_evaluator_options = SsaEvaluatorOptions { ssa_logging: SsaLogging::None, print_codegen_timings: false, @@ -250,6 +259,7 @@ fn ssa_to_acir_program(ssa: Ssa) -> AcirProgram { brillig_options: BrilligOptions::default(), enable_brillig_constraints_check_lookback: false, skip_passes: vec![], + ssa_logging_hide_unchanged: false, }; let (acir_functions, brillig, _) = match optimize_ssa_builder_into_acir( builder,