Skip to content

Commit

Permalink
feat(wasm)!: improve and simplify wasm compiler interface (#2976)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Oct 4, 2023
1 parent c6f660e commit 1b5124b
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 115 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion acvm-repo/acvm_js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion acvm-repo/barretenberg_blackbox_solver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
3 changes: 2 additions & 1 deletion compiler/integration-tests/test/browser/recursion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions compiler/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion compiler/wasm/noir-script/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
fn main(x : u64, y : pub u64) {
assert(x < y);
}
}
148 changes: 54 additions & 94 deletions compiler/wasm/src/compile.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<bool>,
dependencies: Option<StringArray>,
) -> Result<JsValue, JsCompileError> {
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<String> = 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<String>,
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(<JsValue as JsValueSerdeExt>::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(<JsValue as JsValueSerdeExt>::from_serde(&preprocessed_program).unwrap())
}
}

Expand Down Expand Up @@ -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);

<JsValue as JsValueSerdeExt>::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);

<JsValue as JsValueSerdeExt>::from_serde(&preprocessed_program).unwrap()
}
}

fn preprocess_program(program: CompiledProgram) -> PreprocessedProgram {
PreprocessedProgram {
hash: program.hash,
Expand Down
29 changes: 29 additions & 0 deletions compiler/wasm/src/errors.rs
Original file line number Diff line number Diff line change
@@ -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))
}
}
3 changes: 2 additions & 1 deletion compiler/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion compiler/wasm/test/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export async function compileNoirSource(noir_source: string): Promise<any> {

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.');

Expand Down
2 changes: 1 addition & 1 deletion tooling/noirc_abi_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 1b5124b

Please sign in to comment.