Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: noir-wasm outputs debug symbols #3317

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
Loading