Skip to content

Commit

Permalink
chore: Embed a file map into CompiledProgram/CompiledContract (#2666
Browse files Browse the repository at this point in the history
)
  • Loading branch information
TomAFrench authored Sep 12, 2023
1 parent 16e5e4d commit 4bc42eb
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 71 deletions.
2 changes: 1 addition & 1 deletion compiler/noirc_driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ noirc_abi.workspace = true
acvm.workspace = true
fm.workspace = true
serde.workspace = true
base64.workspace = true
base64.workspace = true
11 changes: 9 additions & 2 deletions compiler/noirc_driver/src/contract.rs
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<ContractFunction>,

pub file_map: BTreeMap<FileId, DebugFile>,
}

/// Each function in the contract will be compiled
Expand Down
45 changes: 45 additions & 0 deletions compiler/noirc_driver/src/debug.rs
Original file line number Diff line number Diff line change
@@ -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<FileId, DebugFile> {
let files_with_debug_symbols: BTreeSet<FileId> = 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
}
12 changes: 10 additions & 2 deletions compiler/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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";
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 })
}
6 changes: 6 additions & 0 deletions compiler/noirc_driver/src/program.rs
Original file line number Diff line number Diff line change
@@ -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<FileId, DebugFile>,
}

pub(crate) fn serialize_circuit<S>(circuit: &Circuit, s: S) -> Result<S::Ok, S::Error>
Expand Down
10 changes: 1 addition & 9 deletions tooling/nargo/src/artifacts/debug.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
79 changes: 36 additions & 43 deletions tooling/nargo_cli/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,46 +72,40 @@ 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<Vec<(CompiledContract, DebugArtifact)>>,
)> = contract_packages
let program_results: Vec<(FileManager, CompilationResult<CompiledProgram>)> = 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<Vec<CompiledContract>>)> =
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<CompiledProgram> = program_results
.into_iter()
.map(|(file_manager, compilation_result)| {
report_errors(compilation_result, &file_manager, args.compile_options.deny_warnings)
})
.collect::<Result<_, _>>()?;
let compiled_contracts: Vec<Vec<(CompiledContract, DebugArtifact)>> = contract_results
let compiled_contracts: Vec<Vec<CompiledContract>> = contract_results
.into_iter()
.map(|(file_manager, compilation_result)| {
report_errors(compilation_result, &file_manager, args.compile_options.deny_warnings)
})
.collect::<Result<_, _>>()?;

// 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(())
Expand All @@ -122,26 +116,25 @@ pub(crate) fn compile_bin_package(
compile_options: &CompileOptions,
np_language: Language,
is_opcode_supported: &impl Fn(&Opcode) -> bool,
) -> Result<(CompiledProgram, DebugArtifact), CliError> {
) -> Result<CompiledProgram, CliError> {
if package.is_library() {
return Err(CompileError::LibraryCrate(package.name.clone()).into());
}

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(
package: &Package,
compile_options: &CompileOptions,
np_language: Language,
is_opcode_supported: &impl Fn(&Opcode) -> bool,
) -> Result<Vec<(CompiledContract, DebugArtifact)>, CliError> {
) -> Result<Vec<CompiledContract>, CliError> {
let (file_manager, compilation_result) =
compile_contracts(package, compile_options, np_language, &is_opcode_supported);
let contracts_with_debug_artifacts =
Expand All @@ -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<CompiledProgram>) {
let (mut context, crate_id) = prepare_package(package);

let (program, warnings) =
Expand All @@ -170,18 +163,15 @@ 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(
package: &Package,
compile_options: &CompileOptions,
np_language: Language,
is_opcode_supported: &impl Fn(&Opcode) -> bool,
) -> (FileManager, CompilationResult<Vec<(CompiledContract, DebugArtifact)>>) {
) -> (FileManager, CompilationResult<Vec<CompiledContract>>) {
let (mut context, crate_id) = prepare_package(package);
let (contracts, warnings) =
match noirc_driver::compile_contracts(&mut context, crate_id, compile_options) {
Expand All @@ -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,
Expand All @@ -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<CompiledContract>,
package: &Package,
circuit_dir: &Path,
output_debug: bool,
Expand All @@ -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,
Expand Down
14 changes: 5 additions & 9 deletions tooling/nargo_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<InputValue>, 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, _) =
Expand Down
Loading

0 comments on commit 4bc42eb

Please sign in to comment.