From 196299f92674a21f4607ccfa6629f224d801f7fb Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Fri, 30 Jun 2023 16:18:28 -0700 Subject: [PATCH] feat(driver): Remove Driver struct and refactor functions to take context --- crates/lsp/src/lib.rs | 81 ++- crates/nargo_cli/src/cli/check_cmd.rs | 17 +- crates/nargo_cli/src/cli/compile_cmd.rs | 27 +- crates/nargo_cli/src/cli/mod.rs | 12 +- crates/nargo_cli/src/cli/test_cmd.rs | 28 +- crates/nargo_cli/src/resolver.rs | 180 +++--- crates/noirc_driver/src/lib.rs | 563 ++++++++---------- crates/noirc_driver/src/main.rs | 37 +- .../src/hir/def_collector/dc_crate.rs | 2 +- crates/noirc_frontend/src/hir/def_map/mod.rs | 2 +- crates/noirc_frontend/src/hir/mod.rs | 60 +- crates/noirc_frontend/src/main.rs | 2 +- crates/wasm/src/compile.rs | 66 +- 13 files changed, 539 insertions(+), 538 deletions(-) diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 820638baba6..f4b797a11c5 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -2,7 +2,7 @@ use std::{ future::Future, ops::{self, ControlFlow}, pin::Pin, - task::{Context, Poll}, + task::{self, Poll}, }; use async_lsp::{ @@ -17,9 +17,9 @@ use lsp_types::{ InitializeParams, InitializeResult, InitializedParams, Position, PublishDiagnosticsParams, Range, ServerCapabilities, TextDocumentSyncOptions, }; -use noirc_driver::Driver; +use noirc_driver::{check_crate, create_local_crate}; use noirc_errors::{DiagnosticKind, FileDiagnostic}; -use noirc_frontend::graph::CrateType; +use noirc_frontend::{graph::CrateType, hir::Context}; use serde_json::Value as JsonValue; use tower::Service; @@ -27,14 +27,15 @@ const TEST_COMMAND: &str = "nargo.test"; const TEST_CODELENS_TITLE: &str = "▶\u{fe0e} Run Test"; // State for the LSP gets implemented on this struct and is internal to the implementation -#[derive(Debug)] struct LspState { + context: Context, client: ClientSocket, } impl LspState { fn new(client: &ClientSocket) -> Self { - Self { client: client.clone() } + // TODO: Do we want to build the Context here or when we get the initialize message? + Self { client: client.clone(), context: Context::default() } } } @@ -68,7 +69,7 @@ impl Service for NargoLspService { type Error = ResponseError; type Future = Pin> + Send>>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { self.router.poll_ready(cx) } @@ -129,49 +130,47 @@ fn on_shutdown( } fn on_code_lens_request( - _state: &mut LspState, + state: &mut LspState, params: CodeLensParams, ) -> impl Future>, ResponseError>> { - async move { - let mut driver = Driver::new(); + let file_path = ¶ms.text_document.uri.to_file_path().unwrap(); - let file_path = ¶ms.text_document.uri.to_file_path().unwrap(); + let crate_id = create_local_crate(&mut state.context, file_path, CrateType::Binary); - driver.create_local_crate(file_path, CrateType::Binary); + // We ignore the warnings and errors produced by compilation for producing codelenses + // because we can still get the test functions even if compilation fails + let _ = check_crate(&mut state.context, false); - // We ignore the warnings and errors produced by compilation for producing codelenses - // because we can still get the test functions even if compilation fails - let _ = driver.check_crate(false); + let fm = &state.context.file_manager; + let files = fm.as_simple_files(); + let tests = state.context.get_all_test_functions_in_crate_matching(&crate_id, ""); - let fm = driver.file_manager(); - let files = fm.as_simple_files(); - let tests = driver.get_all_test_functions_in_crate_matching(""); - - let mut lenses: Vec = vec![]; - for func_id in tests { - let location = driver.function_meta(&func_id).name.location; - let file_id = location.file; - // TODO(#1681): This file_id never be 0 because the "path" where it maps is the directory, not a file - if file_id.as_usize() != 0 { - continue; - } + let mut lenses: Vec = vec![]; + for func_id in tests { + let location = state.context.function_meta(&func_id).name.location; + let file_id = location.file; + // TODO(#1681): This file_id never be 0 because the "path" where it maps is the directory, not a file + if file_id.as_usize() != 0 { + continue; + } - let func_name = driver.function_name(func_id); + let func_name = state.context.function_name(&func_id); - let range = byte_span_to_range(files, file_id.as_usize(), location.span.into()) - .unwrap_or_default(); + let range = + byte_span_to_range(files, file_id.as_usize(), location.span.into()).unwrap_or_default(); - let command = Command { - title: TEST_CODELENS_TITLE.into(), - command: TEST_COMMAND.into(), - arguments: Some(vec![func_name.into()]), - }; + let command = Command { + title: TEST_CODELENS_TITLE.into(), + command: TEST_COMMAND.into(), + arguments: Some(vec![func_name.into()]), + }; - let lens = CodeLens { range, command: command.into(), data: None }; + let lens = CodeLens { range, command: command.into(), data: None }; - lenses.push(lens); - } + lenses.push(lens); + } + async move { if lenses.is_empty() { Ok(None) } else { @@ -219,21 +218,19 @@ fn on_did_save_text_document( state: &mut LspState, params: DidSaveTextDocumentParams, ) -> ControlFlow> { - let mut driver = Driver::new(); - let file_path = ¶ms.text_document.uri.to_file_path().unwrap(); - driver.create_local_crate(file_path, CrateType::Binary); + create_local_crate(&mut state.context, file_path, CrateType::Binary); let mut diagnostics = Vec::new(); - let file_diagnostics = match driver.check_crate(false) { + let file_diagnostics = match check_crate(&mut state.context, false) { Ok(warnings) => warnings, Err(errors_and_warnings) => errors_and_warnings, }; if !file_diagnostics.is_empty() { - let fm = driver.file_manager(); + let fm = &state.context.file_manager; let files = fm.as_simple_files(); for FileDiagnostic { file_id, diagnostic } in file_diagnostics { diff --git a/crates/nargo_cli/src/cli/check_cmd.rs b/crates/nargo_cli/src/cli/check_cmd.rs index 1702742fc8c..89f4cc5764f 100644 --- a/crates/nargo_cli/src/cli/check_cmd.rs +++ b/crates/nargo_cli/src/cli/check_cmd.rs @@ -1,10 +1,11 @@ -use crate::{errors::CliError, resolver::Resolver}; +use crate::{errors::CliError, resolver::resolve_root_manifest}; use acvm::Backend; use clap::Args; use iter_extended::btree_map; use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME}; -use noirc_driver::CompileOptions; +use noirc_driver::{check_crate, compute_function_signature, CompileOptions}; use noirc_errors::reporter::ReportedErrors; +use noirc_frontend::hir::Context; use std::path::{Path, PathBuf}; use super::fs::write_to_file; @@ -35,11 +36,11 @@ fn check_from_path( program_dir: &Path, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let mut driver = Resolver::resolve_root_manifest(program_dir)?; - check_crate_and_report_errors(&mut driver, compile_options.deny_warnings)?; + let mut context = resolve_root_manifest(program_dir)?; + check_crate_and_report_errors(&mut context, compile_options.deny_warnings)?; // XXX: We can have a --overwrite flag to determine if you want to overwrite the Prover/Verifier.toml files - if let Some((parameters, return_type)) = driver.compute_function_signature() { + if let Some((parameters, return_type)) = compute_function_signature(&context) { // XXX: The root config should return an enum to determine if we are looking for .json or .toml // For now it is hard-coded to be toml. // @@ -211,9 +212,9 @@ d2 = ["", "", ""] /// Run the lexing, parsing, name resolution, and type checking passes and report any warnings /// and errors found. pub(crate) fn check_crate_and_report_errors( - driver: &mut noirc_driver::Driver, + context: &mut Context, deny_warnings: bool, ) -> Result<(), ReportedErrors> { - let result = driver.check_crate(deny_warnings).map(|warnings| ((), warnings)); - super::compile_cmd::report_errors(result, driver, deny_warnings) + let result = check_crate(context, deny_warnings).map(|warnings| ((), warnings)); + super::compile_cmd::report_errors(result, context, deny_warnings) } diff --git a/crates/nargo_cli/src/cli/compile_cmd.rs b/crates/nargo_cli/src/cli/compile_cmd.rs index 9c732396988..64ee7bd44ee 100644 --- a/crates/nargo_cli/src/cli/compile_cmd.rs +++ b/crates/nargo_cli/src/cli/compile_cmd.rs @@ -1,15 +1,18 @@ use acvm::Backend; use iter_extended::try_vecmap; use nargo::artifacts::contract::PreprocessedContract; -use noirc_driver::{CompileOptions, CompiledProgram, Driver, ErrorsAndWarnings, Warnings}; +use noirc_driver::{ + compile_contracts, compile_main, CompileOptions, CompiledProgram, ErrorsAndWarnings, Warnings, +}; use noirc_errors::reporter::ReportedErrors; +use noirc_frontend::hir::Context; use std::path::Path; use clap::Args; use nargo::ops::{preprocess_contract_function, preprocess_program}; -use crate::{constants::TARGET_DIR, errors::CliError, resolver::Resolver}; +use crate::{constants::TARGET_DIR, errors::CliError, resolver::resolve_root_manifest}; use super::fs::{ common_reference_string::{ @@ -48,14 +51,15 @@ pub(crate) fn run( // If contracts is set we're compiling every function in a 'contract' rather than just 'main'. if args.contracts { - let mut driver = Resolver::resolve_root_manifest(&config.program_dir)?; + let mut context = resolve_root_manifest(&config.program_dir)?; - let result = driver.compile_contracts( + let result = compile_contracts( + &mut context, backend.np_language(), &|op| backend.supports_opcode(op), &args.compile_options, ); - let contracts = report_errors(result, &driver, args.compile_options.deny_warnings)?; + let contracts = report_errors(result, &context, args.compile_options.deny_warnings)?; // 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) @@ -109,26 +113,27 @@ pub(crate) fn compile_circuit( program_dir: &Path, compile_options: &CompileOptions, ) -> Result> { - let mut driver = Resolver::resolve_root_manifest(program_dir)?; - let result = driver.compile_main( + let mut context = resolve_root_manifest(program_dir)?; + let result = compile_main( + &mut context, backend.np_language(), &|op| backend.supports_opcode(op), compile_options, ); - report_errors(result, &driver, compile_options.deny_warnings).map_err(Into::into) + report_errors(result, &context, compile_options.deny_warnings).map_err(Into::into) } /// Helper function for reporting any errors in a Result<(T, Warnings), ErrorsAndWarnings> /// structure that is commonly used as a return result in this file. pub(crate) fn report_errors( result: Result<(T, Warnings), ErrorsAndWarnings>, - driver: &Driver, + context: &Context, deny_warnings: bool, ) -> Result { let (t, warnings) = result.map_err(|errors| { - noirc_errors::reporter::report_all(driver.file_manager(), &errors, deny_warnings) + noirc_errors::reporter::report_all(&context.file_manager, &errors, deny_warnings) })?; - noirc_errors::reporter::report_all(driver.file_manager(), &warnings, deny_warnings); + noirc_errors::reporter::report_all(&context.file_manager, &warnings, deny_warnings); Ok(t) } diff --git a/crates/nargo_cli/src/cli/mod.rs b/crates/nargo_cli/src/cli/mod.rs index 73bd4f6d119..b83f2042a40 100644 --- a/crates/nargo_cli/src/cli/mod.rs +++ b/crates/nargo_cli/src/cli/mod.rs @@ -166,9 +166,9 @@ pub fn prove_and_verify(program_dir: &Path, experimental_ssa: bool) -> bool { // FIXME: I not sure that this is the right place for this tests. #[cfg(test)] mod tests { - use noirc_driver::Driver; + use noirc_driver::{check_crate, create_local_crate}; use noirc_errors::reporter; - use noirc_frontend::graph::CrateType; + use noirc_frontend::{graph::CrateType, hir::Context}; use std::path::{Path, PathBuf}; @@ -178,10 +178,10 @@ mod tests { /// /// This is used for tests. fn file_compiles>(root_file: P) -> bool { - let mut driver = Driver::new(); - driver.create_local_crate(&root_file, CrateType::Binary); + let mut context = Context::default(); + create_local_crate(&mut context, &root_file, CrateType::Binary); - let result = driver.check_crate(false); + let result = check_crate(&mut context, false); let success = result.is_ok(); let errors = match result { @@ -189,7 +189,7 @@ mod tests { Err(errors) => errors, }; - reporter::report_all(driver.file_manager(), &errors, false); + reporter::report_all(&context.file_manager, &errors, false); success } diff --git a/crates/nargo_cli/src/cli/test_cmd.rs b/crates/nargo_cli/src/cli/test_cmd.rs index 6db75c4fefd..b845ac2d294 100644 --- a/crates/nargo_cli/src/cli/test_cmd.rs +++ b/crates/nargo_cli/src/cli/test_cmd.rs @@ -3,11 +3,14 @@ use std::{io::Write, path::Path}; use acvm::{acir::native_types::WitnessMap, Backend}; use clap::Args; use nargo::ops::execute_circuit; -use noirc_driver::{CompileOptions, Driver}; -use noirc_frontend::node_interner::FuncId; +use noirc_driver::{compile_no_check, CompileOptions}; +use noirc_frontend::{graph::LOCAL_CRATE, hir::Context, node_interner::FuncId}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use crate::{cli::check_cmd::check_crate_and_report_errors, errors::CliError, resolver::Resolver}; +use crate::{ + cli::check_cmd::check_crate_and_report_errors, errors::CliError, + resolver::resolve_root_manifest, +}; use super::NargoConfig; @@ -37,10 +40,10 @@ fn run_tests( test_name: &str, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let mut driver = Resolver::resolve_root_manifest(program_dir)?; - check_crate_and_report_errors(&mut driver, compile_options.deny_warnings)?; + let mut context = resolve_root_manifest(program_dir)?; + check_crate_and_report_errors(&mut context, compile_options.deny_warnings)?; - let test_functions = driver.get_all_test_functions_in_crate_matching(test_name); + let test_functions = context.get_all_test_functions_in_crate_matching(&LOCAL_CRATE, test_name); println!("Running {} test functions...", test_functions.len()); let mut failing = 0; @@ -48,11 +51,11 @@ fn run_tests( let mut writer = writer.lock(); for test_function in test_functions { - let test_name = driver.function_name(test_function); + let test_name = context.function_name(&test_function); writeln!(writer, "Testing {test_name}...").expect("Failed to write to stdout"); writer.flush().ok(); - match run_test(backend, test_name, test_function, &driver, compile_options) { + match run_test(backend, test_name, test_function, &context, compile_options) { Ok(_) => { writer.set_color(ColorSpec::new().set_fg(Some(Color::Green))).ok(); writeln!(writer, "ok").ok(); @@ -79,12 +82,13 @@ fn run_test( backend: &B, test_name: &str, main: FuncId, - driver: &Driver, + context: &Context, config: &CompileOptions, ) -> Result<(), CliError> { - let program = driver - .compile_no_check(config, main, backend.np_language(), &|op| backend.supports_opcode(op)) - .map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?; + let program = compile_no_check(context, config, main, backend.np_language(), &|op| { + backend.supports_opcode(op) + }) + .map_err(|_| CliError::Generic(format!("Test '{test_name}' failed to compile")))?; // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. diff --git a/crates/nargo_cli/src/resolver.rs b/crates/nargo_cli/src/resolver.rs index 5bbf2601fc6..f15c5f3024e 100644 --- a/crates/nargo_cli/src/resolver.rs +++ b/crates/nargo_cli/src/resolver.rs @@ -4,8 +4,11 @@ use std::{ }; use nargo::manifest::{Dependency, PackageManifest}; -use noirc_driver::Driver; -use noirc_frontend::graph::{CrateId, CrateType}; +use noirc_driver::{add_dep, create_local_crate, create_non_local_crate}; +use noirc_frontend::{ + graph::{CrateId, CrateType}, + hir::Context, +}; use thiserror::Error; use crate::{git::clone_git_repo, InvalidPackageError}; @@ -54,112 +57,101 @@ struct CachedDep { /// or it uses the repo on the cache. /// Downloading will be recursive, so if a package contains packages /// We need to download those too -pub(crate) struct Resolver<'a> { - driver: &'a mut Driver, + +/// Returns the Driver and the backend to use +/// Note that the backend is ignored in the dependencies. +/// Since Noir is backend agnostic, this is okay to do. +/// XXX: Need to handle when a local package changes! +pub(crate) fn resolve_root_manifest( + dir_path: &std::path::Path, +) -> Result { + let mut context = Context::default(); + let (entry_path, crate_type) = super::lib_or_bin(dir_path)?; + + let manifest_path = super::find_package_manifest(dir_path)?; + let manifest = super::manifest::parse(&manifest_path)?; + + let crate_id = create_local_crate(&mut context, entry_path, crate_type); + + let pkg_root = manifest_path.parent().expect("Every manifest path has a parent."); + resolve_manifest(&mut context, crate_id, manifest, pkg_root)?; + + Ok(context) } -impl<'a> Resolver<'a> { - fn with_driver(driver: &mut Driver) -> Resolver { - Resolver { driver } - } +// Resolves a config file by recursively resolving the dependencies in the config +// Need to solve the case of a project trying to use itself as a dep +// +// We do not need to add stdlib, as it's implicitly +// imported. However, it may be helpful to have the stdlib imported by the +// package manager. +fn resolve_manifest( + context: &mut Context, + parent_crate: CrateId, + manifest: PackageManifest, + pkg_root: &Path, +) -> Result<(), DependencyResolutionError> { + let mut cached_packages: HashMap = HashMap::new(); - /// Returns the Driver and the backend to use - /// Note that the backend is ignored in the dependencies. - /// Since Noir is backend agnostic, this is okay to do. - /// XXX: Need to handle when a local package changes! - pub(crate) fn resolve_root_manifest( - dir_path: &std::path::Path, - ) -> Result { - let mut driver = Driver::new(); - let (entry_path, crate_type) = super::lib_or_bin(dir_path)?; + // First download and add these top level dependencies crates to the Driver + for (dep_pkg_name, pkg_src) in manifest.dependencies.iter() { + let (dir_path, dep_meta) = cache_dep(pkg_src, pkg_root)?; - let manifest_path = super::find_package_manifest(dir_path)?; - let manifest = super::manifest::parse(&manifest_path)?; + let (entry_path, crate_type) = (&dep_meta.entry_path, &dep_meta.crate_type); - let crate_id = driver.create_local_crate(entry_path, crate_type); + if crate_type == &CrateType::Binary { + return Err(DependencyResolutionError::BinaryDependency { + dep_pkg_name: dep_pkg_name.to_string(), + }); + } - let mut resolver = Resolver::with_driver(&mut driver); - let pkg_root = manifest_path.parent().expect("Every manifest path has a parent."); - resolver.resolve_manifest(crate_id, manifest, pkg_root)?; + let crate_id = create_non_local_crate(context, entry_path, *crate_type); + add_dep(context, parent_crate, crate_id, dep_pkg_name); - Ok(driver) + cached_packages.insert(dir_path, (crate_id, dep_meta)); } - // Resolves a config file by recursively resolving the dependencies in the config - // Need to solve the case of a project trying to use itself as a dep - // - // We do not need to add stdlib, as it's implicitly - // imported. However, it may be helpful to have the stdlib imported by the - // package manager. - fn resolve_manifest( - &mut self, - parent_crate: CrateId, - manifest: PackageManifest, - pkg_root: &Path, - ) -> Result<(), DependencyResolutionError> { - let mut cached_packages: HashMap = HashMap::new(); - - // First download and add these top level dependencies crates to the Driver - for (dep_pkg_name, pkg_src) in manifest.dependencies.iter() { - let (dir_path, dep_meta) = Resolver::cache_dep(pkg_src, pkg_root)?; - - let (entry_path, crate_type) = (&dep_meta.entry_path, &dep_meta.crate_type); - - if crate_type == &CrateType::Binary { - return Err(DependencyResolutionError::BinaryDependency { - dep_pkg_name: dep_pkg_name.to_string(), - }); - } - - let crate_id = self.driver.create_non_local_crate(entry_path, *crate_type); - self.driver.add_dep(parent_crate, crate_id, dep_pkg_name); - - cached_packages.insert(dir_path, (crate_id, dep_meta)); + // Resolve all transitive dependencies + for (dependency_path, (crate_id, dep_meta)) in cached_packages { + if dep_meta.remote && dep_meta.manifest.has_local_dependency() { + return Err(DependencyResolutionError::RemoteDepWithLocalDep { dependency_path }); } + // TODO: Why did it create a new resolver? + resolve_manifest(context, crate_id, dep_meta.manifest, &dependency_path)?; + } + Ok(()) +} - // Resolve all transitive dependencies - for (dependency_path, (crate_id, dep_meta)) in cached_packages { - if dep_meta.remote && dep_meta.manifest.has_local_dependency() { - return Err(DependencyResolutionError::RemoteDepWithLocalDep { dependency_path }); - } - let mut new_res = Resolver::with_driver(self.driver); - new_res.resolve_manifest(crate_id, dep_meta.manifest, &dependency_path)?; - } - Ok(()) +/// If the dependency is remote, download the dependency +/// and return the directory path along with the metadata +/// Needed to fill the CachedDep struct +/// +/// If it's a local path, the same applies, however it will not +/// be downloaded +fn cache_dep( + dep: &Dependency, + pkg_root: &Path, +) -> Result<(PathBuf, CachedDep), DependencyResolutionError> { + fn retrieve_meta( + dir_path: &Path, + remote: bool, + ) -> Result { + let (entry_path, crate_type) = super::lib_or_bin(dir_path)?; + let manifest_path = super::find_package_manifest(dir_path)?; + let manifest = super::manifest::parse(manifest_path)?; + Ok(CachedDep { entry_path, crate_type, manifest, remote }) } - /// If the dependency is remote, download the dependency - /// and return the directory path along with the metadata - /// Needed to fill the CachedDep struct - /// - /// If it's a local path, the same applies, however it will not - /// be downloaded - fn cache_dep( - dep: &Dependency, - pkg_root: &Path, - ) -> Result<(PathBuf, CachedDep), DependencyResolutionError> { - fn retrieve_meta( - dir_path: &Path, - remote: bool, - ) -> Result { - let (entry_path, crate_type) = super::lib_or_bin(dir_path)?; - let manifest_path = super::find_package_manifest(dir_path)?; - let manifest = super::manifest::parse(manifest_path)?; - Ok(CachedDep { entry_path, crate_type, manifest, remote }) + match dep { + Dependency::Github { git, tag } => { + let dir_path = clone_git_repo(git, tag).map_err(DependencyResolutionError::GitError)?; + let meta = retrieve_meta(&dir_path, true)?; + Ok((dir_path, meta)) } - - match dep { - Dependency::Github { git, tag } => { - let dir_path = - clone_git_repo(git, tag).map_err(DependencyResolutionError::GitError)?; - let meta = retrieve_meta(&dir_path, true)?; - Ok((dir_path, meta)) - } - Dependency::Path { path } => { - let dir_path = pkg_root.join(path); - let meta = retrieve_meta(&dir_path, false)?; - Ok((dir_path, meta)) - } + Dependency::Path { path } => { + let dir_path = pkg_root.join(path); + let meta = retrieve_meta(&dir_path, false)?; + Ok((dir_path, meta)) } } } diff --git a/crates/noirc_driver/src/lib.rs b/crates/noirc_driver/src/lib.rs index 720f0f59ef4..89dd588f012 100644 --- a/crates/noirc_driver/src/lib.rs +++ b/crates/noirc_driver/src/lib.rs @@ -7,14 +7,13 @@ use acvm::acir::circuit::Opcode; use acvm::compiler::CircuitSimplifier; use acvm::Language; use clap::Args; -use fm::{FileId, FileManager, FileType}; +use fm::{FileId, FileType}; use noirc_abi::FunctionSignature; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; use noirc_evaluator::{create_circuit, ssa_refactor::experimental_create_circuit}; use noirc_frontend::graph::{CrateId, CrateName, CrateType, LOCAL_CRATE}; use noirc_frontend::hir::def_map::{Contract, CrateDefMap}; use noirc_frontend::hir::Context; -use noirc_frontend::hir_def::function::FuncMeta; use noirc_frontend::monomorphization::monomorphize; use noirc_frontend::node_interner::FuncId; use serde::{Deserialize, Serialize}; @@ -26,11 +25,6 @@ mod program; pub use contract::{CompiledContract, ContractFunction, ContractFunctionType}; pub use program::CompiledProgram; -#[derive(Default)] -pub struct Driver { - context: Context, -} - #[derive(Args, Clone, Debug, Serialize, Deserialize)] pub struct CompileOptions { /// Emit debug information for the intermediate SSA IR @@ -72,345 +66,292 @@ pub type Warnings = Vec; /// Helper type used to signify where errors or warnings are expected in file diagnostics pub type ErrorsAndWarnings = Vec; -impl Driver { - pub fn new() -> Self { - Self::default() - } +// This is here for backwards compatibility +// with the restricted version which only uses one file +pub fn compile_file( + context: &mut Context, + root_file: PathBuf, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, +) -> Result<(CompiledProgram, Warnings), ErrorsAndWarnings> { + create_local_crate(context, root_file, CrateType::Binary); + compile_main(context, np_language, is_opcode_supported, &CompileOptions::default()) +} - // TODO(#1599): Move control of the FileManager into nargo - pub fn file_manager(&self) -> &FileManager { - &self.context.file_manager - } +/// Adds the File with the local crate root to the file system +/// and adds the local crate to the graph +/// XXX: This may pose a problem with workspaces, where you can change the local crate and where +/// we have multiple binaries in one workspace +/// A Fix would be for the driver instance to store the local crate id. +// Granted that this is the only place which relies on the local crate being first +pub fn create_local_crate>( + context: &mut Context, + root_file: P, + crate_type: CrateType, +) -> CrateId { + let dir_path = root_file.as_ref().to_path_buf(); + let root_file_id = context.file_manager.add_file(&dir_path, FileType::Root).unwrap(); + + let crate_id = context.crate_graph.add_crate_root(crate_type, root_file_id); + + assert!(crate_id == LOCAL_CRATE); + + LOCAL_CRATE +} - // This is here for backwards compatibility - // with the restricted version which only uses one file - pub fn compile_file( - root_file: PathBuf, - np_language: Language, - is_opcode_supported: &impl Fn(&Opcode) -> bool, - ) -> Result<(CompiledProgram, Warnings), ErrorsAndWarnings> { - let mut driver = Driver::new(); - driver.create_local_crate(root_file, CrateType::Binary); - driver.compile_main(np_language, is_opcode_supported, &CompileOptions::default()) - } +/// Creates a Non Local Crate. A Non Local Crate is any crate which is the not the crate that +/// the compiler is compiling. +pub fn create_non_local_crate>( + context: &mut Context, + root_file: P, + crate_type: CrateType, +) -> CrateId { + let dir_path = root_file.as_ref().to_path_buf(); + let root_file_id = context.file_manager.add_file(&dir_path, FileType::Root).unwrap(); + + // The first crate is always the local crate + assert!(context.crate_graph.number_of_crates() != 0); + + // You can add any crate type to the crate graph + // but you cannot depend on Binaries + context.crate_graph.add_crate_root(crate_type, root_file_id) +} - /// Adds the File with the local crate root to the file system - /// and adds the local crate to the graph - /// XXX: This may pose a problem with workspaces, where you can change the local crate and where - /// we have multiple binaries in one workspace - /// A Fix would be for the driver instance to store the local crate id. - // Granted that this is the only place which relies on the local crate being first - pub fn create_local_crate>( - &mut self, - root_file: P, - crate_type: CrateType, - ) -> CrateId { - let dir_path = root_file.as_ref().to_path_buf(); - let root_file_id = self.context.file_manager.add_file(&dir_path, FileType::Root).unwrap(); - - let crate_id = self.context.crate_graph.add_crate_root(crate_type, root_file_id); - - assert!(crate_id == LOCAL_CRATE); - - LOCAL_CRATE - } +/// Adds a edge in the crate graph for two crates +pub fn add_dep(context: &mut Context, this_crate: CrateId, depends_on: CrateId, crate_name: &str) { + let crate_name = CrateName::new(crate_name) + .expect("crate name contains blacklisted characters, please remove"); - /// Creates a Non Local Crate. A Non Local Crate is any crate which is the not the crate that - /// the compiler is compiling. - pub fn create_non_local_crate>( - &mut self, - root_file: P, - crate_type: CrateType, - ) -> CrateId { - let dir_path = root_file.as_ref().to_path_buf(); - let root_file_id = self.context.file_manager.add_file(&dir_path, FileType::Root).unwrap(); - - // The first crate is always the local crate - assert!(self.context.crate_graph.number_of_crates() != 0); - - // You can add any crate type to the crate graph - // but you cannot depend on Binaries - self.context.crate_graph.add_crate_root(crate_type, root_file_id) + // Cannot depend on a binary + if context.crate_graph.crate_type(depends_on) == CrateType::Binary { + panic!("crates cannot depend on binaries. {crate_name:?} is a binary crate") } - /// Adds a edge in the crate graph for two crates - pub fn add_dep(&mut self, this_crate: CrateId, depends_on: CrateId, crate_name: &str) { - let crate_name = CrateName::new(crate_name) - .expect("crate name contains blacklisted characters, please remove"); - - // Cannot depend on a binary - if self.context.crate_graph.crate_type(depends_on) == CrateType::Binary { - panic!("crates cannot depend on binaries. {crate_name:?} is a binary crate") - } - - self.context - .crate_graph - .add_dep(this_crate, crate_name, depends_on) - .expect("cyclic dependency triggered"); - } + context + .crate_graph + .add_dep(this_crate, crate_name, depends_on) + .expect("cyclic dependency triggered"); +} - /// Propagates a given dependency to every other crate. - pub fn propagate_dep(&mut self, dep_to_propagate: CrateId, dep_to_propagate_name: &CrateName) { - let crate_ids: Vec<_> = self - .context +/// Propagates a given dependency to every other crate. +pub fn propagate_dep( + context: &mut Context, + dep_to_propagate: CrateId, + dep_to_propagate_name: &CrateName, +) { + let crate_ids: Vec<_> = + context.crate_graph.iter_keys().filter(|crate_id| *crate_id != dep_to_propagate).collect(); + + for crate_id in crate_ids { + context .crate_graph - .iter_keys() - .filter(|crate_id| *crate_id != dep_to_propagate) - .collect(); - - for crate_id in crate_ids { - self.context - .crate_graph - .add_dep(crate_id, dep_to_propagate_name.clone(), dep_to_propagate) - .expect("ice: cyclic error triggered with std library"); - } + .add_dep(crate_id, dep_to_propagate_name.clone(), dep_to_propagate) + .expect("ice: cyclic error triggered with std library"); } +} - /// Run the lexing, parsing, name resolution, and type checking passes. - /// - /// This returns a (possibly empty) vector of any warnings found on success. - /// On error, this returns a non-empty vector of warnings and error messages, with at least one error. - pub fn check_crate(&mut self, deny_warnings: bool) -> Result { - // Add the stdlib before we check the crate - // TODO: This should actually be done when constructing the driver and then propagated to each dependency when added; - // however, the `create_non_local_crate` panics if you add the stdlib as the first crate in the graph and other - // parts of the code expect the `0` FileID to be the crate root. See also #1681 - let std_crate_name = "std"; - let path_to_std_lib_file = PathBuf::from(std_crate_name).join("lib.nr"); - let std_crate = self.create_non_local_crate(path_to_std_lib_file, CrateType::Library); - self.propagate_dep(std_crate, &CrateName::new(std_crate_name).unwrap()); - - let mut errors = vec![]; - CrateDefMap::collect_defs(LOCAL_CRATE, &mut self.context, &mut errors); - - if Self::has_errors(&errors, deny_warnings) { - Err(errors) - } else { - Ok(errors) - } +/// Run the lexing, parsing, name resolution, and type checking passes. +/// +/// This returns a (possibly empty) vector of any warnings found on success. +/// On error, this returns a non-empty vector of warnings and error messages, with at least one error. +pub fn check_crate( + context: &mut Context, + deny_warnings: bool, +) -> Result { + // Add the stdlib before we check the crate + // TODO: This should actually be done when constructing the driver and then propagated to each dependency when added; + // however, the `create_non_local_crate` panics if you add the stdlib as the first crate in the graph and other + // parts of the code expect the `0` FileID to be the crate root. See also #1681 + let std_crate_name = "std"; + let path_to_std_lib_file = PathBuf::from(std_crate_name).join("lib.nr"); + let std_crate = create_non_local_crate(context, path_to_std_lib_file, CrateType::Library); + propagate_dep(context, std_crate, &CrateName::new(std_crate_name).unwrap()); + + let mut errors = vec![]; + CrateDefMap::collect_defs(LOCAL_CRATE, context, &mut errors); + + if has_errors(&errors, deny_warnings) { + Err(errors) + } else { + Ok(errors) } +} - pub fn compute_function_signature(&self) -> Option { - let local_crate = self.context.def_map(LOCAL_CRATE).unwrap(); - - let main_function = local_crate.main_function()?; +pub fn compute_function_signature(context: &Context) -> Option { + let main_function = context.get_main_function(&LOCAL_CRATE)?; - let func_meta = self.context.def_interner.function_meta(&main_function); + let func_meta = context.def_interner.function_meta(&main_function); - Some(func_meta.into_function_signature(&self.context.def_interner)) - } + Some(func_meta.into_function_signature(&context.def_interner)) +} - /// Run the frontend to check the crate for errors then compile the main function if there were none - /// - /// On success this returns the compiled program alongside any warnings that were found. - /// On error this returns the non-empty list of warnings and errors. - pub fn compile_main( - &mut self, - np_language: Language, - is_opcode_supported: &impl Fn(&Opcode) -> bool, - options: &CompileOptions, - ) -> Result<(CompiledProgram, Warnings), ErrorsAndWarnings> { - let warnings = self.check_crate(options.deny_warnings)?; - - let main = match self.main_function() { - Some(m) => m, - None => { - let err = FileDiagnostic { +/// Run the frontend to check the crate for errors then compile the main function if there were none +/// +/// On success this returns the compiled program alongside any warnings that were found. +/// On error this returns the non-empty list of warnings and errors. +pub fn compile_main( + context: &mut Context, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, + options: &CompileOptions, +) -> Result<(CompiledProgram, Warnings), ErrorsAndWarnings> { + let warnings = check_crate(context, options.deny_warnings)?; + + let main = match context.get_main_function(&LOCAL_CRATE) { + Some(m) => m, + None => { + let err = FileDiagnostic { file_id: FileId::default(), diagnostic: CustomDiagnostic::from_message("cannot compile crate into a program as the local crate is not a binary. For libraries, please use the check command") }; - return Err(vec![err]); - } - }; - - let compiled_program = - self.compile_no_check(options, main, np_language, is_opcode_supported)?; - - if options.print_acir { - println!("Compiled ACIR for main:"); - println!("{}", compiled_program.circuit); + return Err(vec![err]); } + }; - Ok((compiled_program, warnings)) - } - - /// Run the frontend to check the crate for errors then compile all contracts if there were none - pub fn compile_contracts( - &mut self, - np_language: Language, - is_opcode_supported: &impl Fn(&Opcode) -> bool, - options: &CompileOptions, - ) -> Result<(Vec, Warnings), ErrorsAndWarnings> { - let warnings = self.check_crate(options.deny_warnings)?; - - let contracts = self.get_all_contracts(); - let mut compiled_contracts = vec![]; - let mut errors = warnings; - - for contract in contracts { - match self.compile_contract( - contract, - // TODO: Remove clone when it implements Copy - np_language.clone(), - is_opcode_supported, - options, - ) { - Ok(contract) => compiled_contracts.push(contract), - Err(mut more_errors) => errors.append(&mut more_errors), - } - } + let compiled_program = + compile_no_check(context, options, main, np_language, is_opcode_supported)?; - if Self::has_errors(&errors, options.deny_warnings) { - Err(errors) - } else { - if options.print_acir { - for compiled_contract in &compiled_contracts { - for contract_function in &compiled_contract.functions { - println!( - "Compiled ACIR for {}::{}:", - compiled_contract.name, contract_function.name - ); - println!("{}", contract_function.bytecode); - } - } - } - // errors here is either empty or contains only warnings - Ok((compiled_contracts, errors)) - } + if options.print_acir { + println!("Compiled ACIR for main:"); + println!("{}", compiled_program.circuit); } - /// True if there are (non-warning) errors present and we should halt compilation - fn has_errors(errors: &[FileDiagnostic], deny_warnings: bool) -> bool { - if deny_warnings { - !errors.is_empty() - } else { - errors.iter().any(|error| error.diagnostic.is_error()) + Ok((compiled_program, warnings)) +} + +/// Run the frontend to check the crate for errors then compile all contracts if there were none +pub fn compile_contracts( + context: &mut Context, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, + options: &CompileOptions, +) -> Result<(Vec, Warnings), ErrorsAndWarnings> { + let warnings = check_crate(context, options.deny_warnings)?; + + let contracts = context.get_all_contracts(&LOCAL_CRATE); + let mut compiled_contracts = vec![]; + let mut errors = warnings; + + for contract in contracts { + match compile_contract( + context, + contract, + // TODO: Remove clone when it implements Copy + np_language.clone(), + is_opcode_supported, + options, + ) { + Ok(contract) => compiled_contracts.push(contract), + Err(mut more_errors) => errors.append(&mut more_errors), } } - /// Compile all of the functions associated with a Noir contract. - fn compile_contract( - &self, - contract: Contract, - np_language: Language, - is_opcode_supported: &impl Fn(&Opcode) -> bool, - options: &CompileOptions, - ) -> Result> { - let mut functions = Vec::new(); - let mut errs = Vec::new(); - for function_id in &contract.functions { - let name = self.function_name(*function_id).to_owned(); - let function = match self.compile_no_check( - options, - *function_id, - // TODO: Remove clone when it implements Copy - np_language.clone(), - is_opcode_supported, - ) { - Ok(function) => function, - Err(err) => { - errs.push(err); - continue; + if has_errors(&errors, options.deny_warnings) { + Err(errors) + } else { + if options.print_acir { + for compiled_contract in &compiled_contracts { + for contract_function in &compiled_contract.functions { + println!( + "Compiled ACIR for {}::{}:", + compiled_contract.name, contract_function.name + ); + println!("{}", contract_function.bytecode); } - }; - let func_meta = self.context.def_interner.function_meta(function_id); - let func_type = func_meta - .contract_function_type - .expect("Expected contract function to have a contract visibility"); - - let function_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); - - functions.push(ContractFunction { - name, - function_type, - abi: function.abi, - bytecode: function.circuit, - }); - } - - if errs.is_empty() { - Ok(CompiledContract { name: contract.name, functions }) - } else { - Err(errs) + } } + // errors here is either empty or contains only warnings + Ok((compiled_contracts, errors)) } +} - /// Returns the FuncId of the 'main' function. - /// - Expects check_crate to be called beforehand - /// - Panics if no main function is found - pub fn main_function(&self) -> Option { - // Find the local crate, one should always be present - let local_crate = self.context.def_map(LOCAL_CRATE).unwrap(); - - // Check the crate type - // We don't panic here to allow users to `evaluate` libraries which will do nothing - if self.context.crate_graph[LOCAL_CRATE].crate_type != CrateType::Binary { - None - } else { - // All Binaries should have a main function - local_crate.main_function() - } +/// True if there are (non-warning) errors present and we should halt compilation +fn has_errors(errors: &[FileDiagnostic], deny_warnings: bool) -> bool { + if deny_warnings { + !errors.is_empty() + } else { + errors.iter().any(|error| error.diagnostic.is_error()) } +} - /// Compile the current crate. Assumes self.check_crate is called beforehand! - /// - /// This function also assumes all errors in experimental_create_circuit and create_circuit - /// are not warnings. - #[allow(deprecated)] - pub fn compile_no_check( - &self, - options: &CompileOptions, - main_function: FuncId, - np_language: Language, - is_opcode_supported: &impl Fn(&Opcode) -> bool, - ) -> Result { - let program = monomorphize(main_function, &self.context.def_interner); - - let (circuit, abi) = if options.experimental_ssa { - experimental_create_circuit(program, options.show_ssa, options.show_output)? - } else { - create_circuit(program, options.show_ssa, options.show_output)? +/// Compile all of the functions associated with a Noir contract. +fn compile_contract( + context: &Context, + contract: Contract, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, + options: &CompileOptions, +) -> Result> { + let mut functions = Vec::new(); + let mut errs = Vec::new(); + for function_id in &contract.functions { + let name = context.function_name(function_id).to_owned(); + let function = match compile_no_check( + context, + options, + *function_id, + // TODO: Remove clone when it implements Copy + np_language.clone(), + is_opcode_supported, + ) { + Ok(function) => function, + Err(err) => { + errs.push(err); + continue; + } }; - - let abi_len = abi.field_count(); - - let simplifier = CircuitSimplifier::new(abi_len); - let optimized_circuit = - acvm::compiler::compile(circuit, np_language, is_opcode_supported, &simplifier) - .map_err(|_| FileDiagnostic { - file_id: FileId::dummy(), - diagnostic: CustomDiagnostic::from_message("produced an acvm compile error"), - })?; - - Ok(CompiledProgram { circuit: optimized_circuit, abi }) + let func_meta = context.def_interner.function_meta(function_id); + let func_type = func_meta + .contract_function_type + .expect("Expected contract function to have a contract visibility"); + + let function_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); + + functions.push(ContractFunction { + name, + function_type, + abi: function.abi, + bytecode: function.circuit, + }); } - /// Returns a list of all functions in the current crate marked with #[test] - /// whose names contain the given pattern string. An empty pattern string - /// will return all functions marked with #[test]. - pub fn get_all_test_functions_in_crate_matching(&self, pattern: &str) -> Vec { - let interner = &self.context.def_interner; - self.context - .def_map(LOCAL_CRATE) - .expect("The local crate should be analyzed already") - .get_all_test_functions(interner) - .filter_map(|id| interner.function_name(&id).contains(pattern).then_some(id)) - .collect() - } - - /// Return a Vec of all `contract` declarations in the source code and the functions they contain - pub fn get_all_contracts(&self) -> Vec { - self.context - .def_map(LOCAL_CRATE) - .expect("The local crate should be analyzed already") - .get_all_contracts() - } - - pub fn function_name(&self, id: FuncId) -> &str { - self.context.def_interner.function_name(&id) + if errs.is_empty() { + Ok(CompiledContract { name: contract.name, functions }) + } else { + Err(errs) } +} - pub fn function_meta(&self, func_id: &FuncId) -> FuncMeta { - self.context.def_interner.function_meta(func_id) - } +/// Compile the current crate. Assumes self.check_crate is called beforehand! +/// +/// This function also assumes all errors in experimental_create_circuit and create_circuit +/// are not warnings. +#[allow(deprecated)] +pub fn compile_no_check( + context: &Context, + options: &CompileOptions, + main_function: FuncId, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, +) -> Result { + let program = monomorphize(main_function, &context.def_interner); + + let (circuit, abi) = if options.experimental_ssa { + experimental_create_circuit(program, options.show_ssa, options.show_output)? + } else { + create_circuit(program, options.show_ssa, options.show_output)? + }; + + let abi_len = abi.field_count(); + + let simplifier = CircuitSimplifier::new(abi_len); + let optimized_circuit = + acvm::compiler::compile(circuit, np_language, is_opcode_supported, &simplifier).map_err( + |_| FileDiagnostic { + file_id: FileId::dummy(), + diagnostic: CustomDiagnostic::from_message("produced an acvm compile error"), + }, + )?; + + Ok(CompiledProgram { circuit: optimized_circuit, abi }) } diff --git a/crates/noirc_driver/src/main.rs b/crates/noirc_driver/src/main.rs index b9d5d2583ca..f8de8ab8a98 100644 --- a/crates/noirc_driver/src/main.rs +++ b/crates/noirc_driver/src/main.rs @@ -1,30 +1,35 @@ use acvm::Language; -use noirc_driver::{CompileOptions, Driver}; -use noirc_frontend::graph::{CrateType, LOCAL_CRATE}; +use noirc_driver::{ + add_dep, compile_main, create_local_crate, create_non_local_crate, CompileOptions, +}; +use noirc_frontend::{ + graph::{CrateType, LOCAL_CRATE}, + hir::Context, +}; fn main() { const EXTERNAL_DIR: &str = "dep_b/lib.nr"; const EXTERNAL_DIR2: &str = "dep_a/lib.nr"; const ROOT_DIR_MAIN: &str = "example_real_project/main.nr"; - let mut driver = Driver::new(); + let mut context = Context::default(); // Add local crate to dep graph - driver.create_local_crate(ROOT_DIR_MAIN, CrateType::Binary); + create_local_crate(&mut context, ROOT_DIR_MAIN, CrateType::Binary); // Add libraries into Driver - let crate_id1 = driver.create_non_local_crate(EXTERNAL_DIR2, CrateType::Library); - let crate_id2 = driver.create_non_local_crate(EXTERNAL_DIR, CrateType::Library); + let crate_id1 = create_non_local_crate(&mut context, EXTERNAL_DIR2, CrateType::Library); + let crate_id2 = create_non_local_crate(&mut context, EXTERNAL_DIR, CrateType::Library); // Add dependencies as package - driver.add_dep(LOCAL_CRATE, crate_id1, "coo4"); - driver.add_dep(LOCAL_CRATE, crate_id2, "coo3"); + add_dep(&mut context, LOCAL_CRATE, crate_id1, "coo4"); + add_dep(&mut context, LOCAL_CRATE, crate_id2, "coo3"); - driver - .compile_main( - Language::R1CS, - #[allow(deprecated)] - &acvm::pwg::default_is_opcode_supported(Language::R1CS), - &CompileOptions::default(), - ) - .ok(); + compile_main( + &mut context, + Language::R1CS, + #[allow(deprecated)] + &acvm::pwg::default_is_opcode_supported(Language::R1CS), + &CompileOptions::default(), + ) + .ok(); } diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs index ef2fb84db57..b0022e26924 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -99,7 +99,7 @@ impl DefCollector { CrateDefMap::collect_defs(dep.crate_id, context, errors); let dep_def_root = - context.def_map(dep.crate_id).expect("ice: def map was just created").root; + context.def_map(&dep.crate_id).expect("ice: def map was just created").root; let module_id = ModuleId { krate: dep.crate_id, local_id: dep_def_root }; // Add this crate as a dependency by linking it's root module def_map.extern_prelude.insert(dep.as_name(), module_id); diff --git a/crates/noirc_frontend/src/hir/def_map/mod.rs b/crates/noirc_frontend/src/hir/def_map/mod.rs index fdaf2dd3acc..a05872027e5 100644 --- a/crates/noirc_frontend/src/hir/def_map/mod.rs +++ b/crates/noirc_frontend/src/hir/def_map/mod.rs @@ -71,7 +71,7 @@ impl CrateDefMap { // Without this check, the compiler will panic as it does not // expect the same crate to be processed twice. It would not // make the implementation wrong, if the same crate was processed twice, it just makes it slow. - if context.def_map(crate_id).is_some() { + if context.def_map(&crate_id).is_some() { return; } diff --git a/crates/noirc_frontend/src/hir/mod.rs b/crates/noirc_frontend/src/hir/mod.rs index 9142d2515c3..bf9057b6dcd 100644 --- a/crates/noirc_frontend/src/hir/mod.rs +++ b/crates/noirc_frontend/src/hir/mod.rs @@ -4,9 +4,10 @@ pub mod resolution; pub mod scope; pub mod type_check; -use crate::graph::{CrateGraph, CrateId}; -use crate::node_interner::NodeInterner; -use def_map::CrateDefMap; +use crate::graph::{CrateGraph, CrateId, CrateType}; +use crate::hir_def::function::FuncMeta; +use crate::node_interner::{FuncId, NodeInterner}; +use def_map::{Contract, CrateDefMap}; use fm::FileManager; use std::collections::HashMap; @@ -18,6 +19,7 @@ pub struct Context { pub def_interner: NodeInterner, pub crate_graph: CrateGraph, pub(crate) def_maps: HashMap, + // TODO(#1599): Remove default impl and move creation/control of the FileManager into places that construct Context pub file_manager: FileManager, /// Maps a given (contract) module id to the next available storage slot @@ -42,8 +44,8 @@ impl Context { /// It is perfectly valid for the compiler to look /// up a CrateDefMap and it is not available. /// This is how the compiler knows to compile a Crate. - pub fn def_map(&self, crate_id: CrateId) -> Option<&CrateDefMap> { - self.def_maps.get(&crate_id) + pub fn def_map(&self, crate_id: &CrateId) -> Option<&CrateDefMap> { + self.def_maps.get(crate_id) } /// Return the CrateId for each crate that has been compiled @@ -52,6 +54,54 @@ impl Context { self.crate_graph.iter_keys() } + pub fn function_name(&self, id: &FuncId) -> &str { + self.def_interner.function_name(id) + } + + pub fn function_meta(&self, func_id: &FuncId) -> FuncMeta { + self.def_interner.function_meta(func_id) + } + + /// Returns the FuncId of the 'main' function in a crate. + /// - Expects check_crate to be called beforehand + /// - Panics if no main function is found + pub fn get_main_function(&self, crate_id: &CrateId) -> Option { + // Find the local crate, one should always be present + let local_crate = self.def_map(crate_id).unwrap(); + + // Check the crate type + // We don't panic here to allow users to `evaluate` libraries which will do nothing + if self.crate_graph[*crate_id].crate_type == CrateType::Binary { + // All Binaries should have a main function + local_crate.main_function() + } else { + None + } + } + + /// Returns a list of all functions in the current crate marked with #[test] + /// whose names contain the given pattern string. An empty pattern string + /// will return all functions marked with #[test]. + pub fn get_all_test_functions_in_crate_matching( + &self, + crate_id: &CrateId, + pattern: &str, + ) -> Vec { + let interner = &self.def_interner; + self.def_map(crate_id) + .expect("The local crate should be analyzed already") + .get_all_test_functions(interner) + .filter_map(|id| interner.function_name(&id).contains(pattern).then_some(id)) + .collect() + } + + /// Return a Vec of all `contract` declarations in the source code and the functions they contain + pub fn get_all_contracts(&self, crate_id: &CrateId) -> Vec { + self.def_map(crate_id) + .expect("The local crate should be analyzed already") + .get_all_contracts() + } + fn module(&self, module_id: def_map::ModuleId) -> &def_map::ModuleData { module_id.module(&self.def_maps) } diff --git a/crates/noirc_frontend/src/main.rs b/crates/noirc_frontend/src/main.rs index 48c6c4b8b1a..c31f9867eea 100644 --- a/crates/noirc_frontend/src/main.rs +++ b/crates/noirc_frontend/src/main.rs @@ -36,7 +36,7 @@ fn main() { let mut errors = vec![]; CrateDefMap::collect_defs(crate_id, &mut context, &mut errors); assert_eq!(errors, vec![]); - let def_map = context.def_map(crate_id).unwrap(); + let def_map = context.def_map(&crate_id).unwrap(); // Get root module let root = def_map.root(); diff --git a/crates/wasm/src/compile.rs b/crates/wasm/src/compile.rs index 18c9722e8ff..d01798e8be7 100644 --- a/crates/wasm/src/compile.rs +++ b/crates/wasm/src/compile.rs @@ -1,7 +1,13 @@ use gloo_utils::format::JsValueSerdeExt; use log::debug; -use noirc_driver::{CompileOptions, Driver}; -use noirc_frontend::graph::{CrateName, CrateType}; +use noirc_driver::{ + check_crate, compile_contracts, compile_no_check, create_local_crate, create_non_local_crate, + propagate_dep, CompileOptions, +}; +use noirc_frontend::{ + graph::{CrateName, CrateType}, + hir::Context, +}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use wasm_bindgen::prelude::*; @@ -53,11 +59,11 @@ impl Default for WASMCompileOptions { } } -fn add_noir_lib(driver: &mut Driver, crate_name: &str) { +fn add_noir_lib(context: &mut Context, crate_name: &str) { let path_to_lib = PathBuf::from(&crate_name).join("lib.nr"); - let library_crate = driver.create_non_local_crate(path_to_lib, CrateType::Library); + let library_crate = create_non_local_crate(context, path_to_lib, CrateType::Library); - driver.propagate_dep(library_crate, &CrateName::new(crate_name).unwrap()); + propagate_dep(context, library_crate, &CrateName::new(crate_name).unwrap()); } #[wasm_bindgen] @@ -75,42 +81,42 @@ pub fn compile(args: JsValue) -> JsValue { // For now we default to plonk width = 3, though we can add it as a parameter let language = acvm::Language::PLONKCSat { width: 3 }; - let mut driver = noirc_driver::Driver::new(); + let mut context = Context::default(); let path = PathBuf::from(&options.entry_point); - driver.create_local_crate(path, CrateType::Binary); + let crate_id = create_local_crate(&mut context, path, CrateType::Binary); for dependency in options.optional_dependencies_set { - add_noir_lib(&mut driver, dependency.as_str()); + add_noir_lib(&mut context, dependency.as_str()); } - driver.check_crate(false).expect("Crate check failed"); + check_crate(&mut context, false).expect("Crate check failed"); if options.contracts { - let compiled_contracts = driver - .compile_contracts( - // TODO: Remove clone when it implements Copy - language.clone(), - #[allow(deprecated)] - &acvm::pwg::default_is_opcode_supported(language), - &options.compile_options, - ) - .expect("Contract compilation failed") - .0; + let compiled_contracts = compile_contracts( + &mut context, + // TODO: Remove clone when it implements Copy + language.clone(), + #[allow(deprecated)] + &acvm::pwg::default_is_opcode_supported(language), + &options.compile_options, + ) + .expect("Contract compilation failed") + .0; ::from_serde(&compiled_contracts).unwrap() } else { - let main = driver.main_function().expect("Could not find main function!"); - let compiled_program = driver - .compile_no_check( - &options.compile_options, - main, - // TODO: Remove clone when it implements Copy - language.clone(), - #[allow(deprecated)] - &acvm::pwg::default_is_opcode_supported(language), - ) - .expect("Compilation failed"); + let main = context.get_main_function(&crate_id).expect("Could not find main function!"); + let compiled_program = compile_no_check( + &context, + &options.compile_options, + main, + // TODO: Remove clone when it implements Copy + language.clone(), + #[allow(deprecated)] + &acvm::pwg::default_is_opcode_supported(language), + ) + .expect("Compilation failed"); ::from_serde(&compiled_program).unwrap() }