diff --git a/compiler/noirc_driver/Cargo.toml b/compiler/noirc_driver/Cargo.toml index 2afc7a4cb53..bd38371f2ad 100644 --- a/compiler/noirc_driver/Cargo.toml +++ b/compiler/noirc_driver/Cargo.toml @@ -15,4 +15,4 @@ noirc_abi.workspace = true acvm.workspace = true fm.workspace = true serde.workspace = true -base64.workspace = true \ No newline at end of file +base64.workspace = true diff --git a/compiler/noirc_driver/src/contract.rs b/compiler/noirc_driver/src/contract.rs index a1820ff2e47..69a92764318 100644 --- a/compiler/noirc_driver/src/contract.rs +++ b/compiler/noirc_driver/src/contract.rs @@ -1,8 +1,13 @@ -use crate::program::{deserialize_circuit, serialize_circuit}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + use acvm::acir::circuit::Circuit; +use fm::FileId; use noirc_abi::Abi; use noirc_errors::debug_info::DebugInfo; -use serde::{Deserialize, Serialize}; + +use super::debug::DebugFile; +use crate::program::{deserialize_circuit, serialize_circuit}; /// Describes the types of smart contract functions that are allowed. /// Unlike the similar enum in noirc_frontend, 'open' and 'unconstrained' @@ -28,6 +33,8 @@ pub struct CompiledContract { /// Each of the contract's functions are compiled into a separate `CompiledProgram` /// stored in this `Vector`. pub functions: Vec, + + pub file_map: BTreeMap, } /// Each function in the contract will be compiled diff --git a/compiler/noirc_driver/src/debug.rs b/compiler/noirc_driver/src/debug.rs new file mode 100644 index 00000000000..9808c9b54a2 --- /dev/null +++ b/compiler/noirc_driver/src/debug.rs @@ -0,0 +1,45 @@ +use fm::{FileId, FileManager}; +use noirc_errors::debug_info::DebugInfo; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::PathBuf, +}; + +/// For a given file, we store the source code and the path to the file +/// so consumers of the debug artifact can reconstruct the original source code structure. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DebugFile { + pub source: String, + pub path: PathBuf, +} + +pub(crate) fn filter_relevant_files( + debug_symbols: &[DebugInfo], + file_manager: &FileManager, +) -> BTreeMap { + let files_with_debug_symbols: BTreeSet = debug_symbols + .iter() + .flat_map(|function_symbols| { + function_symbols + .locations + .values() + .filter_map(|call_stack| call_stack.last().map(|location| location.file)) + }) + .collect(); + + let mut file_map = BTreeMap::new(); + + for file_id in files_with_debug_symbols { + let file_source = file_manager.fetch_file(file_id).source(); + + file_map.insert( + file_id, + DebugFile { + source: file_source.to_string(), + path: file_manager.path(file_id).to_path_buf(), + }, + ); + } + file_map +} diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index a608879ce77..ec569e0e182 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -4,6 +4,7 @@ #![warn(clippy::semicolon_if_nothing_returned)] use clap::Args; +use debug::filter_relevant_files; use fm::FileId; use noirc_abi::{AbiParameter, AbiType}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; @@ -17,9 +18,11 @@ use serde::{Deserialize, Serialize}; use std::path::Path; mod contract; +mod debug; mod program; pub use contract::{CompiledContract, ContractFunction, ContractFunctionType}; +pub use debug::DebugFile; pub use program::CompiledProgram; const STD_CRATE_NAME: &str = "std"; @@ -248,7 +251,10 @@ fn compile_contract( } if errors.is_empty() { - Ok(CompiledContract { name: contract.name, functions }) + let debug_infos: Vec<_> = functions.iter().map(|function| function.debug.clone()).collect(); + let file_map = filter_relevant_files(&debug_infos, &context.file_manager); + + Ok(CompiledContract { name: contract.name, functions, file_map }) } else { Err(errors) } @@ -269,5 +275,7 @@ pub fn compile_no_check( let (circuit, debug, abi) = create_circuit(context, program, options.show_ssa, options.show_brillig)?; - Ok(CompiledProgram { circuit, debug, abi }) + let file_map = filter_relevant_files(&[debug.clone()], &context.file_manager); + + Ok(CompiledProgram { circuit, debug, abi, file_map }) } diff --git a/compiler/noirc_driver/src/program.rs b/compiler/noirc_driver/src/program.rs index 9323f90d522..1ed2b0ddddc 100644 --- a/compiler/noirc_driver/src/program.rs +++ b/compiler/noirc_driver/src/program.rs @@ -1,15 +1,21 @@ +use std::collections::BTreeMap; + use acvm::acir::circuit::Circuit; +use fm::FileId; use base64::Engine; use noirc_errors::debug_info::DebugInfo; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use super::debug::DebugFile; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CompiledProgram { #[serde(serialize_with = "serialize_circuit", deserialize_with = "deserialize_circuit")] pub circuit: Circuit, pub abi: noirc_abi::Abi, pub debug: DebugInfo, + pub file_map: BTreeMap, } pub(crate) fn serialize_circuit(circuit: &Circuit, s: S) -> Result diff --git a/tooling/nargo/src/artifacts/debug.rs b/tooling/nargo/src/artifacts/debug.rs index 2a201a82c48..3c173f34876 100644 --- a/tooling/nargo/src/artifacts/debug.rs +++ b/tooling/nargo/src/artifacts/debug.rs @@ -1,22 +1,14 @@ use codespan_reporting::files::{Error, Files, SimpleFile}; +use noirc_driver::DebugFile; use noirc_errors::debug_info::DebugInfo; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, ops::Range, - path::PathBuf, }; use fm::{FileId, FileManager, PathString}; -/// For a given file, we store the source code and the path to the file -/// so consumers of the debug artifact can reconstruct the original source code structure. -#[derive(Debug, Serialize, Deserialize)] -pub struct DebugFile { - pub source: String, - pub path: PathBuf, -} - /// A Debug Artifact stores, for a given program, the debug info for every function /// along with a map of file Id to the source code so locations in debug info can be mapped to source code they point to. #[derive(Debug, Serialize, Deserialize)] diff --git a/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs b/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs index 6199bf0761d..16ff311f704 100644 --- a/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -81,7 +81,7 @@ fn smart_contract_for_package( let preprocessed_program = if circuit_build_path.exists() { read_program_from_file(circuit_build_path)? } else { - let (program, _) = + let program = compile_bin_package(package, compile_options, np_language, &is_opcode_supported)?; PreprocessedProgram { diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 61d3b64a47a..d979cfe5efd 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -72,32 +72,28 @@ pub(crate) fn run( .partition(|package| package.is_binary()); // Compile all of the packages in parallel. - let program_results: Vec<(FileManager, CompilationResult<(CompiledProgram, DebugArtifact)>)> = - binary_packages - .par_iter() - .map(|package| { - compile_program(package, &args.compile_options, np_language, &is_opcode_supported) - }) - .collect(); - #[allow(clippy::type_complexity)] - let contract_results: Vec<( - FileManager, - CompilationResult>, - )> = contract_packages + let program_results: Vec<(FileManager, CompilationResult)> = binary_packages .par_iter() .map(|package| { - compile_contracts(package, &args.compile_options, np_language, &is_opcode_supported) + compile_program(package, &args.compile_options, np_language, &is_opcode_supported) }) .collect(); + let contract_results: Vec<(FileManager, CompilationResult>)> = + contract_packages + .par_iter() + .map(|package| { + compile_contracts(package, &args.compile_options, np_language, &is_opcode_supported) + }) + .collect(); // Report any warnings/errors which were encountered during compilation. - let compiled_programs: Vec<(CompiledProgram, DebugArtifact)> = program_results + let compiled_programs: Vec = program_results .into_iter() .map(|(file_manager, compilation_result)| { report_errors(compilation_result, &file_manager, args.compile_options.deny_warnings) }) .collect::>()?; - let compiled_contracts: Vec> = contract_results + let compiled_contracts: Vec> = contract_results .into_iter() .map(|(file_manager, compilation_result)| { report_errors(compilation_result, &file_manager, args.compile_options.deny_warnings) @@ -105,13 +101,11 @@ pub(crate) fn run( .collect::>()?; // Save build artifacts to disk. - for (package, (program, debug_artifact)) in binary_packages.into_iter().zip(compiled_programs) { - save_program(debug_artifact, program, package, &circuit_dir, args.output_debug); + for (package, program) in binary_packages.into_iter().zip(compiled_programs) { + save_program(program, package, &circuit_dir, args.output_debug); } - for (package, contracts_with_debug_artifacts) in - contract_packages.into_iter().zip(compiled_contracts) - { - save_contracts(contracts_with_debug_artifacts, package, &circuit_dir, args.output_debug); + for (package, compiled_contracts) in contract_packages.into_iter().zip(compiled_contracts) { + save_contracts(compiled_contracts, package, &circuit_dir, args.output_debug); } Ok(()) @@ -122,7 +116,7 @@ pub(crate) fn compile_bin_package( compile_options: &CompileOptions, np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, -) -> Result<(CompiledProgram, DebugArtifact), CliError> { +) -> Result { if package.is_library() { return Err(CompileError::LibraryCrate(package.name.clone()).into()); } @@ -130,10 +124,9 @@ pub(crate) fn compile_bin_package( let (file_manager, compilation_result) = compile_program(package, compile_options, np_language, &is_opcode_supported); - let (program, debug_artifact) = - report_errors(compilation_result, &file_manager, compile_options.deny_warnings)?; + let program = report_errors(compilation_result, &file_manager, compile_options.deny_warnings)?; - Ok((program, debug_artifact)) + Ok(program) } pub(crate) fn compile_contract_package( @@ -141,7 +134,7 @@ pub(crate) fn compile_contract_package( compile_options: &CompileOptions, np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, -) -> Result, CliError> { +) -> Result, CliError> { let (file_manager, compilation_result) = compile_contracts(package, compile_options, np_language, &is_opcode_supported); let contracts_with_debug_artifacts = @@ -154,7 +147,7 @@ fn compile_program( compile_options: &CompileOptions, np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, -) -> (FileManager, CompilationResult<(CompiledProgram, DebugArtifact)>) { +) -> (FileManager, CompilationResult) { let (mut context, crate_id) = prepare_package(package); let (program, warnings) = @@ -170,10 +163,7 @@ fn compile_program( nargo::ops::optimize_program(program, np_language, &is_opcode_supported) .expect("Backend does not support an opcode that is in the IR"); - let debug_artifact = - DebugArtifact::new(vec![optimized_program.debug.clone()], &context.file_manager); - - (context.file_manager, Ok(((optimized_program, debug_artifact), warnings))) + (context.file_manager, Ok((optimized_program, warnings))) } fn compile_contracts( @@ -181,7 +171,7 @@ fn compile_contracts( compile_options: &CompileOptions, np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, -) -> (FileManager, CompilationResult>) { +) -> (FileManager, CompilationResult>) { let (mut context, crate_id) = prepare_package(package); let (contracts, warnings) = match noirc_driver::compile_contracts(&mut context, crate_id, compile_options) { @@ -196,18 +186,10 @@ fn compile_contracts( }) .expect("Backend does not support an opcode that is in the IR"); - let contracts_with_debug_artifacts = vecmap(optimized_contracts, |contract| { - let debug_infos = vecmap(&contract.functions, |func| func.debug.clone()); - let debug_artifact = DebugArtifact::new(debug_infos, &context.file_manager); - - (contract, debug_artifact) - }); - - (context.file_manager, Ok((contracts_with_debug_artifacts, warnings))) + (context.file_manager, Ok((optimized_contracts, warnings))) } fn save_program( - debug_artifact: DebugArtifact, program: CompiledProgram, package: &Package, circuit_dir: &Path, @@ -222,13 +204,15 @@ fn save_program( save_program_to_file(&preprocessed_program, &package.name, circuit_dir); if output_debug { + let debug_artifact = + DebugArtifact { debug_symbols: vec![program.debug], file_map: program.file_map }; let circuit_name: String = (&package.name).into(); save_debug_artifact_to_file(&debug_artifact, &circuit_name, circuit_dir); } } fn save_contracts( - contracts: Vec<(CompiledContract, DebugArtifact)>, + contracts: Vec, package: &Package, circuit_dir: &Path, output_debug: bool, @@ -238,7 +222,16 @@ fn save_contracts( // are compiled via nargo-core and then the PreprocessedContract is constructed here. // This is due to EACH function needing it's own CRS, PKey, and VKey from the backend. let preprocessed_contracts: Vec<(PreprocessedContract, DebugArtifact)> = - vecmap(contracts, |(contract, debug_artifact)| { + vecmap(contracts, |contract| { + let debug_artifact = DebugArtifact { + debug_symbols: contract + .functions + .iter() + .map(|function| function.debug.clone()) + .collect(), + file_map: contract.file_map, + }; + let preprocessed_functions = vecmap(contract.functions, |func| PreprocessedContractFunction { name: func.name, diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index a08cfb09995..8c434f8fe21 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -56,15 +56,11 @@ pub(crate) fn run( let (np_language, is_opcode_supported) = backend.get_backend_info()?; for package in &workspace { - let (compiled_program, debug_artifact) = + let compiled_program = compile_bin_package(package, &args.compile_options, np_language, &is_opcode_supported)?; - let (return_value, solved_witness) = execute_program_and_decode( - compiled_program, - debug_artifact, - package, - &args.prover_name, - )?; + let (return_value, solved_witness) = + execute_program_and_decode(compiled_program, package, &args.prover_name)?; println!("[{}] Circuit witness successfully solved", package.name); if let Some(return_value) = return_value { @@ -81,11 +77,11 @@ pub(crate) fn run( fn execute_program_and_decode( program: CompiledProgram, - debug_artifact: DebugArtifact, package: &Package, prover_name: &str, ) -> Result<(Option, WitnessMap), CliError> { - let CompiledProgram { abi, circuit, .. } = program; + let CompiledProgram { abi, circuit, debug, file_map } = program; + let debug_artifact = DebugArtifact { debug_symbols: vec![debug], file_map }; // Parse the initial witness values from Prover.toml let (inputs_map, _) = diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index e51a0256426..49ae1327c5a 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -174,7 +174,7 @@ fn count_opcodes_and_gates_in_program( np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, ) -> Result { - let (compiled_program, _) = + let compiled_program = compile_bin_package(package, compile_options, np_language, &is_opcode_supported)?; let (language, _) = backend.get_backend_info()?; @@ -197,7 +197,7 @@ fn count_opcodes_and_gates_in_contracts( compile_contract_package(package, compile_options, np_language, &is_opcode_supported)?; let (language, _) = backend.get_backend_info()?; - try_vecmap(contracts, |(contract, _)| { + try_vecmap(contracts, |contract| { let functions = try_vecmap(contract.functions, |function| -> Result<_, BackendError> { Ok(FunctionInfo { name: function.name, diff --git a/tooling/nargo_cli/src/cli/prove_cmd.rs b/tooling/nargo_cli/src/cli/prove_cmd.rs index c451b78add5..03146d3919c 100644 --- a/tooling/nargo_cli/src/cli/prove_cmd.rs +++ b/tooling/nargo_cli/src/cli/prove_cmd.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use acvm::acir::circuit::Opcode; use acvm::Language; use clap::Args; +use nargo::artifacts::debug::DebugArtifact; use nargo::artifacts::program::PreprocessedProgram; use nargo::constants::{PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}; use nargo::package::Package; @@ -101,13 +102,16 @@ pub(crate) fn prove_package( (program, None) } else { - let (program, debug_artifact) = + let program = compile_bin_package(package, compile_options, np_language, &is_opcode_supported)?; let preprocessed_program = PreprocessedProgram { backend: String::from(BACKEND_IDENTIFIER), abi: program.abi, bytecode: program.circuit, }; + let debug_artifact = + DebugArtifact { debug_symbols: vec![program.debug], file_map: program.file_map }; + (preprocessed_program, Some(debug_artifact)) }; diff --git a/tooling/nargo_cli/src/cli/verify_cmd.rs b/tooling/nargo_cli/src/cli/verify_cmd.rs index 9d1a98da4da..452d58ff667 100644 --- a/tooling/nargo_cli/src/cli/verify_cmd.rs +++ b/tooling/nargo_cli/src/cli/verify_cmd.rs @@ -85,7 +85,7 @@ fn verify_package( let preprocessed_program = if circuit_build_path.exists() { read_program_from_file(circuit_build_path)? } else { - let (program, _) = + let program = compile_bin_package(package, compile_options, np_language, &is_opcode_supported)?; PreprocessedProgram {