diff --git a/compiler/noirc_errors/src/debug_info.rs b/compiler/noirc_errors/src/debug_info.rs index 946841c279b..a4092ec4a97 100644 --- a/compiler/noirc_errors/src/debug_info.rs +++ b/compiler/noirc_errors/src/debug_info.rs @@ -4,6 +4,7 @@ use acvm::compiler::AcirTransformationMap; use serde_with::serde_as; use serde_with::DisplayFromStr; use std::collections::BTreeMap; +use std::collections::HashMap; use std::mem; use crate::Location; @@ -19,6 +20,13 @@ pub struct DebugInfo { pub locations: BTreeMap>, } +/// Holds OpCodes Counts for Acir and Brillig Opcodes +/// To be printed with `nargo info --profile-info` +pub struct OpCodesCount { + pub acir_size: usize, + pub brillig_size: usize, +} + impl DebugInfo { pub fn new(locations: BTreeMap>) -> Self { DebugInfo { locations } @@ -42,4 +50,38 @@ impl DebugInfo { pub fn opcode_location(&self, loc: &OpcodeLocation) -> Option> { self.locations.get(loc).cloned() } + + pub fn count_span_opcodes(&self) -> HashMap<&Location, OpCodesCount> { + let mut accumulator: HashMap<&Location, Vec<&OpcodeLocation>> = HashMap::new(); + + for (opcode_location, locations) in self.locations.iter() { + for location in locations.iter() { + let opcodes = accumulator.entry(location).or_insert(Vec::new()); + opcodes.push(opcode_location); + } + } + + let counted_opcodes = accumulator + .iter() + .map(|(location, opcodes)| { + let acir_opcodes: Vec<_> = opcodes + .iter() + .filter(|opcode_location| matches!(opcode_location, OpcodeLocation::Acir(_))) + .collect(); + let brillig_opcodes: Vec<_> = opcodes + .iter() + .filter(|opcode_location| { + matches!(opcode_location, OpcodeLocation::Brillig { .. }) + }) + .collect(); + let opcodes_count = OpCodesCount { + acir_size: acir_opcodes.len(), + brillig_size: brillig_opcodes.len(), + }; + (*location, opcodes_count) + }) + .collect(); + + counted_opcodes + } } diff --git a/tooling/nargo/src/artifacts/debug.rs b/tooling/nargo/src/artifacts/debug.rs index 19d8b88e641..40acc7db8f8 100644 --- a/tooling/nargo/src/artifacts/debug.rs +++ b/tooling/nargo/src/artifacts/debug.rs @@ -1,5 +1,5 @@ use codespan_reporting::files::{Error, Files, SimpleFile}; -use noirc_driver::DebugFile; +use noirc_driver::{CompiledContract, CompiledProgram, DebugFile}; use noirc_errors::{debug_info::DebugInfo, Location}; use noirc_evaluator::errors::SsaReport; use serde::{Deserialize, Serialize}; @@ -96,6 +96,32 @@ impl DebugArtifact { } } +impl From for DebugArtifact { + fn from(compiled_program: CompiledProgram) -> Self { + DebugArtifact { + debug_symbols: vec![compiled_program.debug], + file_map: compiled_program.file_map, + warnings: compiled_program.warnings, + } + } +} + +impl From<&CompiledContract> for DebugArtifact { + fn from(compiled_artifact: &CompiledContract) -> Self { + let all_functions_debug: Vec = compiled_artifact + .functions + .iter() + .map(|contract_function| contract_function.debug.clone()) + .collect(); + + DebugArtifact { + debug_symbols: all_functions_debug, + file_map: compiled_artifact.file_map.clone(), + warnings: compiled_artifact.warnings.clone(), + } + } +} + impl<'a> Files<'a> for DebugArtifact { type FileId = FileId; type Name = PathString; diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index de0c63c3ab3..665eb2ca256 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -1,12 +1,15 @@ +use std::collections::HashMap; + use acvm::Language; use backend_interface::BackendError; use clap::Args; use iter_extended::vecmap; -use nargo::package::Package; +use nargo::{artifacts::debug::DebugArtifact, package::Package}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{ CompileOptions, CompiledContract, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; +use noirc_errors::{debug_info::OpCodesCount, Location}; use noirc_frontend::graph::CrateName; use prettytable::{row, table, Row}; use rayon::prelude::*; @@ -36,6 +39,9 @@ pub(crate) struct InfoCommand { #[clap(long, hide = true)] json: bool, + #[clap(long, hide = true)] + profile_info: bool, + #[clap(flatten)] compile_options: CompileOptions, } @@ -71,6 +77,23 @@ pub(crate) fn run( &args.compile_options, )?; + if args.profile_info { + for compiled_program in &compiled_programs { + let span_opcodes = compiled_program.debug.count_span_opcodes(); + let debug_artifact: DebugArtifact = compiled_program.clone().into(); + print_span_opcodes(&span_opcodes, &debug_artifact); + } + + for compiled_contract in &compiled_contracts { + let debug_artifact: DebugArtifact = compiled_contract.clone().into(); + let functions = &compiled_contract.functions; + for contract_function in functions { + let span_opcodes = contract_function.debug.count_span_opcodes(); + print_span_opcodes(&span_opcodes, &debug_artifact); + } + } + } + let program_info = binary_packages .into_par_iter() .zip(compiled_programs) @@ -121,6 +144,55 @@ pub(crate) fn run( Ok(()) } +/// Provides profiling information on +/// +/// Number of OpCodes in relation to Noir source file +/// and line number information +fn print_span_opcodes( + span_opcodes_map: &HashMap<&Location, OpCodesCount>, + debug_artifact: &DebugArtifact, +) { + let mut pairs: Vec<(&&Location, &OpCodesCount)> = span_opcodes_map.iter().collect(); + + pairs.sort_by(|a, b| { + a.1.acir_size.cmp(&b.1.acir_size).then_with(|| a.1.brillig_size.cmp(&b.1.brillig_size)) + }); + + for (location, opcodes_count) in pairs { + let debug_file = debug_artifact.file_map.get(&location.file).unwrap(); + + let start_byte = byte_index(&debug_file.source, location.span.start() + 1); + let end_byte = byte_index(&debug_file.source, location.span.end() + 1); + let range = start_byte..end_byte; + let span_content = &debug_file.source[range]; + let line = debug_artifact.location_line_index(**location).unwrap() + 1; + println!( + "Ln. {}: {} (ACIR:{}, Brillig:{} opcode|s) in file: {}", + line, + span_content, + opcodes_count.acir_size, + opcodes_count.brillig_size, + debug_file.path.to_str().unwrap() + ); + } +} +fn byte_index(string: &str, index: u32) -> usize { + let mut byte_index = 0; + let mut char_index = 0; + + #[allow(clippy::explicit_counter_loop)] + for (byte_offset, _) in string.char_indices() { + if char_index == index { + return byte_index; + } + + byte_index = byte_offset; + char_index += 1; + } + + byte_index +} + #[derive(Debug, Default, Serialize)] struct InfoReport { programs: Vec,