From 541d88fbdaa88562bac3254df5e6b1512dceb253 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 10 Jun 2025 14:06:39 -0300 Subject: [PATCH] feat: allow printing each SSA interpreter definition --- .../src/ssa/interpreter/mod.rs | 34 ++++++++++++++--- tooling/nargo_cli/src/cli/interpret_cmd.rs | 38 ++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs b/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs index 4905f72eb4f..efc918fd9ef 100644 --- a/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/interpreter/mod.rs @@ -39,6 +39,14 @@ struct Interpreter<'ssa> { /// expected to have no effect if there are no such instructions or if the code /// being executed is an unconstrained function. side_effects_enabled: bool, + + options: InterpreterOptions, +} + +#[derive(Copy, Clone, Default)] +pub struct InterpreterOptions { + /// If true, the interpreter will print each value definition to stdout. + pub print_definitions: bool, } struct CallContext { @@ -66,20 +74,33 @@ type IResults = IResult>; #[allow(unused)] impl Ssa { pub fn interpret(&self, args: Vec) -> IResults { - self.interpret_function(self.main_id, args) + self.interpret_with_options(args, InterpreterOptions::default()) + } + + pub fn interpret_with_options( + &self, + args: Vec, + options: InterpreterOptions, + ) -> IResults { + self.interpret_function(self.main_id, args, options) } - pub(crate) fn interpret_function(&self, function: FunctionId, args: Vec) -> IResults { - let mut interpreter = Interpreter::new(self); + fn interpret_function( + &self, + function: FunctionId, + args: Vec, + options: InterpreterOptions, + ) -> IResults { + let mut interpreter = Interpreter::new(self, options); interpreter.interpret_globals()?; interpreter.call_function(function, args) } } impl<'ssa> Interpreter<'ssa> { - fn new(ssa: &'ssa Ssa) -> Self { + fn new(ssa: &'ssa Ssa, options: InterpreterOptions) -> Self { let call_stack = vec![CallContext::global_context()]; - Self { ssa, call_stack, side_effects_enabled: true } + Self { ssa, call_stack, side_effects_enabled: true, options } } fn call_context(&self) -> &CallContext { @@ -113,6 +134,9 @@ impl<'ssa> Interpreter<'ssa> { /// Define or redefine a value. /// Redefinitions are expected in the case of loops. fn define(&mut self, id: ValueId, value: Value) { + if self.options.print_definitions { + println!("{id} = {value}"); + } self.call_context_mut().scope.insert(id, value); } diff --git a/tooling/nargo_cli/src/cli/interpret_cmd.rs b/tooling/nargo_cli/src/cli/interpret_cmd.rs index 5aff10b11d7..d467a811b7f 100644 --- a/tooling/nargo_cli/src/cli/interpret_cmd.rs +++ b/tooling/nargo_cli/src/cli/interpret_cmd.rs @@ -12,6 +12,7 @@ use noirc_driver::{CompilationResult, CompileOptions}; use clap::Args; use noirc_errors::CustomDiagnostic; use noirc_evaluator::brillig::BrilligOptions; +use noirc_evaluator::ssa::interpreter::InterpreterOptions; use noirc_evaluator::ssa::interpreter::value::Value; use noirc_evaluator::ssa::ssa_gen::{Ssa, generate_ssa}; use noirc_evaluator::ssa::{SsaEvaluatorOptions, SsaLogging, primary_passes}; @@ -44,6 +45,10 @@ pub(super) struct InterpretCommand { /// When nothing is specified, the SSA is interpreted after all passes. #[clap(long)] ssa_pass: Vec, + + /// If true, the interpreter will print each value definition to stdout. + #[clap(long)] + print_definitions: bool, } impl WorkspaceCommand for InterpretCommand { @@ -93,7 +98,16 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl let mut ssa = generate_ssa(program) .map_err(|e| CliError::Generic(format!("failed to generate SSA: {e}")))?; - print_and_interpret_ssa(&ssa_options, &args.ssa_pass, &mut ssa, "Initial SSA", &ssa_input); + let interpreter_options = InterpreterOptions { print_definitions: args.print_definitions }; + + print_and_interpret_ssa( + &ssa_options, + &args.ssa_pass, + &mut ssa, + "Initial SSA", + &ssa_input, + interpreter_options, + ); // Run SSA passes in the pipeline and interpret the ones we are interested in. for (i, ssa_pass) in ssa_passes.iter().enumerate() { @@ -107,7 +121,14 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl .run(ssa) .map_err(|e| CliError::Generic(format!("failed to run SSA pass {msg}: {e}")))?; - print_and_interpret_ssa(&ssa_options, &args.ssa_pass, &mut ssa, &msg, &ssa_input); + print_and_interpret_ssa( + &ssa_options, + &args.ssa_pass, + &mut ssa, + &msg, + &ssa_input, + interpreter_options, + ); } } Ok(()) @@ -202,11 +223,17 @@ fn print_ssa(options: &SsaEvaluatorOptions, ssa: &mut Ssa, msg: &str) { } } -fn interpret_ssa(passes_to_interpret: &[String], ssa: &Ssa, msg: &str, args: &[Value]) { +fn interpret_ssa( + passes_to_interpret: &[String], + ssa: &Ssa, + msg: &str, + args: &[Value], + options: InterpreterOptions, +) { if passes_to_interpret.is_empty() || msg_matches(passes_to_interpret, msg) { // We need to give a fresh copy of arrays each time, because the shared structures are modified. let args = Value::snapshot_args(args); - let result = ssa.interpret(args); + let result = ssa.interpret_with_options(args, options); println!("--- Interpreter result after {msg}:\n{result:?}\n---"); } } @@ -217,7 +244,8 @@ fn print_and_interpret_ssa( ssa: &mut Ssa, msg: &str, args: &[Value], + interpreter_options: InterpreterOptions, ) { print_ssa(options, ssa, msg); - interpret_ssa(passes_to_interpret, ssa, msg, args); + interpret_ssa(passes_to_interpret, ssa, msg, args, interpreter_options); }