diff --git a/Cargo.lock b/Cargo.lock index e82d47d690a..bf8c5c1dd2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3046,6 +3046,7 @@ dependencies = [ "noir_lsp", "noirc_abi", "noirc_artifacts", + "noirc_artifacts_info", "noirc_driver", "noirc_errors", "noirc_frontend", @@ -3187,6 +3188,20 @@ dependencies = [ "rand", ] +[[package]] +name = "noir_inspector" +version = "1.0.0-beta.1" +dependencies = [ + "acir", + "clap", + "color-eyre", + "const_format", + "noirc_artifacts", + "noirc_artifacts_info", + "serde", + "serde_json", +] + [[package]] name = "noir_lsp" version = "1.0.0-beta.1" @@ -3319,6 +3334,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "noirc_artifacts_info" +version = "1.0.0-beta.1" +dependencies = [ + "acir", + "acvm", + "clap", + "iter-extended", + "noirc_artifacts", + "prettytable-rs", + "rayon", + "serde", + "serde_json", +] + [[package]] name = "noirc_driver" version = "1.0.0-beta.1" diff --git a/Cargo.toml b/Cargo.toml index 567f1db085b..58ca7665c0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ members = [ "tooling/nargo_cli", "tooling/nargo_toml", "tooling/noirc_artifacts", + "tooling/noirc_artifacts_info", "tooling/noirc_abi", "tooling/noirc_abi_wasm", "tooling/acvm_cli", "tooling/profiler", + "tooling/inspector", # ACVM "acvm-repo/acir_field", "acvm-repo/acir", @@ -35,7 +37,12 @@ members = [ # Utility crates "utils/iter-extended", ] -default-members = ["tooling/nargo_cli", "tooling/acvm_cli", "tooling/profiler"] +default-members = [ + "tooling/nargo_cli", + "tooling/acvm_cli", + "tooling/profiler", + "tooling/inspector", +] resolver = "2" [workspace.package] @@ -83,6 +90,7 @@ noir_lsp = { path = "tooling/lsp" } noir_debugger = { path = "tooling/debugger" } noirc_abi = { path = "tooling/noirc_abi" } noirc_artifacts = { path = "tooling/noirc_artifacts" } +noirc_artifacts_info = { path = "tooling/noirc_artifacts_info" } # Arkworks ark-bn254 = { version = "^0.5.0", default-features = false, features = [ diff --git a/tooling/inspector/Cargo.toml b/tooling/inspector/Cargo.toml new file mode 100644 index 00000000000..2124f7e9a28 --- /dev/null +++ b/tooling/inspector/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "noir_inspector" +description = "Inspector for noir build artifacts" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "noir-inspector" +path = "src/main.rs" + +[dependencies] +clap.workspace = true +serde.workspace = true +serde_json.workspace = true +color-eyre.workspace = true +const_format.workspace = true +acir.workspace = true +noirc_artifacts.workspace = true +noirc_artifacts_info.workspace = true diff --git a/tooling/inspector/src/cli/info_cmd.rs b/tooling/inspector/src/cli/info_cmd.rs new file mode 100644 index 00000000000..6a9db2676f2 --- /dev/null +++ b/tooling/inspector/src/cli/info_cmd.rs @@ -0,0 +1,35 @@ +use std::path::PathBuf; + +use clap::Args; +use color_eyre::eyre; +use noirc_artifacts::program::ProgramArtifact; +use noirc_artifacts_info::{count_opcodes_and_gates_in_program, show_info_report, InfoReport}; + +#[derive(Debug, Clone, Args)] +pub(crate) struct InfoCommand { + /// The artifact to inspect + artifact: PathBuf, + + /// Output a JSON formatted report. Changes to this format are not currently considered breaking. + #[clap(long, hide = true)] + json: bool, +} + +pub(crate) fn run(args: InfoCommand) -> eyre::Result<()> { + let file = std::fs::File::open(args.artifact.clone())?; + let artifact: ProgramArtifact = serde_json::from_reader(file)?; + + let package_name = args + .artifact + .with_extension("") + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| "artifact".to_string()); + + let program_info = count_opcodes_and_gates_in_program(artifact, package_name.to_string(), None); + + let info_report = InfoReport { programs: vec![program_info] }; + show_info_report(info_report, args.json); + + Ok(()) +} diff --git a/tooling/inspector/src/cli/mod.rs b/tooling/inspector/src/cli/mod.rs new file mode 100644 index 00000000000..8cce6ec3a6f --- /dev/null +++ b/tooling/inspector/src/cli/mod.rs @@ -0,0 +1,33 @@ +use clap::{command, Parser, Subcommand}; +use color_eyre::eyre; +use const_format::formatcp; + +mod info_cmd; +mod print_acir_cmd; + +const INSPECTOR_VERSION: &str = env!("CARGO_PKG_VERSION"); + +static VERSION_STRING: &str = formatcp!("version = {}\n", INSPECTOR_VERSION,); + +#[derive(Parser, Debug)] +#[command(name="Noir inspector", author, version=VERSION_STRING, about, long_about = None)] +struct InspectorCli { + #[command(subcommand)] + command: InspectorCommand, +} + +#[non_exhaustive] +#[derive(Subcommand, Clone, Debug)] +enum InspectorCommand { + Info(info_cmd::InfoCommand), + PrintAcir(print_acir_cmd::PrintAcirCommand), +} + +pub(crate) fn start_cli() -> eyre::Result<()> { + let InspectorCli { command } = InspectorCli::parse(); + + match command { + InspectorCommand::Info(args) => info_cmd::run(args), + InspectorCommand::PrintAcir(args) => print_acir_cmd::run(args), + } +} diff --git a/tooling/inspector/src/cli/print_acir_cmd.rs b/tooling/inspector/src/cli/print_acir_cmd.rs new file mode 100644 index 00000000000..f3dfe528973 --- /dev/null +++ b/tooling/inspector/src/cli/print_acir_cmd.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +use clap::Args; +use color_eyre::eyre; +use noirc_artifacts::program::ProgramArtifact; + +#[derive(Debug, Clone, Args)] +pub(crate) struct PrintAcirCommand { + /// The artifact to print + artifact: PathBuf, +} + +pub(crate) fn run(args: PrintAcirCommand) -> eyre::Result<()> { + let file = std::fs::File::open(args.artifact.clone())?; + let artifact: ProgramArtifact = serde_json::from_reader(file)?; + + println!("Compiled ACIR for main:"); + println!("{}", artifact.bytecode); + + Ok(()) +} diff --git a/tooling/inspector/src/main.rs b/tooling/inspector/src/main.rs new file mode 100644 index 00000000000..8270fedbf2c --- /dev/null +++ b/tooling/inspector/src/main.rs @@ -0,0 +1,8 @@ +mod cli; + +fn main() { + if let Err(report) = cli::start_cli() { + eprintln!("{report:?}"); + std::process::exit(1); + } +} diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index 001306bb162..92eeed1b391 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -43,6 +43,7 @@ noirc_frontend = { workspace = true, features = ["bn254"] } noirc_abi.workspace = true noirc_errors.workspace = true noirc_artifacts.workspace = true +noirc_artifacts_info.workspace = true acvm = { workspace = true, features = ["bn254"] } bn254_blackbox_solver.workspace = true toml.workspace = true diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 8d0fc257e1c..e60f88ca880 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -8,8 +8,11 @@ use nargo::{ use nargo_toml::{get_package_manifest, resolve_workspace_from_toml}; use noirc_abi::input_parser::Format; use noirc_artifacts::program::ProgramArtifact; +use noirc_artifacts_info::{ + count_opcodes_and_gates_in_program, show_info_report, FunctionInfo, InfoReport, ProgramInfo, +}; use noirc_driver::{CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; -use prettytable::{row, table, Row}; +use prettytable::{row, Row}; use rayon::prelude::*; use serde::Serialize; @@ -94,74 +97,18 @@ pub(crate) fn run(mut args: InfoCommand, config: NargoConfig) -> Result<(), CliE package.expression_width, args.compile_options.expression_width, ); - count_opcodes_and_gates_in_program(program, &package, target_width) + let package_name = package.name.to_string(); + count_opcodes_and_gates_in_program(program, package_name, Some(target_width)) }) .collect() }; let info_report = InfoReport { programs: program_info }; - - if args.json { - // Expose machine-readable JSON data. - println!("{}", serde_json::to_string(&info_report).unwrap()); - } else { - // Otherwise print human-readable table. - if !info_report.programs.is_empty() { - let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Brillig Opcodes"]); - - for program_info in info_report.programs { - let program_rows: Vec = program_info.into(); - for row in program_rows { - program_table.add_row(row); - } - } - program_table.printstd(); - } - } + show_info_report(info_report, args.json); Ok(()) } -#[derive(Debug, Default, Serialize)] -struct InfoReport { - programs: Vec, -} - -#[derive(Debug, Serialize)] -struct ProgramInfo { - package_name: String, - #[serde(skip)] - expression_width: ExpressionWidth, - functions: Vec, - #[serde(skip)] - unconstrained_functions_opcodes: usize, - unconstrained_functions: Vec, -} - -impl From for Vec { - fn from(program_info: ProgramInfo) -> Self { - let mut main = vecmap(program_info.functions, |function| { - row![ - Fm->format!("{}", program_info.package_name), - Fc->format!("{}", function.name), - format!("{:?}", program_info.expression_width), - Fc->format!("{}", function.opcodes), - Fc->format!("{}", program_info.unconstrained_functions_opcodes), - ] - }); - main.extend(vecmap(program_info.unconstrained_functions, |function| { - row![ - Fm->format!("{}", program_info.package_name), - Fc->format!("{}", function.name), - format!("N/A", ), - Fc->format!("N/A"), - Fc->format!("{}", function.opcodes), - ] - })); - main - } -} - #[derive(Debug, Serialize)] struct ContractInfo { name: String, @@ -171,12 +118,6 @@ struct ContractInfo { functions: Vec, } -#[derive(Debug, Serialize)] -struct FunctionInfo { - name: String, - opcodes: usize, -} - impl From for Vec { fn from(contract_info: ContractInfo) -> Self { vecmap(contract_info.functions, |function| { @@ -190,51 +131,6 @@ impl From for Vec { } } -fn count_opcodes_and_gates_in_program( - compiled_program: ProgramArtifact, - package: &Package, - expression_width: ExpressionWidth, -) -> ProgramInfo { - let functions = compiled_program - .bytecode - .functions - .into_par_iter() - .enumerate() - .map(|(i, function)| FunctionInfo { - name: compiled_program.names[i].clone(), - opcodes: function.opcodes.len(), - }) - .collect(); - - let opcodes_len: Vec = compiled_program - .bytecode - .unconstrained_functions - .iter() - .map(|func| func.bytecode.len()) - .collect(); - let unconstrained_functions_opcodes = compiled_program - .bytecode - .unconstrained_functions - .into_par_iter() - .map(|function| function.bytecode.len()) - .sum(); - let unconstrained_info: Vec = compiled_program - .brillig_names - .clone() - .iter() - .zip(opcodes_len) - .map(|(name, len)| FunctionInfo { name: name.clone(), opcodes: len }) - .collect(); - - ProgramInfo { - package_name: package.name.to_string(), - expression_width, - functions, - unconstrained_functions_opcodes, - unconstrained_functions: unconstrained_info, - } -} - fn profile_brillig_execution( binary_packages: Vec<(Package, ProgramArtifact)>, prover_name: &str, @@ -270,7 +166,7 @@ fn profile_brillig_execution( program_info.push(ProgramInfo { package_name: package.name.to_string(), - expression_width, + expression_width: Some(expression_width), functions: vec![FunctionInfo { name: "main".to_string(), opcodes: 0 }], unconstrained_functions_opcodes: profiling_samples.len(), unconstrained_functions: vec![FunctionInfo { diff --git a/tooling/noirc_artifacts_info/Cargo.toml b/tooling/noirc_artifacts_info/Cargo.toml new file mode 100644 index 00000000000..0b8d18eef2f --- /dev/null +++ b/tooling/noirc_artifacts_info/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "noirc_artifacts_info" +description = "The logic for `nargo info` and `nargo-inspector`" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +clap.workspace = true +serde.workspace = true +serde_json.workspace = true +acir.workspace = true +noirc_artifacts.workspace = true +rayon.workspace = true +acvm = { workspace = true, features = ["bn254"] } +iter-extended.workspace = true +prettytable-rs = "0.10" diff --git a/tooling/noirc_artifacts_info/src/lib.rs b/tooling/noirc_artifacts_info/src/lib.rs new file mode 100644 index 00000000000..6f4c80accbd --- /dev/null +++ b/tooling/noirc_artifacts_info/src/lib.rs @@ -0,0 +1,122 @@ +use acvm::acir::circuit::ExpressionWidth; +use iter_extended::vecmap; +use noirc_artifacts::program::ProgramArtifact; +use prettytable::{row, table, Row}; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +use serde::Serialize; + +#[derive(Debug, Default, Serialize)] +pub struct InfoReport { + pub programs: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ProgramInfo { + pub package_name: String, + #[serde(skip)] + pub expression_width: Option, + pub functions: Vec, + #[serde(skip)] + pub unconstrained_functions_opcodes: usize, + pub unconstrained_functions: Vec, +} + +impl From for Vec { + fn from(program_info: ProgramInfo) -> Self { + let expression_width = if let Some(expression_width) = program_info.expression_width { + format!("{:?}", expression_width) + } else { + "N/A".to_string() + }; + let mut main = vecmap(program_info.functions, |function| { + row![ + Fm->format!("{}", program_info.package_name), + Fc->format!("{}", function.name), + format!("{}", expression_width), + Fc->format!("{}", function.opcodes), + Fc->format!("{}", program_info.unconstrained_functions_opcodes), + ] + }); + main.extend(vecmap(program_info.unconstrained_functions, |function| { + row![ + Fm->format!("{}", program_info.package_name), + Fc->format!("{}", function.name), + format!("N/A", ), + Fc->format!("N/A"), + Fc->format!("{}", function.opcodes), + ] + })); + main + } +} + +#[derive(Debug, Serialize)] +pub struct FunctionInfo { + pub name: String, + pub opcodes: usize, +} + +pub fn count_opcodes_and_gates_in_program( + compiled_program: ProgramArtifact, + package_name: String, + expression_width: Option, +) -> ProgramInfo { + let functions = compiled_program + .bytecode + .functions + .into_par_iter() + .enumerate() + .map(|(i, function)| FunctionInfo { + name: compiled_program.names[i].clone(), + opcodes: function.opcodes.len(), + }) + .collect(); + + let opcodes_len: Vec = compiled_program + .bytecode + .unconstrained_functions + .iter() + .map(|func| func.bytecode.len()) + .collect(); + let unconstrained_functions_opcodes = compiled_program + .bytecode + .unconstrained_functions + .into_par_iter() + .map(|function| function.bytecode.len()) + .sum(); + let unconstrained_info: Vec = compiled_program + .brillig_names + .clone() + .iter() + .zip(opcodes_len) + .map(|(name, len)| FunctionInfo { name: name.clone(), opcodes: len }) + .collect(); + + ProgramInfo { + package_name, + expression_width, + functions, + unconstrained_functions_opcodes, + unconstrained_functions: unconstrained_info, + } +} + +pub fn show_info_report(info_report: InfoReport, json: bool) { + if json { + // Expose machine-readable JSON data. + println!("{}", serde_json::to_string(&info_report).unwrap()); + } else { + // Otherwise print human-readable table. + if !info_report.programs.is_empty() { + let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Brillig Opcodes"]); + + for program_info in info_report.programs { + let program_rows: Vec = program_info.into(); + for row in program_rows { + program_table.add_row(row); + } + } + program_table.printstd(); + } + } +}