diff --git a/Cargo.lock b/Cargo.lock index f1ae4510054..e5b2fb34431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2392,6 +2392,7 @@ dependencies = [ "noirc_evaluator", "noirc_frontend", "noirc_printable_type", + "rayon", "rustc_version", "serde", "thiserror", @@ -2529,6 +2530,7 @@ dependencies = [ "cfg-if", "codespan-lsp", "codespan-reporting", + "fm", "lsp-types 0.94.1", "nargo", "nargo_toml", @@ -2537,6 +2539,7 @@ dependencies = [ "noirc_frontend", "serde", "serde_json", + "serde_with", "tokio", "tower", "wasm-bindgen", diff --git a/compiler/noirc_errors/src/debug_info.rs b/compiler/noirc_errors/src/debug_info.rs index a4092ec4a97..888c24adc1a 100644 --- a/compiler/noirc_errors/src/debug_info.rs +++ b/compiler/noirc_errors/src/debug_info.rs @@ -22,6 +22,7 @@ pub struct DebugInfo { /// Holds OpCodes Counts for Acir and Brillig Opcodes /// To be printed with `nargo info --profile-info` +#[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct OpCodesCount { pub acir_size: usize, pub brillig_size: usize, @@ -51,12 +52,12 @@ impl DebugInfo { self.locations.get(loc).cloned() } - pub fn count_span_opcodes(&self) -> HashMap<&Location, OpCodesCount> { - let mut accumulator: HashMap<&Location, Vec<&OpcodeLocation>> = HashMap::new(); + pub fn count_span_opcodes(&self) -> HashMap { + let mut accumulator: HashMap> = HashMap::new(); for (opcode_location, locations) in self.locations.iter() { for location in locations.iter() { - let opcodes = accumulator.entry(location).or_insert(Vec::new()); + let opcodes = accumulator.entry(*location).or_insert(Vec::new()); opcodes.push(opcode_location); } } diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 5f35e60dea7..be24c1249c6 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -16,6 +16,7 @@ pub struct Lexer<'a> { position: Position, done: bool, skip_comments: bool, + skip_whitespaces: bool, } pub type SpannedTokenResult = Result; @@ -37,7 +38,13 @@ impl<'a> Lexer<'a> { } pub fn new(source: &'a str) -> Self { - Lexer { chars: source.char_indices(), position: 0, done: false, skip_comments: true } + Lexer { + chars: source.char_indices(), + position: 0, + done: false, + skip_comments: true, + skip_whitespaces: true, + } } pub fn skip_comments(mut self, flag: bool) -> Self { @@ -45,6 +52,11 @@ impl<'a> Lexer<'a> { self } + pub fn skip_whitespaces(mut self, flag: bool) -> Self { + self.skip_whitespaces = flag; + self + } + /// Iterates the cursor and returns the char at the new cursor position fn next_char(&mut self) -> Option { let (position, ch) = self.chars.next()?; @@ -82,9 +94,13 @@ impl<'a> Lexer<'a> { fn next_token(&mut self) -> SpannedTokenResult { match self.next_char() { - Some(x) if { x.is_whitespace() } => { - self.eat_whitespace(); - self.next_token() + Some(x) if x.is_whitespace() => { + let spanned = self.eat_whitespace(x); + if self.skip_whitespaces { + self.next_token() + } else { + Ok(spanned) + } } Some('<') => self.glue(Token::Less), Some('>') => self.glue(Token::Greater), @@ -454,8 +470,10 @@ impl<'a> Lexer<'a> { } /// Skips white space. They are not significant in the source language - fn eat_whitespace(&mut self) { - self.eat_while(None, |ch| ch.is_whitespace()); + fn eat_whitespace(&mut self, initial_char: char) -> SpannedToken { + let start = self.position; + let whitespace = self.eat_while(initial_char.into(), |ch| ch.is_whitespace()); + SpannedToken::new(Token::Whitespace(whitespace), Span::inclusive(start, self.position)) } } diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 28e17b2b88d..72be71865cc 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -88,6 +88,8 @@ pub enum Token { #[allow(clippy::upper_case_acronyms)] EOF, + Whitespace(String), + /// An invalid character is one that is not in noir's language or grammar. /// /// We don't report invalid tokens in the source as errors until parsing to @@ -194,6 +196,7 @@ impl fmt::Display for Token { Token::Bang => write!(f, "!"), Token::EOF => write!(f, "end of input"), Token::Invalid(c) => write!(f, "{c}"), + Token::Whitespace(ref s) => write!(f, "{s}"), } } } diff --git a/tooling/lsp/Cargo.toml b/tooling/lsp/Cargo.toml index a1d58a0de49..9e642d5fe9c 100644 --- a/tooling/lsp/Cargo.toml +++ b/tooling/lsp/Cargo.toml @@ -23,6 +23,8 @@ serde_json.workspace = true tower.workspace = true cfg-if.workspace = true async-lsp = { workspace = true, features = ["omni-trait"] } +serde_with = "3.2.0" +fm.workspace = true [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] wasm-bindgen.workspace = true diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 6e71f3d642d..79fc544692a 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -26,7 +26,8 @@ use notifications::{ on_did_open_text_document, on_did_save_text_document, on_exit, on_initialized, }; use requests::{ - on_code_lens_request, on_initialize, on_shutdown, on_test_run_request, on_tests_request, + on_code_lens_request, on_initialize, on_profile_run_request, on_shutdown, on_test_run_request, + on_tests_request, }; use serde_json::Value as JsonValue; use tower::Service; @@ -66,6 +67,7 @@ impl NargoLspService { .request::(on_code_lens_request) .request::(on_tests_request) .request::(on_test_run_request) + .request::(on_profile_run_request) .notification::(on_initialized) .notification::(on_did_change_configuration) .notification::(on_did_open_text_document) diff --git a/tooling/lsp/src/requests/code_lens_request.rs b/tooling/lsp/src/requests/code_lens_request.rs index 7794fb8444f..602ed268981 100644 --- a/tooling/lsp/src/requests/code_lens_request.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -23,6 +23,9 @@ const INFO_CODELENS_TITLE: &str = "Info"; const EXECUTE_COMMAND: &str = "nargo.execute"; const EXECUTE_CODELENS_TITLE: &str = "Execute"; +const PROFILE_COMMAND: &str = "nargo.profile"; +const PROFILE_CODELENS_TITLE: &str = "Profile"; + fn with_arrow(title: &str) -> String { format!("{ARROW} {title}") } @@ -163,6 +166,16 @@ fn on_code_lens_request_inner( let execute_lens = CodeLens { range, command: Some(execute_command), data: None }; lenses.push(execute_lens); + + let profile_command = Command { + title: PROFILE_CODELENS_TITLE.to_string(), + command: PROFILE_COMMAND.into(), + arguments: Some(package_selection_args(&workspace, package)), + }; + + let profile_lens = CodeLens { range, command: Some(profile_command), data: None }; + + lenses.push(profile_lens); } } @@ -200,6 +213,16 @@ fn on_code_lens_request_inner( let info_lens = CodeLens { range, command: Some(info_command), data: None }; lenses.push(info_lens); + + let profile_command = Command { + title: PROFILE_CODELENS_TITLE.to_string(), + command: PROFILE_COMMAND.into(), + arguments: Some(package_selection_args(&workspace, package)), + }; + + let profile_lens = CodeLens { range, command: Some(profile_command), data: None }; + + lenses.push(profile_lens); } } } diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index 166adb10b5a..b2be24e1187 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -19,11 +19,13 @@ use crate::{ // and params passed in. mod code_lens_request; +mod profile_run; mod test_run; mod tests; pub(crate) use { - code_lens_request::on_code_lens_request, test_run::on_test_run_request, tests::on_tests_request, + code_lens_request::on_code_lens_request, profile_run::on_profile_run_request, + test_run::on_test_run_request, tests::on_tests_request, }; pub(crate) fn on_initialize( diff --git a/tooling/lsp/src/requests/profile_run.rs b/tooling/lsp/src/requests/profile_run.rs new file mode 100644 index 00000000000..84888d30ba5 --- /dev/null +++ b/tooling/lsp/src/requests/profile_run.rs @@ -0,0 +1,102 @@ +use std::{ + collections::{BTreeMap, HashMap}, + future::{self, Future}, +}; + +use acvm::{acir::circuit::Opcode, Language}; +use async_lsp::{ErrorCode, ResponseError}; +use nargo::artifacts::debug::DebugArtifact; +use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_driver::{CompileOptions, DebugFile, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_errors::{debug_info::OpCodesCount, Location}; + +use crate::{ + types::{NargoProfileRunParams, NargoProfileRunResult}, + LspState, +}; +use fm::FileId; + +pub(crate) fn on_profile_run_request( + state: &mut LspState, + params: NargoProfileRunParams, +) -> impl Future> { + future::ready(on_profile_run_request_inner(state, params)) +} + +fn on_profile_run_request_inner( + state: &LspState, + params: NargoProfileRunParams, +) -> Result { + let root_path = state.root_path.as_deref().ok_or_else(|| { + ResponseError::new(ErrorCode::REQUEST_FAILED, "Could not find project root") + })?; + + let toml_path = find_package_manifest(root_path, root_path).map_err(|err| { + // If we cannot find a manifest, we can't run the test + ResponseError::new(ErrorCode::REQUEST_FAILED, err) + })?; + + let crate_name = params.package; + + let workspace = resolve_workspace_from_toml( + &toml_path, + PackageSelection::DefaultOrAll, + Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) + .map_err(|err| { + // If we found a manifest, but the workspace is invalid, we raise an error about it + ResponseError::new(ErrorCode::REQUEST_FAILED, err) + })?; + + // Since we filtered on crate name, this should be the only item in the iterator + match workspace.into_iter().next() { + Some(_package) => { + let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace + .into_iter() + .filter(|package| !package.is_library()) + .cloned() + .partition(|package| package.is_binary()); + + // # TODO(#3504): Consider how to incorporate Backend relevant information in wider context. + let is_opcode_supported = |_opcode: &Opcode| true; + let np_language = Language::PLONKCSat { width: 3 }; + + let (compiled_programs, compiled_contracts) = nargo::ops::compile_workspace( + &workspace, + &binary_packages, + &contract_packages, + np_language, + is_opcode_supported, + &CompileOptions::default(), + ) + .map_err(|err| ResponseError::new(ErrorCode::REQUEST_FAILED, err))?; + + let mut opcodes_counts: HashMap = HashMap::new(); + let mut file_map: BTreeMap = BTreeMap::new(); + for compiled_program in &compiled_programs { + let span_opcodes = compiled_program.debug.count_span_opcodes(); + let debug_artifact: DebugArtifact = compiled_program.clone().into(); + opcodes_counts.extend(span_opcodes); + file_map.extend(debug_artifact.file_map); + } + + for compiled_contract in &compiled_contracts { + let functions = &compiled_contract.functions; + let debug_artifact: DebugArtifact = compiled_contract.clone().into(); + file_map.extend(debug_artifact.file_map); + for contract_function in functions { + let span_opcodes = contract_function.debug.count_span_opcodes(); + opcodes_counts.extend(span_opcodes); + } + } + + let result = NargoProfileRunResult { file_map, opcodes_counts }; + + Ok(result) + } + None => Err(ResponseError::new( + ErrorCode::REQUEST_FAILED, + format!("Could not locate package named: {crate_name}"), + )), + } +} diff --git a/tooling/lsp/src/types.rs b/tooling/lsp/src/types.rs index 10f1764c63f..7a50c538051 100644 --- a/tooling/lsp/src/types.rs +++ b/tooling/lsp/src/types.rs @@ -1,5 +1,10 @@ +use fm::FileId; +use noirc_driver::DebugFile; +use noirc_errors::{debug_info::OpCodesCount, Location}; use noirc_frontend::graph::CrateName; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::collections::{BTreeMap, HashMap}; // Re-providing lsp_types that we don't need to override pub(crate) use lsp_types::{ @@ -14,8 +19,8 @@ pub(crate) mod request { use lsp_types::{request::Request, InitializeParams}; use super::{ - InitializeResult, NargoTestRunParams, NargoTestRunResult, NargoTestsParams, - NargoTestsResult, + InitializeResult, NargoProfileRunParams, NargoProfileRunResult, NargoTestRunParams, + NargoTestRunResult, NargoTestsParams, NargoTestsResult, }; // Re-providing lsp_types that we don't need to override @@ -44,6 +49,14 @@ pub(crate) mod request { type Result = NargoTestsResult; const METHOD: &'static str = "nargo/tests"; } + + #[derive(Debug)] + pub(crate) struct NargoProfileRun; + impl Request for NargoProfileRun { + type Params = NargoProfileRunParams; + type Result = NargoProfileRunResult; + const METHOD: &'static str = "nargo/profile/run"; + } } pub(crate) mod notification { @@ -186,5 +199,16 @@ pub(crate) struct NargoTestRunResult { pub(crate) result: String, pub(crate) message: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct NargoProfileRunParams { + pub(crate) package: CrateName, +} +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct NargoProfileRunResult { + pub(crate) file_map: BTreeMap, + #[serde_as(as = "Vec<(_, _)>")] + pub(crate) opcodes_counts: HashMap, +} pub(crate) type CodeLensResult = Option>; diff --git a/tooling/nargo/Cargo.toml b/tooling/nargo/Cargo.toml index e6674c043d7..f8269459968 100644 --- a/tooling/nargo/Cargo.toml +++ b/tooling/nargo/Cargo.toml @@ -24,3 +24,4 @@ iter-extended.workspace = true serde.workspace = true thiserror.workspace = true codespan-reporting.workspace = true +rayon = "1.8.0" \ No newline at end of file diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index ba52d127e9e..bca8ca24767 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -2,13 +2,36 @@ use acvm::{ acir::circuit::OpcodeLocation, pwg::{ErrorLocation, OpcodeResolutionError}, }; -use noirc_errors::{debug_info::DebugInfo, CustomDiagnostic, FileDiagnostic}; +use noirc_errors::{ + debug_info::DebugInfo, reporter::ReportedErrors, CustomDiagnostic, FileDiagnostic, +}; pub use noirc_errors::Location; +use noirc_frontend::graph::CrateName; use noirc_printable_type::ForeignCallError; use thiserror::Error; +/// Errors covering situations where a package cannot be compiled. +#[derive(Debug, Error)] +pub enum CompileError { + #[error("Package `{0}` has type `lib` but only `bin` types can be compiled")] + LibraryCrate(CrateName), + + #[error("Package `{0}` is expected to have a `main` function but it does not")] + MissingMainFunction(CrateName), + + /// Errors encountered while compiling the Noir program. + /// These errors are already written to stderr. + #[error("Aborting due to {} previous error{}", .0.error_count, if .0.error_count == 1 { "" } else { "s" })] + ReportedErrors(ReportedErrors), +} +impl From for CompileError { + fn from(errors: ReportedErrors) -> Self { + Self::ReportedErrors(errors) + } +} + #[derive(Debug, Error)] pub enum NargoError { /// Error while compiling Noir into ACIR. diff --git a/tooling/nargo/src/ops/compile.rs b/tooling/nargo/src/ops/compile.rs new file mode 100644 index 00000000000..d4164eaa865 --- /dev/null +++ b/tooling/nargo/src/ops/compile.rs @@ -0,0 +1,154 @@ +use acvm::{acir::circuit::Opcode, Language}; +use fm::FileManager; +use noirc_driver::{CompilationResult, CompileOptions, CompiledContract, CompiledProgram}; + +use crate::errors::CompileError; +use crate::prepare_package; +use crate::{package::Package, workspace::Workspace}; + +use rayon::prelude::*; + +/// Compiles workspace. +/// +/// # Errors +/// +/// This function will return an error if there are any compilations errors reported. +pub fn compile_workspace( + workspace: &Workspace, + binary_packages: &[Package], + contract_packages: &[Package], + np_language: Language, + is_opcode_supported: impl Fn(&Opcode) -> bool + std::marker::Sync, + compile_options: &CompileOptions, +) -> Result<(Vec, Vec), CompileError> { + // Compile all of the packages in parallel. + let program_results: Vec<(FileManager, CompilationResult)> = binary_packages + .par_iter() + .map(|package| { + compile_program(workspace, package, compile_options, np_language, &is_opcode_supported) + }) + .collect(); + let contract_results: Vec<(FileManager, CompilationResult)> = + contract_packages + .par_iter() + .map(|package| { + compile_contract(package, compile_options, np_language, &is_opcode_supported) + }) + .collect(); + + // Report any warnings/errors which were encountered during compilation. + let compiled_programs: Vec = program_results + .into_iter() + .map(|(file_manager, compilation_result)| { + report_errors( + compilation_result, + &file_manager, + compile_options.deny_warnings, + compile_options.silence_warnings, + ) + }) + .collect::>()?; + let compiled_contracts: Vec = contract_results + .into_iter() + .map(|(file_manager, compilation_result)| { + report_errors( + compilation_result, + &file_manager, + compile_options.deny_warnings, + compile_options.silence_warnings, + ) + }) + .collect::>()?; + + Ok((compiled_programs, compiled_contracts)) +} + +pub fn compile_program( + workspace: &Workspace, + package: &Package, + compile_options: &CompileOptions, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, +) -> (FileManager, CompilationResult) { + let (mut context, crate_id) = + prepare_package(package, Box::new(|path| std::fs::read_to_string(path))); + + let program_artifact_path = workspace.package_build_path(package); + let mut debug_artifact_path = program_artifact_path.clone(); + debug_artifact_path.set_file_name(format!("debug_{}.json", package.name)); + + let (program, warnings) = + match noirc_driver::compile_main(&mut context, crate_id, compile_options, None, true) { + Ok(program_and_warnings) => program_and_warnings, + Err(errors) => { + return (context.file_manager, Err(errors)); + } + }; + + // TODO: we say that pedersen hashing is supported by all backends for now + let is_opcode_supported_pedersen_hash = |opcode: &Opcode| -> bool { + if let Opcode::BlackBoxFuncCall( + acvm::acir::circuit::opcodes::BlackBoxFuncCall::PedersenHash { .. }, + ) = opcode + { + true + } else { + is_opcode_supported(opcode) + } + }; + + // Apply backend specific optimizations. + let optimized_program = + crate::ops::optimize_program(program, np_language, &is_opcode_supported_pedersen_hash) + .expect("Backend does not support an opcode that is in the IR"); + + (context.file_manager, Ok((optimized_program, warnings))) +} + +fn compile_contract( + package: &Package, + compile_options: &CompileOptions, + np_language: Language, + is_opcode_supported: &impl Fn(&Opcode) -> bool, +) -> (FileManager, CompilationResult) { + let (mut context, crate_id) = + prepare_package(package, Box::new(|path| std::fs::read_to_string(path))); + let (contract, warnings) = + match noirc_driver::compile_contract(&mut context, crate_id, compile_options) { + Ok(contracts_and_warnings) => contracts_and_warnings, + Err(errors) => { + return (context.file_manager, Err(errors)); + } + }; + + let optimized_contract = + crate::ops::optimize_contract(contract, np_language, &is_opcode_supported) + .expect("Backend does not support an opcode that is in the IR"); + + (context.file_manager, Ok((optimized_contract, warnings))) +} + +pub(crate) fn report_errors( + result: CompilationResult, + file_manager: &FileManager, + deny_warnings: bool, + silence_warnings: bool, +) -> Result { + let (t, warnings) = result.map_err(|errors| { + noirc_errors::reporter::report_all( + file_manager.as_file_map(), + &errors, + deny_warnings, + silence_warnings, + ) + })?; + + noirc_errors::reporter::report_all( + file_manager.as_file_map(), + &warnings, + deny_warnings, + silence_warnings, + ); + + Ok(t) +} diff --git a/tooling/nargo/src/ops/mod.rs b/tooling/nargo/src/ops/mod.rs index 491320482cf..9a523c59841 100644 --- a/tooling/nargo/src/ops/mod.rs +++ b/tooling/nargo/src/ops/mod.rs @@ -1,8 +1,10 @@ +pub use self::compile::{compile_program, compile_workspace}; pub use self::execute::execute_circuit; pub use self::foreign_calls::ForeignCallExecutor; pub use self::optimize::{optimize_contract, optimize_program}; pub use self::test::{run_test, TestStatus}; +mod compile; mod execute; mod foreign_calls; mod optimize; diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index 79f199381fa..57b36b8932b 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -1,9 +1,9 @@ use crate::backends::Backend; -use crate::errors::{CliError, CompileError}; +use crate::errors::CliError; use clap::Args; use iter_extended::btree_map; -use nargo::{package::Package, prepare_package}; +use nargo::{errors::CompileError, package::Package, prepare_package}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME}; use noirc_driver::{ diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 156359dab04..9ffbc26828e 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -10,6 +10,7 @@ use nargo::artifacts::contract::PreprocessedContract; use nargo::artifacts::contract::PreprocessedContractFunction; use nargo::artifacts::debug::DebugArtifact; use nargo::artifacts::program::PreprocessedProgram; +use nargo::errors::CompileError; use nargo::package::Package; use nargo::prepare_package; use nargo::workspace::Workspace; @@ -21,7 +22,7 @@ use noirc_frontend::graph::CrateName; use clap::Args; use crate::backends::Backend; -use crate::errors::{CliError, CompileError}; +use crate::errors::CliError; use super::fs::program::{ read_debug_artifact_from_file, read_program_from_file, save_contract_to_file, diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 665eb2ca256..b1cd5f0b64f 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -81,7 +81,7 @@ pub(crate) fn run( for compiled_program in &compiled_programs { let span_opcodes = compiled_program.debug.count_span_opcodes(); let debug_artifact: DebugArtifact = compiled_program.clone().into(); - print_span_opcodes(&span_opcodes, &debug_artifact); + print_span_opcodes(span_opcodes, &debug_artifact); } for compiled_contract in &compiled_contracts { @@ -89,7 +89,7 @@ pub(crate) fn run( let functions = &compiled_contract.functions; for contract_function in functions { let span_opcodes = contract_function.debug.count_span_opcodes(); - print_span_opcodes(&span_opcodes, &debug_artifact); + print_span_opcodes(span_opcodes, &debug_artifact); } } } @@ -149,10 +149,10 @@ pub(crate) fn run( /// Number of OpCodes in relation to Noir source file /// and line number information fn print_span_opcodes( - span_opcodes_map: &HashMap<&Location, OpCodesCount>, + span_opcodes_map: HashMap, debug_artifact: &DebugArtifact, ) { - let mut pairs: Vec<(&&Location, &OpCodesCount)> = span_opcodes_map.iter().collect(); + let mut pairs: Vec<(&Location, &OpCodesCount)> = span_opcodes_map.iter().collect(); pairs.sort_by(|a, b| { a.1.acir_size.cmp(&b.1.acir_size).then_with(|| a.1.brillig_size.cmp(&b.1.brillig_size)) @@ -165,7 +165,7 @@ fn print_span_opcodes( let end_byte = byte_index(&debug_file.source, location.span.end() + 1); let range = start_byte..end_byte; let span_content = &debug_file.source[range]; - let line = debug_artifact.location_line_index(**location).unwrap() + 1; + let line = debug_artifact.location_line_index(*location).unwrap() + 1; println!( "Ln. {}: {} (ACIR:{}, Brillig:{} opcode|s) in file: {}", line, diff --git a/tooling/nargo_cli/src/errors.rs b/tooling/nargo_cli/src/errors.rs index b73a7888f32..92da74c71d4 100644 --- a/tooling/nargo_cli/src/errors.rs +++ b/tooling/nargo_cli/src/errors.rs @@ -1,10 +1,8 @@ use acvm::acir::native_types::WitnessMapError; use hex::FromHexError; -use nargo::NargoError; +use nargo::{errors::CompileError, NargoError}; use nargo_toml::ManifestError; use noirc_abi::errors::{AbiError, InputParserError}; -use noirc_errors::reporter::ReportedErrors; -use noirc_frontend::graph::CrateName; use std::path::PathBuf; use thiserror::Error; @@ -87,24 +85,3 @@ pub(crate) enum BackendError { #[error("Backend installation failed: {0}")] InstallationError(#[from] std::io::Error), } - -/// Errors covering situations where a package cannot be compiled. -#[derive(Debug, Error)] -pub(crate) enum CompileError { - #[error("Package `{0}` has type `lib` but only `bin` types can be compiled")] - LibraryCrate(CrateName), - - #[error("Package `{0}` is expected to have a `main` function but it does not")] - MissingMainFunction(CrateName), - - /// Errors encountered while compiling the Noir program. - /// These errors are already written to stderr. - #[error("Aborting due to {} previous error{}", .0.error_count, if .0.error_count == 1 { "" } else { "s" })] - ReportedErrors(ReportedErrors), -} - -impl From for CompileError { - fn from(errors: ReportedErrors) -> Self { - Self::ReportedErrors(errors) - } -} diff --git a/tooling/nargo_fmt/src/rewrite/array.rs b/tooling/nargo_fmt/src/rewrite/array.rs index dde2cbb1ec7..9c49d827528 100644 --- a/tooling/nargo_fmt/src/rewrite/array.rs +++ b/tooling/nargo_fmt/src/rewrite/array.rs @@ -36,14 +36,14 @@ pub(crate) fn rewrite(mut visitor: FmtVisitor, array: Vec, array_spa let trailing = trailing[..offset].trim_end_matches(',').trim_matches(pattern); last_position = item_span.end() + offset as u32; - let (leading, _) = visitor.format_comment_in_block(leading, 0, false); - let (trailing, _) = visitor.format_comment_in_block(trailing, 0, false); + let (leading, _) = visitor.format_comment_in_block(leading); + let (trailing, _) = visitor.format_comment_in_block(trailing); result.push(Expr { leading, value: item, trailing, different_line: false }); } let slice = visitor.slice(last_position..end_position); - let (comment, _) = visitor.format_comment_in_block(slice, 0, false); + let (comment, _) = visitor.format_comment_in_block(slice); result.push(Expr { leading: "".into(), value: "".into(), diff --git a/tooling/nargo_fmt/src/visitor.rs b/tooling/nargo_fmt/src/visitor.rs index 017d8406116..cf3b3a41e8a 100644 --- a/tooling/nargo_fmt/src/visitor.rs +++ b/tooling/nargo_fmt/src/visitor.rs @@ -163,7 +163,7 @@ impl<'me> FmtVisitor<'me> { self.push_vertical_spaces(slice); process_last_slice(self, "", slice); } else { - let (result, last_end) = self.format_comment_in_block(slice, start, true); + let (result, last_end) = self.format_comment_in_block(slice); if result.trim().is_empty() { process_last_slice(self, slice, slice); } else { @@ -174,75 +174,36 @@ impl<'me> FmtVisitor<'me> { } } - pub(crate) fn format_comment_in_block( - &mut self, - slice: &str, - start: u32, - fix_indent: bool, - ) -> (String, u32) { + pub(crate) fn format_comment_in_block(&mut self, slice: &str) -> (String, u32) { let mut result = String::new(); - let mut last_end = 0; - - let mut comments = Lexer::new(slice).skip_comments(false).flatten(); + let comments = Lexer::new(slice).skip_comments(false).skip_whitespaces(false).flatten(); - if let Some(comment) = comments.next() { + let indent = self.indent.to_string(); + for comment in comments { let span = comment.to_span(); - let diff = start; - let big_snippet = &self.source[..(span.start() + diff) as usize]; - let last_char = big_snippet.chars().rev().find(|rev_c| ![' ', '\t'].contains(rev_c)); - let fix_indent = - fix_indent && last_char.map_or(true, |rev_c| ['{', '\n'].contains(&rev_c)); - - if let Token::LineComment(_, _) | Token::BlockComment(_, _) = comment.into_token() { - let starts_with_newline = slice.starts_with('\n'); - let comment = &slice[span.start() as usize..span.end() as usize]; - - if fix_indent { - if let Some('{') = last_char { - result.push('\n'); - } - if let Some('\n') = last_char { - result.push('\n'); + match comment.token() { + Token::LineComment(_, _) | Token::BlockComment(_, _) => { + let comment = &slice[span.start() as usize..span.end() as usize]; + if result.ends_with('\n') { + result.push_str(&indent); + } else if !self.at_start() { + result.push(' '); } - - let indent_str = self.indent.to_string(); - result.push_str(&indent_str); - } else { - match (starts_with_newline, self.at_start()) { - (false, false) => { - result.push(' '); - } - (true, _) => { - result.push_str(&self.indent.to_string_with_newline()); - } - (false, _) => { - result.push(' '); - } - }; + result.push_str(comment); } - - result.push_str(comment); - } - } - - for spanned in comments { - let span = spanned.to_span(); - last_end = span.end(); - - if let Token::LineComment(_, _) | Token::BlockComment(_, _) = spanned.token() { - let comment = &slice[span.start() as usize..span.end() as usize]; - - result.push_str(&self.indent.to_string_with_newline()); - result.push_str(comment); + Token::Whitespace(whitespaces) => { + let mut visitor = self.fork(); + if whitespaces.contains('\n') { + visitor.push_vertical_spaces(whitespaces.trim_matches(' ')); + result.push_str(&visitor.finish()); + } + } + _ => {} } } - if slice.trim_end_matches([' ', '\t']).ends_with(['\n', '\r']) { - result.push('\n'); - } - - (result, last_end) + (result, slice.len() as u32) } fn push_vertical_spaces(&mut self, slice: &str) { diff --git a/tooling/nargo_fmt/tests/expected/comment.nr b/tooling/nargo_fmt/tests/expected/comment.nr index eb600ff2f75..fae425acfd0 100644 --- a/tooling/nargo_fmt/tests/expected/comment.nr +++ b/tooling/nargo_fmt/tests/expected/comment.nr @@ -1,7 +1,9 @@ fn comment1() { // } + // random comment + fn comment2() { // Test } diff --git a/tooling/nargo_fmt/tests/expected/contract.nr b/tooling/nargo_fmt/tests/expected/contract.nr index 189e69b6003..0d9bd2f100d 100644 --- a/tooling/nargo_fmt/tests/expected/contract.nr +++ b/tooling/nargo_fmt/tests/expected/contract.nr @@ -34,11 +34,13 @@ contract Benchmarking { #[aztec(private)] fn constructor() {} + // Nec tincidunt praesent semper feugiat nibh sed pulvinar. Nibh nisl condimentum id venenatis a. #[aztec(private)] fn create_note(owner: Field, value: Field) { increment(storage.notes.at(owner), value, owner); } + // Diam quam nulla porttitor massa id. Elit ullamcorper dignissim cras tincidunt lobortis feugiat. #[aztec(private)] fn recreate_note(owner: Field, index: u32) { @@ -49,6 +51,7 @@ contract Benchmarking { owner_notes.remove(note); increment(owner_notes, note.value, owner); } + // Ultrices in iaculis nunc sed augue lacus. #[aztec(public)] fn increment_balance(owner: Field, value: Field) { @@ -58,6 +61,7 @@ contract Benchmarking { compute_selector("broadcast(Field)"), [owner]); } + // Est ultricies integer quis auctor elit sed. In nibh mauris cursus mattis molestie a iaculis. #[aztec(public)] fn broadcast(owner: Field) { @@ -69,5 +73,6 @@ contract Benchmarking { note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, preimage) } } + // Uses the token bridge contract, which tells which input token we need to talk to and handles the exit funds to L1 contract Uniswap {} diff --git a/tooling/nargo_fmt/tests/expected/global.nr b/tooling/nargo_fmt/tests/expected/global.nr new file mode 100644 index 00000000000..e73cf96ccbe --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/global.nr @@ -0,0 +1,9 @@ +//! Super module :] + +// super global variable +global answer = 42; + +// Super module :] + +// super global variable +global answer = 42; diff --git a/tooling/nargo_fmt/tests/expected/struct.nr b/tooling/nargo_fmt/tests/expected/struct.nr index 6b80cc1d7d6..cf1795892d2 100644 --- a/tooling/nargo_fmt/tests/expected/struct.nr +++ b/tooling/nargo_fmt/tests/expected/struct.nr @@ -55,12 +55,14 @@ fn main(x: Field, y: Field) { assert(p.bar() == x); assert(p.second == y); assert(p.first.array[0] != p.first.array[1]); + // Nested structs let (struct_from_tuple, a_bool) = test_struct_in_tuple(true, x, y); assert(struct_from_tuple.my_bool == true); assert(a_bool == true); assert(struct_from_tuple.my_int == 5); assert(struct_from_tuple.my_nest.a == 0); + // Regression test for issue #670 let Animal { legs, eyes } = get_dog(); let six = legs + eyes as Field; diff --git a/tooling/nargo_fmt/tests/expected/tuple.nr b/tooling/nargo_fmt/tests/expected/tuple.nr index c96cb15e0ad..c3b32904f15 100644 --- a/tooling/nargo_fmt/tests/expected/tuple.nr +++ b/tooling/nargo_fmt/tests/expected/tuple.nr @@ -22,6 +22,7 @@ fn main() { 2); (/*1*/ 1, /*2*/ 2); + // FIXME: (((//2 1,),),); diff --git a/tooling/nargo_fmt/tests/expected/vec.nr b/tooling/nargo_fmt/tests/expected/vec.nr index 43d68e1d1e7..1c9a791961e 100644 --- a/tooling/nargo_fmt/tests/expected/vec.nr +++ b/tooling/nargo_fmt/tests/expected/vec.nr @@ -1,6 +1,7 @@ struct Vec { slice: [T] } + // A mutable vector type implemented as a wrapper around immutable slices. // A separate type is technically not needed but helps differentiate which operations are mutable. impl Vec { diff --git a/tooling/nargo_fmt/tests/input/global.nr b/tooling/nargo_fmt/tests/input/global.nr new file mode 100644 index 00000000000..bf023c61805 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/global.nr @@ -0,0 +1,17 @@ +//! Super module :] + +// super global variable +global answer = 42; + +// Super module :] + + + + + + + + + +// super global variable +global answer = 42; diff --git a/tooling/noir_js/test/node/e2e.test.ts b/tooling/noir_js/test/node/e2e.test.ts index f7c3ea37a1a..33d64377b06 100644 --- a/tooling/noir_js/test/node/e2e.test.ts +++ b/tooling/noir_js/test/node/e2e.test.ts @@ -69,19 +69,7 @@ it('end-to-end proof creation and verification (inner)', async () => { expect(isValid).to.be.true; }); -// The "real" workflow will involve a prover and a verifier on different systems. -// -// We cannot do this in our tests because they will panic with: -// `unreachable` -// -// This happens when we we create a proof with one barretenberg instance and -// try to verify it with another. -// -// If this bug is fixed, we can remove this test and split barretenberg into -// a prover and verifier class to more accurately reflect what happens in production. -// -// If its not fixable, we can leave it in as documentation of this behavior. -it('[BUG] -- bb.js unreachable (different instance) ', async () => { +it('end-to-end proving and verification with different instances', async () => { // Noir.Js part const inputs = { x: '2', @@ -97,16 +85,9 @@ it('[BUG] -- bb.js unreachable (different instance) ', async () => { const proof = await prover.generateFinalProof(witness); - try { - const verifier = new Backend(assert_lt_program); - await verifier.verifyFinalProof(proof); - expect.fail( - 'bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.', - ); - } catch (error) { - const knownError = error as Error; - expect(knownError.message).to.contain('unreachable'); - } + const verifier = new Backend(assert_lt_program); + const proof_is_valid = await verifier.verifyFinalProof(proof); + expect(proof_is_valid).to.be.true; }); // This bug occurs when we use the same backend to create an inner proof and then an outer proof diff --git a/tooling/noir_js_backend_barretenberg/src/index.ts b/tooling/noir_js_backend_barretenberg/src/index.ts index 2e5a44ae8d2..820cda93c83 100644 --- a/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/tooling/noir_js_backend_barretenberg/src/index.ts @@ -39,6 +39,7 @@ export class BarretenbergBackend implements Backend { await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); this.acirComposer = await api.acirNewAcirComposer(subgroupSize); + await api.acirInitProvingKey(this.acirComposer, this.acirUncompressedBytecode); this.api = api; } }