diff --git a/Cargo.lock b/Cargo.lock index a522ccb3bed..2f4c54f39a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3254,6 +3254,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "thiserror", "tracing-appender", "tracing-subscriber", ] diff --git a/tooling/profiler/Cargo.toml b/tooling/profiler/Cargo.toml index 798a21ea0d6..cdd014f6d65 100644 --- a/tooling/profiler/Cargo.toml +++ b/tooling/profiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "noir_profiler" -description = "Profiler for noir circuits" +description = "Profiler for Noir circuits" version.workspace = true authors.workspace = true edition.workspace = true @@ -34,6 +34,7 @@ nargo.workspace = true noirc_errors.workspace = true noirc_abi.workspace = true noirc_evaluator.workspace = true +thiserror.workspace = true # Logs tracing-subscriber.workspace = true diff --git a/tooling/profiler/src/cli/execution_flamegraph_cmd.rs b/tooling/profiler/src/cli/execution_flamegraph_cmd.rs index dbb2a2c38bc..43a5ca4680f 100644 --- a/tooling/profiler/src/cli/execution_flamegraph_cmd.rs +++ b/tooling/profiler/src/cli/execution_flamegraph_cmd.rs @@ -1,11 +1,15 @@ use std::path::{Path, PathBuf}; +use acir::circuit::Opcode; use acir::circuit::OpcodeLocation; use clap::Args; use color_eyre::eyre::{self, Context}; +use nargo::errors::try_to_diagnose_runtime_error; use nargo::foreign_calls::DefaultForeignCallBuilder; use nargo::PrintOutput; +use noirc_artifacts::program::ProgramArtifact; +use crate::errors::{report_error, CliError}; use crate::flamegraph::{BrilligExecutionSample, FlamegraphGenerator, InfernoFlamegraphGenerator}; use crate::fs::{read_inputs_from_file, read_program_from_file}; use crate::opcode_formatter::format_brillig_opcode; @@ -13,6 +17,7 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use noirc_abi::input_parser::Format; use noirc_artifacts::debug::DebugArtifact; +/// Generates a flamegraph mapping unconstrained Noir execution to source code. #[derive(Debug, Clone, Args)] pub(crate) struct ExecutionFlamegraphCommand { /// The path to the artifact JSON file @@ -54,17 +59,40 @@ fn run_with_generator( let program = read_program_from_file(artifact_path).context("Error reading program from file")?; + ensure_brillig_entry_point(&program)?; + let (inputs_map, _) = read_inputs_from_file(prover_toml_path, Format::Toml, &program.abi)?; let initial_witness = program.abi.encode(&inputs_map, None)?; - println!("Executing"); - let (_, mut profiling_samples) = nargo::ops::execute_program_with_profiling( + println!("Executing..."); + + let solved_witness_stack_err = nargo::ops::execute_program_with_profiling( &program.bytecode, initial_witness, &Bn254BlackBoxSolver(pedantic_solving), &mut DefaultForeignCallBuilder::default().with_output(PrintOutput::Stdout).build(), - )?; + ); + let mut profiling_samples = match solved_witness_stack_err { + Ok((_, profiling_samples)) => profiling_samples, + Err(err) => { + let debug_artifact = DebugArtifact { + debug_symbols: program.debug_symbols.debug_infos.clone(), + file_map: program.file_map.clone(), + }; + + if let Some(diagnostic) = try_to_diagnose_runtime_error( + &err, + &program.abi, + &program.debug_symbols.debug_infos, + ) { + diagnostic.report(&debug_artifact, false); + } + + return Err(CliError::Generic.into()); + } + }; + println!("Executed"); println!("Collecting {} samples", profiling_samples.len()); @@ -104,3 +132,22 @@ fn run_with_generator( Ok(()) } + +fn ensure_brillig_entry_point(artifact: &ProgramArtifact) -> Result<(), CliError> { + let err_msg = "Command only supports fully unconstrained Noir programs e.g. `unconstrained fn main() { .. }".to_owned(); + let program = &artifact.bytecode; + if program.functions.len() != 1 || program.unconstrained_functions.len() != 1 { + return report_error(err_msg); + } + + let main_function = &program.functions[0]; + let Opcode::BrilligCall { id, .. } = main_function.opcodes[0] else { + return report_error(err_msg); + }; + + if id.as_usize() != 0 { + return report_error(err_msg); + } + + Ok(()) +} diff --git a/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index e68a8cd5bd2..23ea15b402a 100644 --- a/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -11,13 +11,14 @@ use crate::fs::read_program_from_file; use crate::gates_provider::{BackendGatesProvider, GatesProvider}; use crate::opcode_formatter::format_acir_opcode; +/// Generates a flamegraph mapping backend opcodes to their associated locations in the source code. #[derive(Debug, Clone, Args)] pub(crate) struct GatesFlamegraphCommand { /// The path to the artifact JSON file #[clap(long, short)] artifact_path: String, - /// Path to the noir backend binary + /// Path to the Noir backend binary #[clap(long, short)] backend_path: String, @@ -25,6 +26,7 @@ pub(crate) struct GatesFlamegraphCommand { #[clap(long, short = 'g', default_value = "gates")] backend_gates_command: String, + /// Optional arguments for the backend gates command #[arg(trailing_var_arg = true, allow_hyphen_values = true)] backend_extra_args: Vec, diff --git a/tooling/profiler/src/cli/mod.rs b/tooling/profiler/src/cli/mod.rs index 80c6bceb3ce..b91dd6990aa 100644 --- a/tooling/profiler/src/cli/mod.rs +++ b/tooling/profiler/src/cli/mod.rs @@ -32,5 +32,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { ProfilerCommand::Gates(args) => gates_flamegraph_cmd::run(args), ProfilerCommand::Opcodes(args) => opcodes_flamegraph_cmd::run(args), ProfilerCommand::ExecutionOpcodes(args) => execution_flamegraph_cmd::run(args), - } + }?; + + Ok(()) } diff --git a/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index 4e271c9f838..90d5da08c1c 100644 --- a/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -11,6 +11,7 @@ use crate::flamegraph::{CompilationSample, FlamegraphGenerator, InfernoFlamegrap use crate::fs::read_program_from_file; use crate::opcode_formatter::{format_acir_opcode, format_brillig_opcode}; +/// Generates a flamegraph mapping ACIR opcodes to their associated locations in the source code. #[derive(Debug, Clone, Args)] pub(crate) struct OpcodesFlamegraphCommand { /// The path to the artifact JSON file diff --git a/tooling/profiler/src/errors.rs b/tooling/profiler/src/errors.rs new file mode 100644 index 00000000000..c49b0b335dd --- /dev/null +++ b/tooling/profiler/src/errors.rs @@ -0,0 +1,16 @@ +use fm::FileMap; +use noirc_errors::{CustomDiagnostic, Span}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub(crate) enum CliError { + #[error("Failed to run profiler command")] + Generic, +} + +/// Report an error from the CLI that is not reliant on a stack trace. +pub(crate) fn report_error(message: String) -> Result<(), CliError> { + let error = CustomDiagnostic::simple_error(message.clone(), String::new(), Span::default()); + noirc_errors::reporter::report(&FileMap::default(), &error, None, false); + Err(CliError::Generic) +} diff --git a/tooling/profiler/src/main.rs b/tooling/profiler/src/main.rs index b0b42e6ee41..a49c24a40b2 100644 --- a/tooling/profiler/src/main.rs +++ b/tooling/profiler/src/main.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] mod cli; +mod errors; mod flamegraph; mod fs; mod gates_provider; @@ -33,7 +34,7 @@ fn main() { } if let Err(report) = cli::start_cli() { - eprintln!("{report:?}"); + eprintln!("{report}"); std::process::exit(1); } }