diff --git a/Cargo.lock b/Cargo.lock index 347e0b0521f..ac69607c4d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2406,6 +2406,7 @@ dependencies = [ "fm", "getrandom", "gloo-utils", + "js-sys", "log", "nargo", "noirc_driver", diff --git a/Cargo.toml b/Cargo.toml index dcd2c20d195..80848fbcf1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ tower = "0.4" url = "2.2.0" wasm-bindgen = { version = "=0.2.86", features = ["serde-serialize"] } wasm-bindgen-test = "0.3.33" +js-sys = "0.3.62" base64 = "0.21.2" fxhash = "0.2.1" acir = { path = "acvm-repo/acir", default-features = false } diff --git a/acvm-repo/acvm_js/Cargo.toml b/acvm-repo/acvm_js/Cargo.toml index 28d99d243d1..f977eb5234f 100644 --- a/acvm-repo/acvm_js/Cargo.toml +++ b/acvm-repo/acvm_js/Cargo.toml @@ -28,7 +28,7 @@ log = "0.4.17" wasm-logger = "0.2.0" console_error_panic_hook = "0.1.7" gloo-utils = { version = "0.1", features = ["serde"] } -js-sys = "0.3.62" +js-sys.workspace = true const-str = "0.5.5" [build-dependencies] diff --git a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml index a76d6313b4e..acecb24c142 100644 --- a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml +++ b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml @@ -32,7 +32,7 @@ wasmer = { version = "3.3", default-features = false, features = [ getrandom = { version = "0.2", features = ["js"] } wasm-bindgen-futures = "0.4.36" -js-sys = "0.3.62" +js-sys.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] getrandom = "0.2" diff --git a/compiler/integration-tests/test/browser/compile_prove_verify.test.ts b/compiler/integration-tests/test/browser/compile_prove_verify.test.ts index e9d6d3a0f9e..a2d6c7e94ed 100644 --- a/compiler/integration-tests/test/browser/compile_prove_verify.test.ts +++ b/compiler/integration-tests/test/browser/compile_prove_verify.test.ts @@ -38,7 +38,8 @@ async function getCircuit(noirSource: string) { return noirSource; }); - return compile({}); + // We're ignoring this in the resolver but pass in something sensible. + return compile('/main.nr'); } test_cases.forEach((testInfo) => { diff --git a/compiler/integration-tests/test/browser/recursion.test.ts b/compiler/integration-tests/test/browser/recursion.test.ts index 6cfe74ae100..c773e80ea43 100644 --- a/compiler/integration-tests/test/browser/recursion.test.ts +++ b/compiler/integration-tests/test/browser/recursion.test.ts @@ -32,7 +32,8 @@ async function getCircuit(noirSource: string) { return noirSource; }); - return compile({}); + // We're ignoring this in the resolver but pass in something sensible. + return compile('./main.nr'); } describe('It compiles noir program code, receiving circuit bytes and abi object.', () => { diff --git a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts index 0cdebc0f4c7..777ef692876 100644 --- a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts +++ b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts @@ -25,10 +25,6 @@ const test_cases = [ }, ]; -async function getCircuit(entry_point: string) { - return compile({ entry_point }); -} - test_cases.forEach((testInfo) => { const test_name = testInfo.case.split('/').pop(); @@ -38,15 +34,7 @@ test_cases.forEach((testInfo) => { const noir_source_path = resolve(`${base_relative_path}/${test_case}/src/main.nr`); - let noir_program; - try { - noir_program = await getCircuit(noir_source_path); - - expect(await noir_program, 'Compile output ').to.be.an('object'); - } catch (e) { - expect(e, 'Compilation Step').to.not.be.an('error'); - throw e; - } + const noir_program = compile(noir_source_path); const backend = new BarretenbergBackend(noir_program); const program = new Noir(noir_program, backend); diff --git a/compiler/wasm/Cargo.toml b/compiler/wasm/Cargo.toml index ebcfca8b49b..47a0acdf8ac 100644 --- a/compiler/wasm/Cargo.toml +++ b/compiler/wasm/Cargo.toml @@ -19,6 +19,7 @@ noirc_driver.workspace = true noirc_frontend.workspace = true wasm-bindgen.workspace = true serde.workspace = true +js-sys.workspace = true cfg-if.workspace = true console_error_panic_hook = "0.1.7" diff --git a/compiler/wasm/noir-script/src/main.nr b/compiler/wasm/noir-script/src/main.nr index 7f3767f4a48..36fcc1916f5 100644 --- a/compiler/wasm/noir-script/src/main.nr +++ b/compiler/wasm/noir-script/src/main.nr @@ -1,3 +1,3 @@ fn main(x : u64, y : pub u64) { assert(x < y); -} \ No newline at end of file +} diff --git a/compiler/wasm/src/compile.rs b/compiler/wasm/src/compile.rs index b6fc9fad573..fbe6e761fa5 100644 --- a/compiler/wasm/src/compile.rs +++ b/compiler/wasm/src/compile.rs @@ -1,6 +1,6 @@ use fm::FileManager; use gloo_utils::format::JsValueSerdeExt; -use log::debug; +use js_sys::Array; use nargo::artifacts::{ contract::{PreprocessedContract, PreprocessedContractFunction}, program::PreprocessedProgram, @@ -10,56 +10,74 @@ use noirc_driver::{ CompiledContract, CompiledProgram, }; use noirc_frontend::{graph::CrateGraph, hir::Context}; -use serde::{Deserialize, Serialize}; use std::path::Path; use wasm_bindgen::prelude::*; +use crate::errors::JsCompileError; + const BACKEND_IDENTIFIER: &str = "acvm-backend-barretenberg"; -#[derive(Debug, Serialize, Deserialize)] -pub struct WASMCompileOptions { - #[serde(default = "default_entry_point")] +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Array, js_name = "StringArray", typescript_type = "string[]")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type StringArray; +} + +#[wasm_bindgen] +pub fn compile( entry_point: String, + contracts: Option, + dependencies: Option, +) -> Result { + console_error_panic_hook::set_once(); - #[serde(default = "default_circuit_name")] - circuit_name: String, + let root = Path::new("/"); + let fm = FileManager::new(root, Box::new(get_non_stdlib_asset)); + let graph = CrateGraph::default(); + let mut context = Context::new(fm, graph); - // Compile each contract function used within the program - #[serde(default = "bool::default")] - contracts: bool, + let path = Path::new(&entry_point); + let crate_id = prepare_crate(&mut context, path); - #[serde(default)] - compile_options: CompileOptions, + let dependencies: Vec = dependencies + .map(|array| array.iter().map(|element| element.as_string().unwrap()).collect()) + .unwrap_or_default(); + for dependency in dependencies { + add_noir_lib(&mut context, dependency.as_str()); + } - #[serde(default)] - optional_dependencies_set: Vec, + let compile_options = CompileOptions::default(); - #[serde(default = "default_log_level")] - log_level: String, -} + // For now we default to plonk width = 3, though we can add it as a parameter + let np_language = acvm::Language::PLONKCSat { width: 3 }; + #[allow(deprecated)] + let is_opcode_supported = acvm::pwg::default_is_opcode_supported(np_language); -fn default_log_level() -> String { - String::from("info") -} + if contracts.unwrap_or_default() { + let compiled_contract = compile_contract(&mut context, crate_id, &compile_options) + .map_err(|_| JsCompileError::new("Failed to compile contract".to_string()))? + .0; -fn default_circuit_name() -> String { - String::from("contract") -} + let optimized_contract = + nargo::ops::optimize_contract(compiled_contract, np_language, &is_opcode_supported) + .expect("Contract optimization failed"); -fn default_entry_point() -> String { - String::from("main.nr") -} + let preprocessed_contract = preprocess_contract(optimized_contract); -impl Default for WASMCompileOptions { - fn default() -> Self { - Self { - entry_point: default_entry_point(), - circuit_name: default_circuit_name(), - log_level: default_log_level(), - contracts: false, - compile_options: CompileOptions::default(), - optional_dependencies_set: vec![], - } + Ok(::from_serde(&preprocessed_contract).unwrap()) + } else { + let compiled_program = compile_main(&mut context, crate_id, &compile_options, None, true) + .map_err(|_| JsCompileError::new("Failed to compile program".to_string()))? + .0; + + let optimized_program = + nargo::ops::optimize_program(compiled_program, np_language, &is_opcode_supported) + .expect("Program optimization failed"); + + let preprocessed_program = preprocess_program(optimized_program); + + Ok(::from_serde(&preprocessed_program).unwrap()) } } @@ -91,64 +109,6 @@ fn add_noir_lib(context: &mut Context, library_name: &str) { } } -#[wasm_bindgen] -pub fn compile(args: JsValue) -> JsValue { - console_error_panic_hook::set_once(); - - let options: WASMCompileOptions = if args.is_undefined() || args.is_null() { - debug!("Initializing compiler with default values."); - WASMCompileOptions::default() - } else { - JsValueSerdeExt::into_serde(&args).expect("Could not deserialize compile arguments") - }; - - debug!("Compiler configuration {:?}", &options); - - let root = Path::new("/"); - let fm = FileManager::new(root, Box::new(get_non_stdlib_asset)); - let graph = CrateGraph::default(); - let mut context = Context::new(fm, graph); - - let path = Path::new(&options.entry_point); - let crate_id = prepare_crate(&mut context, path); - - for dependency in options.optional_dependencies_set { - add_noir_lib(&mut context, dependency.as_str()); - } - - // For now we default to plonk width = 3, though we can add it as a parameter - let np_language = acvm::Language::PLONKCSat { width: 3 }; - #[allow(deprecated)] - let is_opcode_supported = acvm::pwg::default_is_opcode_supported(np_language); - - if options.contracts { - let compiled_contract = compile_contract(&mut context, crate_id, &options.compile_options) - .expect("Contract compilation failed") - .0; - - let optimized_contract = - nargo::ops::optimize_contract(compiled_contract, np_language, &is_opcode_supported) - .expect("Contract optimization failed"); - - let preprocessed_contract = preprocess_contract(optimized_contract); - - ::from_serde(&preprocessed_contract).unwrap() - } else { - let compiled_program = - compile_main(&mut context, crate_id, &options.compile_options, None, true) - .expect("Compilation failed") - .0; - - let optimized_program = - nargo::ops::optimize_program(compiled_program, np_language, &is_opcode_supported) - .expect("Program optimization failed"); - - let preprocessed_program = preprocess_program(optimized_program); - - ::from_serde(&preprocessed_program).unwrap() - } -} - fn preprocess_program(program: CompiledProgram) -> PreprocessedProgram { PreprocessedProgram { hash: program.hash, diff --git a/compiler/wasm/src/errors.rs b/compiler/wasm/src/errors.rs new file mode 100644 index 00000000000..6aa70dafa90 --- /dev/null +++ b/compiler/wasm/src/errors.rs @@ -0,0 +1,29 @@ +use js_sys::JsString; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const COMPILE_ERROR: &'static str = r#" +export type CompileError = Error; +"#; + +/// `CompileError` is a raw js error. +/// It'd be ideal that `CompileError` was a subclass of `Error`, but for that we'd need to use JS snippets or a js module. +/// Currently JS snippets don't work with a nodejs target. And a module would be too much for just a custom error type. +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = js_sys::Error, js_name = "CompileError", typescript_type = "CompileError")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsCompileError; + + #[wasm_bindgen(constructor, js_class = "Error")] + fn constructor(message: JsString) -> JsCompileError; +} + +impl JsCompileError { + /// Creates a new execution error with the given call stack. + /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. + pub fn new(message: String) -> Self { + JsCompileError::constructor(JsString::from(message)) + } +} diff --git a/compiler/wasm/src/lib.rs b/compiler/wasm/src/lib.rs index e3a2a5fc340..3a8e00bc6dd 100644 --- a/compiler/wasm/src/lib.rs +++ b/compiler/wasm/src/lib.rs @@ -13,9 +13,10 @@ use wasm_bindgen::prelude::*; mod circuit; mod compile; +mod errors; pub use circuit::{acir_read_bytes, acir_write_bytes}; -pub use compile::{compile, WASMCompileOptions}; +pub use compile::compile; #[derive(Serialize, Deserialize)] pub struct BuildInfo { diff --git a/compiler/wasm/test/shared.ts b/compiler/wasm/test/shared.ts index eb5b414f2b5..91f420741cb 100644 --- a/compiler/wasm/test/shared.ts +++ b/compiler/wasm/test/shared.ts @@ -15,13 +15,15 @@ export async function compileNoirSource(noir_source: string): Promise { if (typeof source === 'undefined') { throw Error(`Could not resolve source for '${id}'`); + } else if (id !== '/main.nr') { + throw Error(`Unexpected id: '${id}'`); } else { return source; } }); try { - const compiled_noir = compile({}); + const compiled_noir = compile('main.nr'); console.log('Noir source compilation done.'); diff --git a/tooling/noirc_abi_wasm/Cargo.toml b/tooling/noirc_abi_wasm/Cargo.toml index 3383d3f21e8..130f022dd1f 100644 --- a/tooling/noirc_abi_wasm/Cargo.toml +++ b/tooling/noirc_abi_wasm/Cargo.toml @@ -17,11 +17,11 @@ noirc_abi.workspace = true iter-extended.workspace = true wasm-bindgen.workspace = true serde.workspace = true +js-sys.workspace = true console_error_panic_hook = "0.1.7" gloo-utils = { version = "0.1", features = ["serde"] } -js-sys = "0.3.62" # This is an unused dependency, we are adding it # so that we can enable the js feature in getrandom.