#![forbid(unsafe_code)]
#![warn(unused_crate_dependencies, unused_extern_crates)]
use std::hash::BuildHasher;
use abi_gen::{abi_type_from_hir_type, value_from_hir_expression};
use clap::Args;
use fm::{FileId, FileManager};
use iter_extended::vecmap;
use noirc_abi::{AbiParameter, AbiType, AbiValue};
use noirc_artifacts::contract::{CompiledContract, CompiledContractOutputs, ContractFunction};
use noirc_artifacts::debug::{DebugFile, DebugInfo, FunctionLocation};
use noirc_artifacts::program::CompiledProgram;
use noirc_artifacts::ssa::{InternalBug, InternalWarning, SsaReport};
use noirc_errors::{CustomDiagnostic, DiagnosticKind};
use noirc_evaluator::brillig::BrilligOptions;
use noirc_evaluator::brillig::brillig_ir::{
LayoutConfig, MAX_SCRATCH_SPACE, MAX_STACK_FRAME_SIZE, NUM_STACK_FRAMES,
};
use noirc_evaluator::create_program;
use noirc_evaluator::errors::RuntimeError;
use noirc_evaluator::ssa::opt::{
CONSTANT_FOLDING_MAX_ITER, DEFAULT_MAX_SPECIALIZATIONS_PER_FN,
DEFAULT_SPECIALIZATION_THRESHOLD, FORCE_UNROLL_THRESHOLD, INLINING_MAX_INSTRUCTIONS,
MAX_UNROLL_ITERATIONS,
};
use noirc_evaluator::ssa::{
SsaEvaluatorOptions, SsaLogging, SsaProgramArtifact, create_program_with_minimal_passes,
};
use noirc_frontend::debug::build_debug_crate_file;
use noirc_frontend::elaborator::{FrontendOptions, UnstableFeature};
use noirc_frontend::error_reporting::function_locations_in_parsed_module;
use noirc_frontend::hir::def_map::{CrateDefMap, ModuleDefId, ModuleId};
use noirc_frontend::hir::{Context, ParsedFiles};
use noirc_frontend::monomorphization::{
errors::MonomorphizationError, monomorphize, monomorphize_debug,
};
use noirc_frontend::node_interner::{FuncId, GlobalId, TypeId};
use noirc_frontend::token::SecondaryAttributeKind;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::{Path, PathBuf};
use tracing::info;
mod abi_gen;
mod stdlib;
pub use abi_gen::gen_abi;
pub use noirc_frontend::graph::{CrateId, CrateName};
pub use stdlib::{stdlib_nargo_toml_source, stdlib_paths_with_source};
const STD_CRATE_NAME: &str = "std";
const DEBUG_CRATE_NAME: &str = "__debug";
pub const GIT_COMMIT: &str = env!("GIT_COMMIT");
pub const GIT_DIRTY: &str = env!("GIT_DIRTY");
pub const NOIRC_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const NOIR_ARTIFACT_VERSION_STRING: &str =
concat!(env!("CARGO_PKG_VERSION"), "+", env!("GIT_COMMIT"));
#[derive(Args, Clone, Debug)]
pub struct CompileOptions {
#[arg(long = "force")]
pub force_compile: bool,
#[arg(long, hide = true)]
pub show_ssa: bool,
#[arg(long, hide = true)]
pub show_ssa_pass: Vec<String>,
#[arg(long, hide = true)]
pub hide_unchanged_ssa: bool,
#[arg(long, hide = true)]
pub with_ssa_locations: bool,
#[arg(long, hide = true)]
pub show_contract_fn: Option<String>,
#[arg(long, hide = true)]
pub skip_ssa_pass: Vec<String>,
#[arg(long, hide = true)]
pub emit_ssa: bool,
#[arg(long, hide = true)]
pub minimal_ssa: bool,
#[arg(long, hide = true)]
pub show_brillig: bool,
#[arg(long, hide = true)]
pub show_brillig_opcode_advisories: bool,
#[arg(long)]
pub print_acir: bool,
#[arg(long, hide = true)]
pub benchmark_codegen: bool,
#[arg(long, conflicts_with = "silence_warnings")]
pub deny_warnings: bool,
#[arg(long, conflicts_with = "deny_warnings")]
pub silence_warnings: bool,
#[arg(long, hide = true)]
pub show_monomorphized: bool,
#[arg(long, hide = true)]
pub instrument_debug: bool,
#[arg(long, hide = true)]
pub force_brillig: bool,
#[arg(long)]
pub debug_comptime_in_file: Option<String>,
#[arg(long, hide = true)]
pub show_artifact_paths: bool,
#[arg(long)]
pub skip_underconstrained_check: bool,
#[arg(long)]
pub skip_brillig_constraints_check: bool,
#[arg(long, hide = true)]
pub enable_brillig_debug_assertions: bool,
#[arg(long)]
pub count_array_copies: bool,
#[arg(long)]
pub enable_brillig_constraints_check_lookback: bool,
#[arg(long, allow_hyphen_values = true, default_value_t = i64::MAX)]
pub inliner_aggressiveness: i64,
#[arg(long, hide = true, allow_hyphen_values = true, default_value_t = CONSTANT_FOLDING_MAX_ITER)]
pub constant_folding_max_iter: usize,
#[arg(long, hide = true, allow_hyphen_values = true, default_value_t = INLINING_MAX_INSTRUCTIONS)]
pub small_function_max_instructions: usize,
#[arg(long, hide = true, allow_hyphen_values = true)]
pub max_bytecode_increase_percent: Option<i32>,
#[arg(long, hide = true, default_value_t = MAX_UNROLL_ITERATIONS)]
pub max_unroll_iterations: usize,
#[arg(long, hide = true, default_value_t = FORCE_UNROLL_THRESHOLD)]
pub force_unroll_threshold: usize,
#[arg(long, hide = true, default_value_t = DEFAULT_SPECIALIZATION_THRESHOLD)]
pub specialization_threshold: usize,
#[arg(long, hide = true, default_value_t = DEFAULT_MAX_SPECIALIZATIONS_PER_FN)]
pub max_specializations_per_fn: usize,
#[arg(long, hide = true, default_value_t = MAX_STACK_FRAME_SIZE)]
pub max_stack_frame_size: usize,
#[arg(long, hide = true, default_value_t = NUM_STACK_FRAMES)]
pub num_stack_frames: usize,
#[arg(long, hide = true, default_value_t = MAX_SCRATCH_SPACE)]
pub max_scratch_space: usize,
#[arg(long, hide = true)]
pub debug_compile_stdin: bool,
#[arg(value_parser = clap::value_parser!(UnstableFeature))]
#[clap(long, short = 'Z', value_delimiter = ',', conflicts_with = "no_unstable_features")]
pub unstable_features: Vec<UnstableFeature>,
#[arg(long, conflicts_with = "unstable_features")]
pub no_unstable_features: bool,
#[arg(long, hide = true)]
pub disable_comptime_printing: bool,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
force_compile: false,
show_ssa: false,
show_ssa_pass: Vec::new(),
hide_unchanged_ssa: false,
with_ssa_locations: false,
show_contract_fn: None,
skip_ssa_pass: Vec::new(),
emit_ssa: false,
minimal_ssa: false,
show_brillig: false,
show_brillig_opcode_advisories: false,
print_acir: false,
benchmark_codegen: false,
deny_warnings: false,
silence_warnings: false,
show_monomorphized: false,
instrument_debug: false,
force_brillig: false,
debug_comptime_in_file: None,
show_artifact_paths: false,
skip_underconstrained_check: false,
skip_brillig_constraints_check: false,
enable_brillig_debug_assertions: false,
count_array_copies: false,
enable_brillig_constraints_check_lookback: false,
inliner_aggressiveness: i64::MAX,
constant_folding_max_iter: CONSTANT_FOLDING_MAX_ITER,
small_function_max_instructions: INLINING_MAX_INSTRUCTIONS,
max_bytecode_increase_percent: None,
max_unroll_iterations: MAX_UNROLL_ITERATIONS,
force_unroll_threshold: FORCE_UNROLL_THRESHOLD,
specialization_threshold: DEFAULT_SPECIALIZATION_THRESHOLD,
max_specializations_per_fn: DEFAULT_MAX_SPECIALIZATIONS_PER_FN,
max_stack_frame_size: MAX_STACK_FRAME_SIZE,
num_stack_frames: NUM_STACK_FRAMES,
max_scratch_space: MAX_SCRATCH_SPACE,
debug_compile_stdin: false,
unstable_features: Vec::new(),
no_unstable_features: false,
disable_comptime_printing: false,
}
}
}
impl CompileOptions {
pub fn as_ssa_options(&self, package_build_path: PathBuf) -> SsaEvaluatorOptions {
SsaEvaluatorOptions {
ssa_logging: if !self.show_ssa_pass.is_empty() {
SsaLogging::Contains(self.show_ssa_pass.clone())
} else if self.show_ssa {
SsaLogging::All
} else {
SsaLogging::None
},
brillig_options: BrilligOptions {
enable_debug_trace: self.show_brillig,
enable_debug_assertions: self.enable_brillig_debug_assertions,
enable_array_copy_counter: self.count_array_copies,
show_opcode_advisories: self.show_brillig_opcode_advisories,
layout: LayoutConfig::new(
self.max_stack_frame_size,
self.num_stack_frames,
self.max_scratch_space,
),
},
print_codegen_timings: self.benchmark_codegen,
emit_ssa: if self.emit_ssa { Some(package_build_path) } else { None },
skip_underconstrained_check: !self.silence_warnings && self.skip_underconstrained_check,
enable_brillig_constraints_check_lookback: self
.enable_brillig_constraints_check_lookback,
skip_brillig_constraints_check: !self.silence_warnings
&& self.skip_brillig_constraints_check,
inliner_aggressiveness: self.inliner_aggressiveness,
constant_folding_max_iter: self.constant_folding_max_iter,
small_function_max_instruction: self.small_function_max_instructions,
max_bytecode_increase_percent: self.max_bytecode_increase_percent,
max_unroll_iterations: self.max_unroll_iterations,
force_unroll_threshold: self.force_unroll_threshold,
specialization_threshold: self.specialization_threshold,
max_specializations_per_fn: self.max_specializations_per_fn,
skip_passes: self.skip_ssa_pass.clone(),
ssa_logging_hide_unchanged: self.hide_unchanged_ssa,
}
}
}
impl CompileOptions {
pub(crate) fn frontend_options(&self) -> FrontendOptions {
FrontendOptions {
debug_comptime_in_file: self.debug_comptime_in_file.as_deref(),
enabled_unstable_features: &self.unstable_features,
disable_required_unstable_features: self.no_unstable_features,
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum CompileError {
MonomorphizationError(MonomorphizationError),
RuntimeError(RuntimeError),
}
impl From<MonomorphizationError> for CompileError {
fn from(error: MonomorphizationError) -> Self {
Self::MonomorphizationError(error)
}
}
impl From<RuntimeError> for CompileError {
fn from(error: RuntimeError) -> Self {
Self::RuntimeError(error)
}
}
impl From<CompileError> for CustomDiagnostic {
fn from(error: CompileError) -> CustomDiagnostic {
match error {
CompileError::RuntimeError(err) => err.into(),
CompileError::MonomorphizationError(err) => err.into(),
}
}
}
pub type Warnings = Vec<CustomDiagnostic>;
pub type ErrorsAndWarnings = Vec<CustomDiagnostic>;
pub type CompilationResult<T> = Result<(T, Warnings), ErrorsAndWarnings>;
pub fn file_manager_with_stdlib(root: &Path) -> FileManager {
let mut file_manager = FileManager::new(root);
add_stdlib_source_to_file_manager(&mut file_manager);
add_debug_source_to_file_manager(&mut file_manager);
file_manager
}
fn add_stdlib_source_to_file_manager(file_manager: &mut FileManager) {
let stdlib_paths_with_source = stdlib_paths_with_source();
for (path, source) in stdlib_paths_with_source {
file_manager.add_file_with_source_canonical_path(Path::new(&path), source);
}
}
fn add_debug_source_to_file_manager(file_manager: &mut FileManager) {
let path_to_debug_lib_file = Path::new(DEBUG_CRATE_NAME).join("lib.nr");
file_manager
.add_file_with_source_canonical_path(&path_to_debug_lib_file, build_debug_crate_file());
}
pub fn prepare_crate(context: &mut Context, file_name: &Path) -> CrateId {
let path_to_std_lib_file = Path::new(STD_CRATE_NAME).join("lib.nr");
let std_file_id = context.file_manager.name_to_id(path_to_std_lib_file);
let std_crate_id = std_file_id.map(|std_file_id| context.crate_graph.add_stdlib(std_file_id));
let root_file_id = context.file_manager.name_to_id(file_name.to_path_buf()).unwrap_or_else(|| panic!("files are expected to be added to the FileManager before reaching the compiler file_path: {}", file_name.display()));
if let Some(std_crate_id) = std_crate_id {
let root_crate_id = context.crate_graph.add_crate_root(root_file_id);
add_dep(context, root_crate_id, std_crate_id, STD_CRATE_NAME.parse().unwrap());
root_crate_id
} else {
context.crate_graph.add_crate_root_and_stdlib(root_file_id)
}
}
pub fn link_to_debug_crate(context: &mut Context, root_crate_id: CrateId) {
let path_to_debug_lib_file = Path::new(DEBUG_CRATE_NAME).join("lib.nr");
let debug_crate_id = prepare_dependency(context, &path_to_debug_lib_file);
add_dep(context, root_crate_id, debug_crate_id, DEBUG_CRATE_NAME.parse().unwrap());
context.debug_crate_id = Some(debug_crate_id);
}
pub fn prepare_dependency(context: &mut Context, file_name: &Path) -> CrateId {
let root_file_id = context
.file_manager
.name_to_id(file_name.to_path_buf())
.unwrap_or_else(|| panic!("files are expected to be added to the FileManager before reaching the compiler file_path: {}", file_name.display()));
let crate_id = context.crate_graph.add_crate(root_file_id);
let std_crate_id = context.stdlib_crate_id();
add_dep(context, crate_id, *std_crate_id, STD_CRATE_NAME.parse().unwrap());
crate_id
}
pub fn add_dep(
context: &mut Context,
this_crate: CrateId,
depends_on: CrateId,
crate_name: CrateName,
) {
context
.crate_graph
.add_dep(this_crate, crate_name, depends_on)
.expect("cyclic dependency triggered");
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn check_crate(
context: &mut Context,
crate_id: CrateId,
options: &CompileOptions,
) -> CompilationResult<()> {
if options.disable_comptime_printing {
context.disable_comptime_printing();
}
let diagnostics = CrateDefMap::collect_defs(crate_id, context, options.frontend_options());
let crate_files = context.crate_files(&crate_id);
let warnings_and_errors: Vec<CustomDiagnostic> = diagnostics
.iter()
.map(CustomDiagnostic::from)
.filter(|diagnostic| {
!options.silence_warnings || diagnostic.kind != DiagnosticKind::Warning
})
.filter(|error| {
if error.is_warning() { crate_files.contains(&error.file) } else { true }
})
.collect();
if has_errors(&warnings_and_errors, options.deny_warnings) {
Err(warnings_and_errors)
} else {
Ok(((), warnings_and_errors))
}
}
pub fn compute_function_abi(
context: &Context,
crate_id: &CrateId,
) -> Option<(Vec<AbiParameter>, Option<AbiType>)> {
let main_function = context.get_main_function(crate_id)?;
Some(abi_gen::compute_function_abi(context, &main_function))
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn compile_main(
context: &mut Context,
crate_id: CrateId,
options: &CompileOptions,
cached_program: Option<CompiledProgram>,
) -> CompilationResult<CompiledProgram> {
let (_, mut warnings) = check_crate(context, crate_id, options)?;
let main = context.get_main_function(&crate_id).ok_or_else(|| {
let err = CustomDiagnostic::from_message(
"cannot compile crate into a program as it does not contain a `main` function",
FileId::default(),
);
vec![err]
})?;
let compiled_program =
compile_no_check(context, options, main, cached_program, options.force_compile)
.map_err(|error| vec![CustomDiagnostic::from(error)])?;
let compilation_warnings =
vecmap(compiled_program.warnings.clone(), ssa_report_to_custom_diagnostic);
if options.deny_warnings && !compilation_warnings.is_empty() {
return Err(compilation_warnings);
}
if !options.silence_warnings {
warnings.extend(compilation_warnings);
}
if options.print_acir {
noirc_errors::println_to_stdout!("Compiled ACIR for main:");
noirc_errors::println_to_stdout!("{}", compiled_program.program);
}
Ok((compiled_program, warnings))
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn compile_contract(
context: &mut Context,
crate_id: CrateId,
options: &CompileOptions,
) -> CompilationResult<CompiledContract> {
let (_, warnings) = check_crate(context, crate_id, options)?;
let def_map = context.def_map(&crate_id).expect("The local crate should be analyzed already");
let mut contracts = def_map.get_all_contracts();
let Some((module_id, name)) = contracts.next() else {
let err = CustomDiagnostic::from_message(
"cannot compile crate into a contract as it does not contain any contracts",
FileId::default(),
);
return Err(vec![err]);
};
if contracts.next().is_some() {
let err = CustomDiagnostic::from_message(
"Packages are limited to a single contract",
FileId::default(),
);
return Err(vec![err]);
}
drop(contracts);
let module_id = ModuleId { krate: crate_id, local_id: module_id };
let contract = read_contract(context, module_id, name);
let mut errors = warnings;
let compiled_contract = match compile_contract_inner(context, contract, options) {
Ok(contract) => contract,
Err(mut more_errors) => {
errors.append(&mut more_errors);
return Err(errors);
}
};
if has_errors(&errors, options.deny_warnings) {
Err(errors)
} else {
if options.print_acir {
for contract_function in &compiled_contract.functions {
if let Some(ref name) = options.show_contract_fn
&& name != &contract_function.name
{
continue;
}
println!(
"Compiled ACIR for {}::{} (non-transformed):",
compiled_contract.name, contract_function.name
);
println!("{}", contract_function.bytecode);
}
}
Ok((compiled_contract, errors))
}
}
fn read_contract(context: &Context, module_id: ModuleId, name: String) -> Contract {
let module = context.module(module_id);
let functions: Vec<ContractFunctionMeta> = module
.value_definitions()
.filter_map(|id| {
id.as_function().map(|function_id| {
let attrs = context.def_interner.function_attributes(&function_id);
let is_entry_point = attrs.is_contract_entry_point();
ContractFunctionMeta { function_id, is_entry_point }
})
})
.collect();
let mut outputs = ContractOutputs { structs: HashMap::new(), globals: HashMap::new() };
context.def_interner.get_all_globals().iter().for_each(|global_info| {
context.def_interner.global_attributes(&global_info.id).iter().for_each(|attr| {
if let SecondaryAttributeKind::Abi(tag) = &attr.kind {
if let Some(tagged) = outputs.globals.get_mut(tag) {
tagged.push(global_info.id);
} else {
outputs.globals.insert(tag.clone(), vec![global_info.id]);
}
}
});
});
module.type_definitions().for_each(|id| {
if let ModuleDefId::TypeId(struct_id) = id {
context.def_interner.type_attributes(&struct_id).iter().for_each(|attr| {
if let SecondaryAttributeKind::Abi(tag) = &attr.kind {
if let Some(tagged) = outputs.structs.get_mut(tag) {
tagged.push(struct_id);
} else {
outputs.structs.insert(tag.clone(), vec![struct_id]);
}
}
});
}
});
Contract { name, functions, outputs }
}
fn has_errors(errors: &[CustomDiagnostic], deny_warnings: bool) -> bool {
if deny_warnings { !errors.is_empty() } else { errors.iter().any(|error| error.is_error()) }
}
fn compile_contract_inner(
context: &mut Context,
contract: Contract,
options: &CompileOptions,
) -> Result<CompiledContract, ErrorsAndWarnings> {
let mut functions = Vec::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
for contract_function in &contract.functions {
let function_id = contract_function.function_id;
let is_entry_point = contract_function.is_entry_point;
let name = context.function_name(&function_id).to_owned();
if !is_entry_point {
continue;
}
let mut options = options.clone();
if name == "public_dispatch" {
options.inliner_aggressiveness = 0;
}
if let Some(ref name_filter) = options.show_contract_fn {
let show = name == *name_filter;
options.show_ssa &= show;
if !show {
options.show_ssa_pass.clear();
}
}
let function = match compile_no_check(context, &options, function_id, None, true) {
Ok(function) => function,
Err(new_error) => {
errors.push(new_error.into());
continue;
}
};
warnings.extend(function.warnings);
let modifiers = context.def_interner.function_modifiers(&function_id);
let is_unconstrained = context.def_interner.function_meta(&function_id).is_unconstrained();
let custom_attributes = modifiers
.attributes
.secondary
.iter()
.filter_map(|attr| match &attr.kind {
SecondaryAttributeKind::Tag(contents) => Some(contents.clone()),
SecondaryAttributeKind::Meta(meta_attribute) => {
context.def_interner.get_meta_attribute_name(meta_attribute)
}
_ => None,
})
.collect();
functions.push(ContractFunction {
name,
hash: function.hash,
custom_attributes,
abi: function.abi,
bytecode: function.program,
debug: function.debug,
is_unconstrained,
});
}
if errors.is_empty() {
let debug_infos: Vec<_> =
functions.iter().flat_map(|function| function.debug.clone()).collect();
let file_map =
filter_relevant_files(&debug_infos, &context.file_manager, &context.parsed_files);
let out_structs = contract
.outputs
.structs
.into_iter()
.map(|(tag, structs)| {
let structs = structs
.into_iter()
.map(|struct_id| {
let typ = context.def_interner.get_type(struct_id);
let typ = typ.borrow();
let fields =
vecmap(typ.get_fields(&[]).unwrap_or_default(), |(name, typ, _)| {
(name, abi_type_from_hir_type(context, &typ))
});
let path =
context.fully_qualified_struct_path(context.root_crate_id(), typ.id);
AbiType::Struct { path, fields }
})
.collect();
(tag, structs)
})
.collect();
let out_globals = contract
.outputs
.globals
.iter()
.map(|(tag, globals)| {
let globals: Vec<AbiValue> = globals
.iter()
.map(|global_id| {
let let_statement =
context.def_interner.get_global_let_statement(*global_id).unwrap();
let hir_expression =
context.def_interner.expression(&let_statement.expression);
value_from_hir_expression(context, hir_expression)
})
.collect();
(tag.clone(), globals)
})
.collect();
Ok(CompiledContract {
name: contract.name,
functions,
outputs: CompiledContractOutputs { structs: out_structs, globals: out_globals },
file_map,
noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(),
warnings,
})
} else {
Err(errors)
}
}
pub fn filter_relevant_files(
debug_symbols: &[DebugInfo],
file_manager: &FileManager,
parsed_files: &ParsedFiles,
) -> BTreeMap<FileId, DebugFile> {
let mut files_with_debug_symbols: BTreeSet<FileId> = debug_symbols
.iter()
.flat_map(|function_symbols| {
function_symbols.acir_locations.values().flat_map(|call_stack_id| {
function_symbols
.location_tree
.get_call_stack(*call_stack_id)
.into_iter()
.map(|location| location.file)
})
})
.collect();
let files_with_brillig_debug_symbols: BTreeSet<FileId> = debug_symbols
.iter()
.flat_map(|function_symbols| {
function_symbols.brillig_locations.values().flat_map(|brillig_location_map| {
brillig_location_map.values().flat_map(|call_stack_id| {
function_symbols
.location_tree
.get_call_stack(*call_stack_id)
.into_iter()
.map(|location| location.file)
})
})
})
.collect();
files_with_debug_symbols.extend(files_with_brillig_debug_symbols);
let mut file_map = BTreeMap::new();
for file_id in files_with_debug_symbols {
let file_path = file_manager.path(file_id).expect("file should exist");
let file_source = file_manager.fetch_file(file_id).expect("file should exist");
let (parsed_module, _errors) = parsed_files.get(&file_id).expect("file should exist");
let include_comptime_items = false;
let mut function_locations =
function_locations_in_parsed_module(parsed_module, file_id, include_comptime_items);
let function_locations = function_locations
.all_in_file(file_id)
.map(|(name, span)| FunctionLocation { name: name.to_string(), start: span.start() })
.collect::<BTreeSet<_>>();
file_map.insert(
file_id,
DebugFile {
source: file_source.to_string(),
path: file_path.to_path_buf(),
function_locations,
},
);
}
file_map
}
#[tracing::instrument(level = "trace", skip_all, fields(function_name = context.function_name(&main_function)))]
#[allow(clippy::result_large_err)]
pub fn compile_no_check(
context: &mut Context,
options: &CompileOptions,
main_function: FuncId,
cached_program: Option<CompiledProgram>,
force_compile: bool,
) -> Result<CompiledProgram, CompileError> {
let force_unconstrained = options.force_brillig || options.minimal_ssa;
let program = if options.instrument_debug {
monomorphize_debug(
main_function,
&mut context.def_interner,
&context.debug_instrumenter,
context.debug_crate_id,
force_unconstrained,
)?
} else {
monomorphize(main_function, &mut context.def_interner, force_unconstrained)?
};
if options.show_monomorphized {
println!("{program}");
}
let force_compile = force_compile
|| options.print_acir
|| options.show_brillig
|| options.force_brillig
|| options.count_array_copies
|| options.show_ssa
|| !options.show_ssa_pass.is_empty()
|| options.emit_ssa
|| options.minimal_ssa;
let hash = rustc_hash::FxBuildHasher.hash_one(&program);
if let Some(cached_program) = cached_program
&& !force_compile
&& cached_program.hash == hash
{
info!("Program matches existing artifact, returning early");
return Ok(cached_program);
}
let return_visibility = program.return_visibility();
let ssa_evaluator_options = options.as_ssa_options(context.package_build_path.clone());
let SsaProgramArtifact { program, debug, warnings, error_types, .. } = if options.minimal_ssa {
create_program_with_minimal_passes(program, &ssa_evaluator_options, &context.file_manager)?
} else {
create_program(
program,
&ssa_evaluator_options,
if options.with_ssa_locations { Some(&context.file_manager) } else { None },
)?
};
let abi = gen_abi(context, &main_function, return_visibility, error_types);
let file_map = filter_relevant_files(&debug, &context.file_manager, &context.parsed_files);
Ok(CompiledProgram {
hash,
program,
debug,
abi,
file_map,
noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(),
warnings,
})
}
struct ContractFunctionMeta {
function_id: FuncId,
is_entry_point: bool,
}
struct ContractOutputs {
structs: HashMap<String, Vec<TypeId>>,
globals: HashMap<String, Vec<GlobalId>>,
}
struct Contract {
name: String,
functions: Vec<ContractFunctionMeta>,
outputs: ContractOutputs,
}
fn ssa_report_to_custom_diagnostic(error: SsaReport) -> CustomDiagnostic {
match error {
SsaReport::Warning(warning) => {
let message = warning.to_string();
let (secondary_message, call_stack) = match warning {
InternalWarning::ReturnConstant { call_stack } => {
("This variable contains a value which is constrained to be a constant. Consider removing this value as additional return values increase proving/verification time".to_string(), call_stack)
},
};
let call_stack = vecmap(call_stack, |location| location);
let location = call_stack.last().expect("Expected RuntimeError to have a location");
let diagnostic =
CustomDiagnostic::simple_warning(message, secondary_message, *location);
diagnostic.with_call_stack(call_stack)
}
SsaReport::Bug(bug) => {
let mut message = bug.to_string();
let (secondary_message, call_stack) = match bug {
InternalBug::IndependentSubgraph { call_stack } => {
("There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability".to_string(), call_stack)
}
InternalBug::UncheckedBrilligCall { call_stack } => {
("This Brillig call's inputs and its return values haven't been sufficiently constrained. This should be done to prevent potential soundness vulnerabilities".to_string(), call_stack)
}
InternalBug::AssertFailed { call_stack, message: assertion_failure_message } => {
if let Some(assertion_failure_message) = assertion_failure_message {
message.push_str(&format!(": {assertion_failure_message}"));
}
("As a result, the compiled circuit is ensured to fail. Other assertions may also fail during execution".to_string(), call_stack)
}
};
let call_stack = vecmap(call_stack, |location| location);
let location = call_stack.last().expect("Expected RuntimeError to have a location");
let diagnostic = CustomDiagnostic::simple_bug(message, secondary_message, *location);
diagnostic.with_call_stack(call_stack)
}
}
}