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 fa5f8d7c3fe..1b627adb3e4 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"; @@ -256,7 +259,10 @@ fn compile_contract_inner( } 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) } @@ -277,5 +283,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 cc48b7e6cbf..0574dfdf768 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<(CompiledContract, DebugArtifact)>, - )> = contract_packages + let program_results: Vec<(FileManager, CompilationResult)> = binary_packages .par_iter() .map(|package| { - compile_contract(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_contract(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<(CompiledContract, DebugArtifact)> = 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, contract_and_debug_artifact) in - contract_packages.into_iter().zip(compiled_contracts) - { - save_contract(contract_and_debug_artifact, package, &circuit_dir, args.output_debug); + for (package, compiled_contract) in contract_packages.into_iter().zip(compiled_contracts) { + save_contract(compiled_contract, 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<(CompiledContract, DebugArtifact), CliError> { +) -> Result { let (file_manager, compilation_result) = compile_contract(package, compile_options, np_language, &is_opcode_supported); let contract_and_debug_artifact = @@ -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_contract( @@ -181,7 +171,7 @@ fn compile_contract( compile_options: &CompileOptions, np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, -) -> (FileManager, CompilationResult<(CompiledContract, DebugArtifact)>) { +) -> (FileManager, CompilationResult) { let (mut context, crate_id) = prepare_package(package); let (contract, warnings) = match noirc_driver::compile_contract(&mut context, crate_id, compile_options) { @@ -195,14 +185,10 @@ fn compile_contract( nargo::ops::optimize_contract(contract, np_language, &is_opcode_supported) .expect("Backend does not support an opcode that is in the IR"); - let debug_infos = vecmap(&optimized_contract.functions, |func| func.debug.clone()); - let debug_artifact = DebugArtifact::new(debug_infos, &context.file_manager); - - (context.file_manager, Ok(((optimized_contract, debug_artifact), warnings))) + (context.file_manager, Ok((optimized_contract, warnings))) } fn save_program( - debug_artifact: DebugArtifact, program: CompiledProgram, package: &Package, circuit_dir: &Path, @@ -217,29 +203,33 @@ 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_contract( - contract: (CompiledContract, DebugArtifact), + contract: CompiledContract, package: &Package, circuit_dir: &Path, output_debug: bool, ) { - let (contract, debug_artifact) = contract; - // TODO(#1389): I wonder if it is incorrect for nargo-core to know anything about contracts. // As can be seen here, It seems like a leaky abstraction where ContractFunctions (essentially CompiledPrograms) // 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 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, function_type: func.function_type, is_internal: func.is_internal, abi: func.abi, - bytecode: func.bytecode, }); 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 6ddb7e9fea7..3359a61aa76 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()?; @@ -193,7 +193,7 @@ fn count_opcodes_and_gates_in_contracts( np_language: Language, is_opcode_supported: &impl Fn(&Opcode) -> bool, ) -> Result { - let (contract, _) = + let contract = compile_contract_package(package, compile_options, np_language, &is_opcode_supported)?; let (language, _) = backend.get_backend_info()?; 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 {