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 f2063c5e4b0..95a1aa502ad 100644 --- a/compiler/integration-tests/test/browser/compile_prove_verify.test.ts +++ b/compiler/integration-tests/test/browser/compile_prove_verify.test.ts @@ -3,7 +3,7 @@ import { Logger } from 'tslog'; import * as TOML from 'smol-toml'; import { initializeResolver } from '@noir-lang/source-resolver'; -import newCompiler, { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; +import newCompiler, { CompiledProgram, compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; import { Noir } from '@noir-lang/noir_js'; import { InputMap } from '@noir-lang/noirc_abi'; import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; @@ -32,7 +32,7 @@ const suite = Mocha.Suite.create(mocha.suite, 'Noir end to end test'); suite.timeout(60 * 20e3); //20mins -async function getCircuit(noirSource: string) { +function getCircuit(noirSource: string): CompiledProgram { // eslint-disable-next-line @typescript-eslint/no-unused-vars initializeResolver((id: string) => { logger.debug('source-resolver: resolving:', id); @@ -40,7 +40,12 @@ async function getCircuit(noirSource: string) { }); // We're ignoring this in the resolver but pass in something sensible. - return compile('/main.nr'); + const result = compile('/main.nr'); + if (!('program' in result)) { + throw new Error('Compilation failed'); + } + + return result.program; } test_cases.forEach((testInfo) => { @@ -51,11 +56,11 @@ test_cases.forEach((testInfo) => { const noir_source = await getFile(`${base_relative_path}/${test_case}/src/main.nr`); - let noir_program; + let noir_program: CompiledProgram; try { - noir_program = await getCircuit(noir_source); + noir_program = getCircuit(noir_source); - expect(await noir_program, 'Compile output ').to.be.an('object'); + expect(noir_program, 'Compile output ').to.be.an('object'); } catch (e) { expect(e, 'Compilation Step').to.not.be.an('error'); throw e; diff --git a/compiler/integration-tests/test/browser/recursion.test.ts b/compiler/integration-tests/test/browser/recursion.test.ts index 6a5592bca67..dbf74882654 100644 --- a/compiler/integration-tests/test/browser/recursion.test.ts +++ b/compiler/integration-tests/test/browser/recursion.test.ts @@ -3,7 +3,7 @@ import { expect } from '@esm-bundle/chai'; import { TEST_LOG_LEVEL } from '../environment.js'; import { Logger } from 'tslog'; import { initializeResolver } from '@noir-lang/source-resolver'; -import newCompiler, { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; +import newCompiler, { CompiledProgram, compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; import { acvm, abi, Noir } from '@noir-lang/noir_js'; import * as TOML from 'smol-toml'; @@ -26,7 +26,7 @@ const base_relative_path = '../../../../..'; const circuit_main = 'compiler/integration-tests/circuits/main'; const circuit_recursion = 'compiler/integration-tests/circuits/recursion'; -async function getCircuit(noirSource: string) { +function getCircuit(noirSource: string): CompiledProgram { // eslint-disable-next-line @typescript-eslint/no-unused-vars initializeResolver((id: string) => { logger.debug('source-resolver: resolving:', id); @@ -34,7 +34,12 @@ async function getCircuit(noirSource: string) { }); // We're ignoring this in the resolver but pass in something sensible. - return compile('./main.nr'); + const result = compile('/main.nr'); + if (!('program' in result)) { + throw new Error('Compilation failed'); + } + + return result.program; } describe('It compiles noir program code, receiving circuit bytes and abi object.', () => { @@ -50,7 +55,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object. }); it('Should generate valid inner proof for correct input, then verify proof within a proof', async () => { - const main_program = await getCircuit(circuit_main_source); + const main_program = getCircuit(circuit_main_source); const main_inputs: InputMap = TOML.parse(circuit_main_toml) as InputMap; const main_backend = new BarretenbergBackend(main_program); 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 038c692220f..738bc2df8dd 100644 --- a/compiler/integration-tests/test/node/smart_contract_verifier.test.ts +++ b/compiler/integration-tests/test/node/smart_contract_verifier.test.ts @@ -33,7 +33,12 @@ test_cases.forEach((testInfo) => { const noir_source_path = resolve(`${base_relative_path}/${test_case}/src/main.nr`); - const noir_program = compile(noir_source_path); + const compileResult = compile(noir_source_path); + if (!('program' in compileResult)) { + throw new Error('Compilation failed'); + } + + const noir_program = compileResult.program; const backend = new BarretenbergBackend(noir_program); const program = new Noir(noir_program, backend); diff --git a/compiler/wasm/src/compile.rs b/compiler/wasm/src/compile.rs index ac6219ee625..e7fd3dd5212 100644 --- a/compiler/wasm/src/compile.rs +++ b/compiler/wasm/src/compile.rs @@ -1,8 +1,9 @@ use fm::FileManager; use gloo_utils::format::JsValueSerdeExt; -use js_sys::Object; +use js_sys::{JsString, Object}; use nargo::artifacts::{ contract::{PreprocessedContract, PreprocessedContractFunction}, + debug::DebugArtifact, program::PreprocessedProgram, }; use noirc_driver::{ @@ -27,6 +28,38 @@ export type DependencyGraph = { root_dependencies: readonly string[]; library_dependencies: Readonly>; } + +export type CompiledContract = { + noir_version: string; + name: string; + backend: string; + functions: Array; + events: Array; +}; + +export type CompiledProgram = { + noir_version: string; + backend: string; + abi: any; + bytecode: string; +} + +export type DebugArtifact = { + debug_symbols: Array; + file_map: Record; + warnings: Array; +}; + +export type CompileResult = ( + | { + contract: CompiledContract; + debug: DebugArtifact; + } + | { + program: CompiledProgram; + debug: DebugArtifact; + } +); "#; #[wasm_bindgen] @@ -34,6 +67,57 @@ extern "C" { #[wasm_bindgen(extends = Object, js_name = "DependencyGraph", typescript_type = "DependencyGraph")] #[derive(Clone, Debug, PartialEq, Eq)] pub type JsDependencyGraph; + + #[wasm_bindgen(extends = Object, js_name = "CompileResult", typescript_type = "CompileResult")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsCompileResult; + + #[wasm_bindgen(constructor, js_class = "Object")] + fn constructor() -> JsCompileResult; +} + +impl JsCompileResult { + const CONTRACT_PROP: &'static str = "contract"; + const PROGRAM_PROP: &'static str = "program"; + const DEBUG_PROP: &'static str = "debug"; + + pub fn new(resp: CompileResult) -> JsCompileResult { + let obj = JsCompileResult::constructor(); + match resp { + CompileResult::Contract { contract, debug } => { + js_sys::Reflect::set( + &obj, + &JsString::from(JsCompileResult::CONTRACT_PROP), + &::from_serde(&contract).unwrap(), + ) + .unwrap(); + + js_sys::Reflect::set( + &obj, + &JsString::from(JsCompileResult::DEBUG_PROP), + &::from_serde(&debug).unwrap(), + ) + .unwrap(); + } + CompileResult::Program { program, debug } => { + js_sys::Reflect::set( + &obj, + &JsString::from(JsCompileResult::PROGRAM_PROP), + &::from_serde(&program).unwrap(), + ) + .unwrap(); + + js_sys::Reflect::set( + &obj, + &JsString::from(JsCompileResult::DEBUG_PROP), + &::from_serde(&debug).unwrap(), + ) + .unwrap(); + } + }; + + obj + } } #[derive(Deserialize)] @@ -42,12 +126,17 @@ struct DependencyGraph { library_dependencies: HashMap>, } +pub enum CompileResult { + Contract { contract: PreprocessedContract, debug: DebugArtifact }, + Program { program: PreprocessedProgram, debug: DebugArtifact }, +} + #[wasm_bindgen] pub fn compile( entry_point: String, contracts: Option, dependency_graph: Option, -) -> Result { +) -> Result { console_error_panic_hook::set_once(); let dependency_graph: DependencyGraph = if let Some(dependency_graph) = dependency_graph { @@ -89,9 +178,8 @@ pub fn compile( nargo::ops::optimize_contract(compiled_contract, np_language, &is_opcode_supported) .expect("Contract optimization failed"); - let preprocessed_contract = preprocess_contract(optimized_contract); - - Ok(::from_serde(&preprocessed_contract).unwrap()) + let compile_output = preprocess_contract(optimized_contract); + Ok(JsCompileResult::new(compile_output)) } else { let compiled_program = compile_main(&mut context, crate_id, &compile_options, None, true) .map_err(|errs| { @@ -107,9 +195,8 @@ pub fn compile( 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()) + let compile_output = preprocess_program(optimized_program); + Ok(JsCompileResult::new(compile_output)) } } @@ -145,17 +232,30 @@ fn add_noir_lib(context: &mut Context, library_name: &CrateName) -> CrateId { prepare_dependency(context, &path_to_lib) } -fn preprocess_program(program: CompiledProgram) -> PreprocessedProgram { - PreprocessedProgram { +fn preprocess_program(program: CompiledProgram) -> CompileResult { + let debug_artifact = DebugArtifact { + debug_symbols: vec![program.debug], + file_map: program.file_map, + warnings: program.warnings, + }; + + let preprocessed_program = PreprocessedProgram { hash: program.hash, backend: String::from(BACKEND_IDENTIFIER), abi: program.abi, noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), bytecode: program.circuit, - } + }; + + CompileResult::Program { program: preprocessed_program, debug: debug_artifact } } -fn preprocess_contract(contract: CompiledContract) -> PreprocessedContract { +fn preprocess_contract(contract: CompiledContract) -> CompileResult { + let debug_artifact = DebugArtifact { + debug_symbols: contract.functions.iter().map(|function| function.debug.clone()).collect(), + file_map: contract.file_map, + warnings: contract.warnings, + }; let preprocessed_functions = contract .functions .into_iter() @@ -168,13 +268,15 @@ fn preprocess_contract(contract: CompiledContract) -> PreprocessedContract { }) .collect(); - PreprocessedContract { + let preprocessed_contract = PreprocessedContract { noir_version: String::from(NOIR_ARTIFACT_VERSION_STRING), name: contract.name, backend: String::from(BACKEND_IDENTIFIER), functions: preprocessed_functions, events: contract.events, - } + }; + + CompileResult::Contract { contract: preprocessed_contract, debug: debug_artifact } } cfg_if::cfg_if! { diff --git a/compiler/wasm/test/browser/index.test.ts b/compiler/wasm/test/browser/index.test.ts index cad2ada0c61..8a3f82ffff9 100644 --- a/compiler/wasm/test/browser/index.test.ts +++ b/compiler/wasm/test/browser/index.test.ts @@ -47,10 +47,14 @@ describe('noir wasm', () => { const wasmCircuit = await compile('/main.nr'); const cliCircuit = await getPrecompiledSource(simpleScriptExpectedArtifact); + if (!('program' in wasmCircuit)) { + throw Error('Expected program to be present'); + } + // We don't expect the hashes to match due to how `noir_wasm` handles dependencies - expect(wasmCircuit.bytecode).to.eq(cliCircuit.bytecode); - expect(wasmCircuit.abi).to.deep.eq(cliCircuit.abi); - expect(wasmCircuit.backend).to.eq(cliCircuit.backend); + expect(wasmCircuit.program.bytecode).to.eq(cliCircuit.bytecode); + expect(wasmCircuit.program.abi).to.deep.eq(cliCircuit.abi); + expect(wasmCircuit.program.backend).to.eq(cliCircuit.backend); }).timeout(20e3); // 20 seconds }); @@ -87,12 +91,16 @@ describe('noir wasm', () => { }, }); + if (!('program' in wasmCircuit)) { + throw Error('Expected program to be present'); + } + const cliCircuit = await getPrecompiledSource(depsScriptExpectedArtifact); // We don't expect the hashes to match due to how `noir_wasm` handles dependencies - expect(wasmCircuit.bytecode).to.eq(cliCircuit.bytecode); - expect(wasmCircuit.abi).to.deep.eq(cliCircuit.abi); - expect(wasmCircuit.backend).to.eq(cliCircuit.backend); + expect(wasmCircuit.program.bytecode).to.eq(cliCircuit.bytecode); + expect(wasmCircuit.program.abi).to.deep.eq(cliCircuit.abi); + expect(wasmCircuit.program.backend).to.eq(cliCircuit.backend); }).timeout(20e3); // 20 seconds }); }); diff --git a/compiler/wasm/test/node/index.test.ts b/compiler/wasm/test/node/index.test.ts index 2b430abdca4..c0d5f88e407 100644 --- a/compiler/wasm/test/node/index.test.ts +++ b/compiler/wasm/test/node/index.test.ts @@ -24,10 +24,14 @@ describe('noir wasm compilation', () => { const wasmCircuit = await compile(join(__dirname, simpleScriptSourcePath)); const cliCircuit = await getPrecompiledSource(simpleScriptExpectedArtifact); + if (!('program' in wasmCircuit)) { + throw Error('Expected program to be present'); + } + // We don't expect the hashes to match due to how `noir_wasm` handles dependencies - expect(wasmCircuit.bytecode).to.eq(cliCircuit.bytecode); - expect(wasmCircuit.abi).to.deep.eq(cliCircuit.abi); - expect(wasmCircuit.backend).to.eq(cliCircuit.backend); + expect(wasmCircuit.program.bytecode).to.eq(cliCircuit.bytecode); + expect(wasmCircuit.program.abi).to.deep.eq(cliCircuit.abi); + expect(wasmCircuit.program.backend).to.eq(cliCircuit.backend); }).timeout(10e3); }); @@ -61,10 +65,14 @@ describe('noir wasm compilation', () => { const cliCircuit = await getPrecompiledSource(depsScriptExpectedArtifact); + if (!('program' in wasmCircuit)) { + throw Error('Expected program to be present'); + } + // We don't expect the hashes to match due to how `noir_wasm` handles dependencies - expect(wasmCircuit.bytecode).to.eq(cliCircuit.bytecode); - expect(wasmCircuit.abi).to.deep.eq(cliCircuit.abi); - expect(wasmCircuit.backend).to.eq(cliCircuit.backend); + expect(wasmCircuit.program.bytecode).to.eq(cliCircuit.bytecode); + expect(wasmCircuit.program.abi).to.deep.eq(cliCircuit.abi); + expect(wasmCircuit.program.backend).to.eq(cliCircuit.backend); }).timeout(10e3); }); });