Skip to content

Commit

Permalink
feat!: noir-wasm outputs debug symbols (#3317)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr authored Oct 30, 2023
1 parent fab020c commit f9933fa
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -32,15 +32,20 @@ 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);
return noirSource;
});

// 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) => {
Expand All @@ -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;
Expand Down
13 changes: 9 additions & 4 deletions compiler/integration-tests/test/browser/recursion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,15 +26,20 @@ 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);
return noirSource;
});

// 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.', () => {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
130 changes: 116 additions & 14 deletions compiler/wasm/src/compile.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -27,13 +28,96 @@ export type DependencyGraph = {
root_dependencies: readonly string[];
library_dependencies: Readonly<Record<string, readonly string[]>>;
}
export type CompiledContract = {
noir_version: string;
name: string;
backend: string;
functions: Array<any>;
events: Array<any>;
};
export type CompiledProgram = {
noir_version: string;
backend: string;
abi: any;
bytecode: string;
}
export type DebugArtifact = {
debug_symbols: Array<any>;
file_map: Record<number, any>;
warnings: Array<any>;
};
export type CompileResult = (
| {
contract: CompiledContract;
debug: DebugArtifact;
}
| {
program: CompiledProgram;
debug: DebugArtifact;
}
);
"#;

#[wasm_bindgen]
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),
&<JsValue as JsValueSerdeExt>::from_serde(&contract).unwrap(),
)
.unwrap();

js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileResult::DEBUG_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&debug).unwrap(),
)
.unwrap();
}
CompileResult::Program { program, debug } => {
js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileResult::PROGRAM_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&program).unwrap(),
)
.unwrap();

js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileResult::DEBUG_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&debug).unwrap(),
)
.unwrap();
}
};

obj
}
}

#[derive(Deserialize)]
Expand All @@ -42,12 +126,17 @@ struct DependencyGraph {
library_dependencies: HashMap<CrateName, Vec<CrateName>>,
}

pub enum CompileResult {
Contract { contract: PreprocessedContract, debug: DebugArtifact },
Program { program: PreprocessedProgram, debug: DebugArtifact },
}

#[wasm_bindgen]
pub fn compile(
entry_point: String,
contracts: Option<bool>,
dependency_graph: Option<JsDependencyGraph>,
) -> Result<JsValue, JsCompileError> {
) -> Result<JsCompileResult, JsCompileError> {
console_error_panic_hook::set_once();

let dependency_graph: DependencyGraph = if let Some(dependency_graph) = dependency_graph {
Expand Down Expand Up @@ -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(<JsValue as JsValueSerdeExt>::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| {
Expand All @@ -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(<JsValue as JsValueSerdeExt>::from_serde(&preprocessed_program).unwrap())
let compile_output = preprocess_program(optimized_program);
Ok(JsCompileResult::new(compile_output))
}
}

Expand Down Expand Up @@ -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()
Expand All @@ -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! {
Expand Down
20 changes: 14 additions & 6 deletions compiler/wasm/test/browser/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down Expand Up @@ -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
});
});
20 changes: 14 additions & 6 deletions compiler/wasm/test/node/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

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

0 comments on commit f9933fa

Please sign in to comment.