diff --git a/Cargo.lock b/Cargo.lock index 3add28d393..75f6040a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2776,6 +2776,7 @@ dependencies = [ "noirc_artifacts", "noirc_driver", "noirc_errors", + "noirc_evaluator", "serde", "serde_json", "tempfile", diff --git a/compiler/noirc_errors/src/debug_info.rs b/compiler/noirc_errors/src/debug_info.rs index b480d20fde..9267722f5a 100644 --- a/compiler/noirc_errors/src/debug_info.rs +++ b/compiler/noirc_errors/src/debug_info.rs @@ -48,6 +48,9 @@ pub type DebugVariables = BTreeMap; pub type DebugFunctions = BTreeMap; pub type DebugTypes = BTreeMap; +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct ProcedureDebugId(pub u32); + #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct ProgramDebugInfo { pub debug_infos: Vec, @@ -104,6 +107,9 @@ pub struct DebugInfo { pub variables: DebugVariables, pub functions: DebugFunctions, pub types: DebugTypes, + /// This a map per brillig function representing the range of opcodes where a procedure is activated. + pub brillig_procedure_locs: + BTreeMap>, } /// Holds OpCodes Counts for Acir and Brillig Opcodes @@ -124,8 +130,12 @@ impl DebugInfo { variables: DebugVariables, functions: DebugFunctions, types: DebugTypes, + brillig_procedure_locs: BTreeMap< + BrilligFunctionId, + BTreeMap, + >, ) -> Self { - Self { locations, brillig_locations, variables, functions, types } + Self { locations, brillig_locations, variables, functions, types, brillig_procedure_locs } } /// Updates the locations map when the [`Circuit`][acvm::acir::circuit::Circuit] is modified. diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index f066d967e0..49e259e0ce 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -67,9 +67,8 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 1 }, ], - assert_messages: Default::default(), - locations: Default::default(), name: "directive_invert".to_string(), + ..Default::default() } } @@ -132,8 +131,7 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { }, BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 2 }, ], - assert_messages: Default::default(), - locations: Default::default(), name: "directive_integer_quotient".to_string(), + ..Default::default() } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index 0c6097b8f6..3633700a79 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -36,6 +36,8 @@ use acvm::{ }; use debug_show::DebugShow; +use super::ProcedureId; + /// The Brillig VM does not apply a limit to the memory address space, /// As a convention, we take use 32 bits. This means that we assume that /// memory has 2^32 memory slots. @@ -111,9 +113,14 @@ impl BrilligContext { /// Special brillig context to codegen compiler intrinsic shared procedures impl BrilligContext { - pub(crate) fn new_for_procedure(enable_debug_trace: bool) -> BrilligContext { + pub(crate) fn new_for_procedure( + enable_debug_trace: bool, + procedure_id: ProcedureId, + ) -> BrilligContext { + let mut obj = BrilligArtifact::default(); + obj.procedure = Some(procedure_id); BrilligContext { - obj: BrilligArtifact::default(), + obj, registers: ScratchSpace::new(), context_label: Label::entrypoint(), current_section: 0, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index d0bd91e185..276456fc40 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -1,9 +1,10 @@ use acvm::acir::brillig::Opcode as BrilligOpcode; use std::collections::{BTreeMap, HashMap}; -use crate::brillig::brillig_ir::procedures::ProcedureId; use crate::ssa::ir::{basic_block::BasicBlockId, dfg::CallStack, function::FunctionId}; +use super::procedures::ProcedureId; + /// Represents a parameter or a return value of an entry point function. #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub(crate) enum BrilligParameter { @@ -24,6 +25,7 @@ pub(crate) struct GeneratedBrillig { pub(crate) locations: BTreeMap, pub(crate) assert_messages: BTreeMap, pub(crate) name: String, + pub(crate) procedure_locations: HashMap, } #[derive(Default, Debug, Clone)] @@ -53,6 +55,14 @@ pub(crate) struct BrilligArtifact { call_stack: CallStack, /// Name of the function, only used for debugging purposes. pub(crate) name: String, + + /// This field contains the given procedure id if this artifact originates from as procedure + pub(crate) procedure: Option, + /// Procedure ID mapped to the range of its opcode locations + /// This is created as artifacts are linked together and allows us to determine + /// which opcodes originate from reusable procedures.s + /// The range is inclusive for both start and end opcode locations. + pub(crate) procedure_locations: HashMap, } /// A pointer to a location in the opcode. @@ -149,6 +159,7 @@ impl BrilligArtifact { locations: self.locations, assert_messages: self.assert_messages, name: self.name, + procedure_locations: self.procedure_locations, } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs index 1c3d1f4d0a..e17b4ac935 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs @@ -13,8 +13,10 @@ use array_copy::compile_array_copy_procedure; use array_reverse::compile_array_reverse_procedure; use check_max_stack_depth::compile_check_max_stack_depth_procedure; use mem_copy::compile_mem_copy_procedure; +use noirc_errors::debug_info::ProcedureDebugId; use prepare_vector_insert::compile_prepare_vector_insert_procedure; use prepare_vector_push::compile_prepare_vector_push_procedure; +use serde::{Deserialize, Serialize}; use vector_copy::compile_vector_copy_procedure; use vector_pop_back::compile_vector_pop_back_procedure; use vector_pop_front::compile_vector_pop_front_procedure; @@ -31,8 +33,8 @@ use super::{ /// Procedures are a set of complex operations that are common in the noir language. /// Extracting them to reusable procedures allows us to reduce the size of the generated Brillig. /// Procedures receive their arguments on scratch space to avoid stack dumping&restoring. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) enum ProcedureId { +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)] +pub enum ProcedureId { ArrayCopy, ArrayReverse, VectorCopy, @@ -45,10 +47,63 @@ pub(crate) enum ProcedureId { CheckMaxStackDepth, } +impl ProcedureId { + pub(crate) fn to_debug_id(self) -> ProcedureDebugId { + ProcedureDebugId(match self { + ProcedureId::ArrayCopy => 0, + ProcedureId::ArrayReverse => 1, + ProcedureId::VectorCopy => 2, + ProcedureId::MemCopy => 3, + ProcedureId::PrepareVectorPush(true) => 4, + ProcedureId::PrepareVectorPush(false) => 5, + ProcedureId::VectorPopFront => 6, + ProcedureId::VectorPopBack => 7, + ProcedureId::PrepareVectorInsert => 8, + ProcedureId::VectorRemove => 9, + ProcedureId::CheckMaxStackDepth => 10, + }) + } + + pub fn from_debug_id(debug_id: ProcedureDebugId) -> Self { + let inner = debug_id.0; + match inner { + 0 => ProcedureId::ArrayCopy, + 1 => ProcedureId::ArrayReverse, + 2 => ProcedureId::VectorCopy, + 3 => ProcedureId::MemCopy, + 4 => ProcedureId::PrepareVectorPush(true), + 5 => ProcedureId::PrepareVectorPush(false), + 6 => ProcedureId::VectorPopFront, + 7 => ProcedureId::VectorPopBack, + 8 => ProcedureId::PrepareVectorInsert, + 9 => ProcedureId::VectorRemove, + 10 => ProcedureId::CheckMaxStackDepth, + _ => panic!("Unsupported procedure debug ID of {inner} was supplied"), + } + } +} + +impl std::fmt::Display for ProcedureId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProcedureId::ArrayCopy => write!(f, "ArrayCopy"), + ProcedureId::ArrayReverse => write!(f, "ArrayReverse"), + ProcedureId::VectorCopy => write!(f, "VectorCopy"), + ProcedureId::MemCopy => write!(f, "MemCopy"), + ProcedureId::PrepareVectorPush(flag) => write!(f, "PrepareVectorPush({flag})"), + ProcedureId::VectorPopFront => write!(f, "VectorPopFront"), + ProcedureId::VectorPopBack => write!(f, "VectorPopBack"), + ProcedureId::PrepareVectorInsert => write!(f, "PrepareVectorInsert"), + ProcedureId::VectorRemove => write!(f, "VectorRemove"), + ProcedureId::CheckMaxStackDepth => write!(f, "CheckMaxStackDepth"), + } + } +} + pub(crate) fn compile_procedure( procedure_id: ProcedureId, ) -> BrilligArtifact { - let mut brillig_context = BrilligContext::new_for_procedure(false); + let mut brillig_context = BrilligContext::new_for_procedure(false, procedure_id); brillig_context.enter_context(Label::procedure(procedure_id)); match procedure_id { diff --git a/compiler/noirc_evaluator/src/brillig/mod.rs b/compiler/noirc_evaluator/src/brillig/mod.rs index f1da76669c..1b61ae1a86 100644 --- a/compiler/noirc_evaluator/src/brillig/mod.rs +++ b/compiler/noirc_evaluator/src/brillig/mod.rs @@ -18,6 +18,8 @@ use crate::ssa::{ use fxhash::FxHashMap as HashMap; use std::{borrow::Cow, collections::BTreeSet}; +pub use self::brillig_ir::procedures::ProcedureId; + /// Context structure for the brillig pass. /// It stores brillig-related data required for brillig generation. #[derive(Default)] diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index ea41b0cfb3..c993ec291d 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -291,6 +291,7 @@ fn convert_generated_acir_into_circuit( assertion_payloads: assert_messages, warnings, name, + brillig_procedure_locs, .. } = generated_acir; @@ -328,8 +329,14 @@ fn convert_generated_acir_into_circuit( }) .collect(); - let mut debug_info = - DebugInfo::new(locations, brillig_locations, debug_variables, debug_functions, debug_types); + let mut debug_info = DebugInfo::new( + locations, + brillig_locations, + debug_variables, + debug_functions, + debug_types, + brillig_procedure_locs, + ); // Perform any ACIR-level optimizations let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 01fcaef904..9a12fbe244 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -20,7 +20,9 @@ use acvm::{ acir::AcirField, acir::{circuit::directives::Directive, native_types::Expression}, }; + use iter_extended::vecmap; +use noirc_errors::debug_info::ProcedureDebugId; use num_bigint::BigUint; /// Brillig calls such as for the Brillig std lib are resolved only after code generation is finished. @@ -72,6 +74,11 @@ pub(crate) struct GeneratedAcir { /// As to avoid passing the ACIR gen shared context into each individual ACIR /// we can instead keep this map and resolve the Brillig calls at the end of code generation. pub(crate) brillig_stdlib_func_locations: BTreeMap, + + /// Brillig function id -> Brillig procedure locations map + /// This maps allows a profiler to determine which Brillig opcodes + /// originated from a reusable procedure. + pub(crate) brillig_procedure_locs: BTreeMap, } /// Correspondence between an opcode index (in opcodes) and the source code call stack which generated it @@ -79,6 +86,8 @@ pub(crate) type OpcodeToLocationsMap = BTreeMap; pub(crate) type BrilligOpcodeToLocationsMap = BTreeMap; +pub(crate) type BrilligProcedureRangeMap = BTreeMap; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(crate) enum BrilligStdlibFunc { Inverse, @@ -591,6 +600,14 @@ impl GeneratedAcir { return; } + for (procedure_id, (start_index, end_index)) in generated_brillig.procedure_locations.iter() + { + self.brillig_procedure_locs + .entry(brillig_function_index) + .or_default() + .insert(procedure_id.to_debug_id(), (*start_index, *end_index)); + } + for (brillig_index, call_stack) in generated_brillig.locations.iter() { self.brillig_locations .entry(brillig_function_index) diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 50a895df23..ea44ffeb7f 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1003,6 +1003,15 @@ impl<'a> Context<'a> { } }; entry_point.link_with(artifact); + // Insert the range of opcode locations occupied by a procedure + if let Some(procedure_id) = artifact.procedure { + let num_opcodes = entry_point.byte_code.len(); + let previous_num_opcodes = entry_point.byte_code.len() - artifact.byte_code.len(); + // We subtract one as to keep the range inclusive on both ends + entry_point + .procedure_locations + .insert(procedure_id, (previous_num_opcodes, num_opcodes - 1)); + } } // Generate the final bytecode Ok(entry_point.finish()) diff --git a/tooling/debugger/src/source_code_printer.rs b/tooling/debugger/src/source_code_printer.rs index d1c82ad96b..b3682c9016 100644 --- a/tooling/debugger/src/source_code_printer.rs +++ b/tooling/debugger/src/source_code_printer.rs @@ -275,6 +275,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 0ad07c91ff..18fb407d41 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -192,7 +192,6 @@ fn compile_programs( let target_width = get_target_width(package.expression_width, compile_options.expression_width); let program = nargo::ops::transform_program(program, target_width); - save_program_to_file(&program.into(), &package.name, workspace.target_directory_path()); Ok(((), warnings)) diff --git a/tooling/noirc_artifacts/src/debug.rs b/tooling/noirc_artifacts/src/debug.rs index 8e2add70ae..5c47f1f265 100644 --- a/tooling/noirc_artifacts/src/debug.rs +++ b/tooling/noirc_artifacts/src/debug.rs @@ -264,6 +264,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); diff --git a/tooling/profiler/Cargo.toml b/tooling/profiler/Cargo.toml index c76b4c73e6..604208b5a5 100644 --- a/tooling/profiler/Cargo.toml +++ b/tooling/profiler/Cargo.toml @@ -33,6 +33,7 @@ acir.workspace = true nargo.workspace = true noirc_errors.workspace = true noirc_abi.workspace = true +noirc_evaluator.workspace = true # Logs tracing-subscriber.workspace = true diff --git a/tooling/profiler/src/flamegraph.rs b/tooling/profiler/src/flamegraph.rs index a72168a20a..7882ac903e 100644 --- a/tooling/profiler/src/flamegraph.rs +++ b/tooling/profiler/src/flamegraph.rs @@ -11,6 +11,7 @@ use inferno::flamegraph::{from_lines, Options, TextTruncateDirection}; use noirc_errors::debug_info::DebugInfo; use noirc_errors::reporter::line_and_column_from_span; use noirc_errors::Location; +use noirc_evaluator::brillig::ProcedureId; use crate::opcode_formatter::AcirOrBrilligOpcode; @@ -111,6 +112,7 @@ fn generate_folded_sorted_lines<'files, F: AcirField>( if let Some(opcode) = &sample.opcode { location_names.push(format_opcode(opcode)); } + add_locations_to_folded_stack_items(&mut folded_stack_items, location_names, sample.count); } @@ -123,10 +125,20 @@ fn find_callsite_labels<'files>( brillig_function_id: Option, files: &'files impl Files<'files, FileId = fm::FileId>, ) -> Vec { + let mut procedure_id = None; let source_locations = debug_symbols.opcode_location(opcode_location).unwrap_or_else(|| { if let (Some(brillig_function_id), Some(brillig_location)) = (brillig_function_id, opcode_location.to_brillig_location()) { + let procedure_locs = debug_symbols.brillig_procedure_locs.get(&brillig_function_id); + if let Some(procedure_locs) = procedure_locs { + for (procedure, range) in procedure_locs.iter() { + if brillig_location.0 >= range.0 && brillig_location.0 <= range.1 { + procedure_id = Some(*procedure); + break; + } + } + } let brillig_locations = debug_symbols.brillig_locations.get(&brillig_function_id); if let Some(brillig_locations) = brillig_locations { brillig_locations.get(&brillig_location).cloned().unwrap_or_default() @@ -137,10 +149,16 @@ fn find_callsite_labels<'files>( vec![] } }); - let callsite_labels: Vec<_> = source_locations + + let mut callsite_labels: Vec<_> = source_locations .into_iter() .map(|location| location_to_callsite_label(location, files)) .collect(); + + if let Some(procedure_id) = procedure_id { + callsite_labels.push(format!("procedure::{}", ProcedureId::from_debug_id(procedure_id))); + } + callsite_labels } @@ -317,6 +335,7 @@ mod tests { BTreeMap::default(), BTreeMap::default(), BTreeMap::default(), + BTreeMap::default(), ); let samples: Vec> = vec![