Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nargo): add hidden option to produce JSON output from nargo info #2542

Merged
merged 2 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crates/nargo_cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ fn compile_success_empty_{test_name}() {{
let mut cmd = Command::cargo_bin("nargo").unwrap();
cmd.arg("--program-dir").arg(test_program_dir);
cmd.arg("info");
cmd.arg("--json");

let output = cmd.output().expect("Failed to execute command");

// `compile_success_empty` tests should be able to compile down to an empty circuit.
cmd.assert().stdout(predicate::str::contains("| Package")
.and(predicate::str::contains("| Language"))
.and(predicate::str::contains("| ACIR Opcodes | Backend Circuit Size |"))
.and(predicate::str::contains("| PLONKCSat {{ width: 3 }} |"))
// This currently matches on there being zero acir opcodes due to the width of the cell.
.and(predicate::str::contains("| 0 |")));
let json: serde_json::Value = serde_json::from_slice(&output.stdout).expect("JSON was not well-formatted");
let num_opcodes = &json["programs"][0]["acir_opcodes"];
assert_eq!(num_opcodes.as_u64().unwrap(), 0);
}}
"#,
test_dir = test_dir.display(),
Expand Down
188 changes: 120 additions & 68 deletions crates/nargo_cli/src/cli/info_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use acvm::Language;
use acvm_backend_barretenberg::BackendError;
use clap::Args;
use iter_extended::try_vecmap;
use iter_extended::{try_vecmap, vecmap};
use nargo::{package::Package, prepare_package};
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection};
use noirc_driver::{compile_contracts, CompileOptions};
use noirc_frontend::graph::CrateName;
use prettytable::{row, Table};
use prettytable::{row, table, Row};
use serde::Serialize;

use crate::backends::Backend;
use crate::{cli::compile_cmd::compile_package, errors::CliError};
Expand All @@ -30,6 +32,10 @@ pub(crate) struct InfoCommand {
#[clap(long, conflicts_with = "package")]
workspace: bool,

/// Output a JSON formatted report. Changes to this format are not currently considered breaking.
#[clap(long, hide = true)]
json: bool,

#[clap(flatten)]
compile_options: CompileOptions,
}
Expand All @@ -45,99 +51,145 @@ pub(crate) fn run(
let selection = args.package.map_or(default_selection, PackageSelection::Selected);
let workspace = resolve_workspace_from_toml(&toml_path, selection)?;

let mut package_table = Table::new();
package_table.add_row(
row![Fm->"Package", Fm->"Language", Fm->"ACIR Opcodes", Fm->"Backend Circuit Size"],
);
let mut contract_table = Table::new();
contract_table.add_row(row![
Fm->"Contract",
Fm->"Function",
Fm->"Language",
Fm->"ACIR Opcodes",
Fm->"Backend Circuit Size"
]);
let mut info_report = InfoReport::default();

for package in &workspace {
if package.is_contract() {
count_opcodes_and_gates_in_contracts(
backend,
package,
&args.compile_options,
&mut contract_table,
)?;
let contract_info =
count_opcodes_and_gates_in_contracts(backend, package, &args.compile_options)?;
info_report.contracts.extend(contract_info);
} else {
count_opcodes_and_gates_in_package(
backend,
package,
&args.compile_options,
&mut package_table,
)?;
let program_info =
count_opcodes_and_gates_in_program(backend, package, &args.compile_options)?;
info_report.programs.push(program_info);
}
}

if package_table.len() > 1 {
package_table.printstd();
}
if contract_table.len() > 1 {
contract_table.printstd();
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->"Language", Fm->"ACIR Opcodes", Fm->"Backend Circuit Size"]);

for program in info_report.programs {
program_table.add_row(program.into());
}
program_table.printstd();
}
if !info_report.contracts.is_empty() {
let mut contract_table = table!([
Fm->"Contract",
Fm->"Function",
Fm->"Language",
Fm->"ACIR Opcodes",
Fm->"Backend Circuit Size"
]);
for contract_info in info_report.contracts {
let contract_rows: Vec<Row> = contract_info.into();
for row in contract_rows {
contract_table.add_row(row);
}
}

contract_table.printstd();
}
}

Ok(())
}

fn count_opcodes_and_gates_in_package(
#[derive(Debug, Default, Serialize)]
struct InfoReport {
programs: Vec<ProgramInfo>,
contracts: Vec<ContractInfo>,
}

#[derive(Debug, Serialize)]
struct ProgramInfo {
name: String,
#[serde(skip)]
language: Language,
acir_opcodes: usize,
circuit_size: u32,
}

impl From<ProgramInfo> for Row {
fn from(program_info: ProgramInfo) -> Self {
row![
Fm->format!("{}", program_info.name),
format!("{:?}", program_info.language),
Fc->format!("{}", program_info.acir_opcodes),
Fc->format!("{}", program_info.circuit_size),
]
}
}

#[derive(Debug, Serialize)]
struct ContractInfo {
name: String,
#[serde(skip)]
language: Language,
functions: Vec<FunctionInfo>,
}

#[derive(Debug, Serialize)]
struct FunctionInfo {
name: String,
acir_opcodes: usize,
circuit_size: u32,
}

impl From<ContractInfo> for Vec<Row> {
fn from(contract_info: ContractInfo) -> Self {
vecmap(contract_info.functions, |function| {
row![
Fm->format!("{}", contract_info.name),
Fc->format!("{}", function.name),
format!("{:?}", contract_info.language),
Fc->format!("{}", function.acir_opcodes),
Fc->format!("{}", function.circuit_size),
]
})
}
}

fn count_opcodes_and_gates_in_program(
backend: &Backend,
package: &Package,
compile_options: &CompileOptions,
table: &mut Table,
) -> Result<(), CliError> {
) -> Result<ProgramInfo, CliError> {
let (_, compiled_program) = compile_package(backend, package, compile_options)?;

let num_opcodes = compiled_program.circuit.opcodes.len();
let exact_circuit_size = backend.get_exact_circuit_size(&compiled_program.circuit)?;

table.add_row(row![
Fm->format!("{}", package.name),
format!("{:?}", backend.np_language()),
Fc->format!("{}", num_opcodes),
Fc->format!("{}", exact_circuit_size),
]);

Ok(())
Ok(ProgramInfo {
name: package.name.to_string(),
language: backend.np_language(),
acir_opcodes: compiled_program.circuit.opcodes.len(),
circuit_size: backend.get_exact_circuit_size(&compiled_program.circuit)?,
})
}

fn count_opcodes_and_gates_in_contracts(
backend: &Backend,
package: &Package,
compile_options: &CompileOptions,
table: &mut Table,
) -> Result<(), CliError> {
) -> Result<Vec<ContractInfo>, CliError> {
let (mut context, crate_id) = prepare_package(package);
let result = compile_contracts(&mut context, crate_id, compile_options);
let contracts = report_errors(result, &context, compile_options.deny_warnings)?;
let optimized_contracts =
try_vecmap(contracts, |contract| optimize_contract(backend, contract))?;

for contract in optimized_contracts {
let function_info: Vec<(String, usize, u32)> =
try_vecmap(contract.functions, |function| {
let num_opcodes = function.bytecode.opcodes.len();
let exact_circuit_size = backend.get_exact_circuit_size(&function.bytecode)?;

Ok::<_, BackendError>((function.name, num_opcodes, exact_circuit_size))
})?;

for info in function_info {
table.add_row(row![
Fm->format!("{}", contract.name),
Fc->format!("{}", info.0),
format!("{:?}", backend.np_language()),
Fc->format!("{}", info.1),
Fc->format!("{}", info.2),
]);
}
}

Ok(())
try_vecmap(optimized_contracts, |contract| {
let functions = try_vecmap(contract.functions, |function| -> Result<_, BackendError> {
Ok(FunctionInfo {
name: function.name,
acir_opcodes: function.bytecode.opcodes.len(),
circuit_size: backend.get_exact_circuit_size(&function.bytecode)?,
})
})?;

Ok(ContractInfo { name: contract.name, language: backend.np_language(), functions })
})
}