Skip to content

Commit

Permalink
feat: expose separate functions to compile programs vs contracts in `…
Browse files Browse the repository at this point in the history
…noir_wasm` (#4413)

# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## Summary\*

This PR exposes separate functions to compile contracts vs programs in
the wasm compiler. This allows us to simplify various code paths as we
don't need to deal with the potential for the two artifact types as this
just leads to us asserting types and breaking type safety.

## Additional Context



## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
TomAFrench authored Feb 26, 2024
1 parent 29e9b5e commit 7cd5fdb
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 194 deletions.
212 changes: 115 additions & 97 deletions compiler/wasm/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use nargo::artifacts::{
program::ProgramArtifact,
};
use noirc_driver::{
add_dep, compile_contract, compile_main, file_manager_with_stdlib, prepare_crate,
prepare_dependency, CompileOptions, CompiledContract, CompiledProgram,
add_dep, file_manager_with_stdlib, prepare_crate, prepare_dependency, CompileOptions,
NOIR_ARTIFACT_VERSION_STRING,
};
use noirc_evaluator::errors::SsaReport;
Expand Down Expand Up @@ -60,51 +59,64 @@ extern "C" {
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsDependencyGraph;

#[wasm_bindgen(extends = Object, js_name = "CompileResult", typescript_type = "CompileResult")]
#[wasm_bindgen(extends = Object, js_name = "ProgramCompileResult", typescript_type = "ProgramCompileResult")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsCompileResult;
pub type JsCompileProgramResult;

#[wasm_bindgen(constructor, js_class = "Object")]
fn constructor() -> JsCompileResult;
fn constructor() -> JsCompileProgramResult;

#[wasm_bindgen(extends = Object, js_name = "ContractCompileResult", typescript_type = "ContractCompileResult")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsCompileContractResult;

#[wasm_bindgen(constructor, js_class = "Object")]
fn constructor() -> JsCompileContractResult;
}

impl JsCompileResult {
const CONTRACT_PROP: &'static str = "contract";
impl JsCompileProgramResult {
const PROGRAM_PROP: &'static str = "program";
const WARNINGS_PROP: &'static str = "warnings";

pub fn new(resp: CompileResult) -> JsCompileResult {
let obj = JsCompileResult::constructor();
match resp {
CompileResult::Contract { contract, warnings } => {
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::WARNINGS_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&warnings).unwrap(),
)
.unwrap();
}
CompileResult::Program { program, warnings } => {
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::WARNINGS_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&warnings).unwrap(),
)
.unwrap();
}
};
pub fn new(program: ProgramArtifact, warnings: Vec<SsaReport>) -> JsCompileProgramResult {
let obj = JsCompileProgramResult::constructor();

js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileProgramResult::PROGRAM_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&program).unwrap(),
)
.unwrap();
js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileProgramResult::WARNINGS_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&warnings).unwrap(),
)
.unwrap();

obj
}
}

impl JsCompileContractResult {
const CONTRACT_PROP: &'static str = "contract";
const WARNINGS_PROP: &'static str = "warnings";

pub fn new(contract: ContractArtifact, warnings: Vec<SsaReport>) -> JsCompileContractResult {
let obj = JsCompileContractResult::constructor();

js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileContractResult::CONTRACT_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&contract).unwrap(),
)
.unwrap();
js_sys::Reflect::set(
&obj,
&JsString::from(JsCompileContractResult::WARNINGS_PROP),
&<JsValue as JsValueSerdeExt>::from_serde(&warnings).unwrap(),
)
.unwrap();

obj
}
Expand Down Expand Up @@ -144,73 +156,98 @@ pub(crate) fn parse_all(fm: &FileManager) -> ParsedFiles {
fm.as_file_map().all_file_ids().map(|&file_id| (file_id, parse_file(fm, file_id))).collect()
}

pub enum CompileResult {
Contract { contract: ContractArtifact, warnings: Vec<SsaReport> },
Program { program: ProgramArtifact, warnings: Vec<SsaReport> },
}

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

let dependency_graph: DependencyGraph = if let Some(dependency_graph) = dependency_graph {
<JsValue as JsValueSerdeExt>::into_serde(&JsValue::from(dependency_graph))
.map_err(|err| err.to_string())?
} else {
DependencyGraph { root_dependencies: vec![], library_dependencies: HashMap::new() }
};

let fm = file_manager_with_source_map(file_source_map);
let parsed_files = parse_all(&fm);
let mut context = Context::new(fm, parsed_files);

let path = Path::new(&entry_point);
let crate_id = prepare_crate(&mut context, path);

process_dependency_graph(&mut context, dependency_graph);
let (crate_id, mut context) = prepare_context(entry_point, dependency_graph, file_source_map)?;

let compile_options = CompileOptions::default();

// For now we default to a bounded width of 3, though we can add it as a parameter
let expression_width = acvm::acir::circuit::ExpressionWidth::Bounded { width: 3 };

if contracts.unwrap_or_default() {
let compiled_contract = compile_contract(&mut context, crate_id, &compile_options)
let compiled_program =
noirc_driver::compile_main(&mut context, crate_id, &compile_options, None)
.map_err(|errs| {
CompileError::with_file_diagnostics(
"Failed to compile contract",
"Failed to compile program",
errs,
&context.file_manager,
)
})?
.0;

let optimized_contract =
nargo::ops::transform_contract(compiled_contract, expression_width);
let optimized_program = nargo::ops::transform_program(compiled_program, expression_width);
let warnings = optimized_program.warnings.clone();

let compile_output = generate_contract_artifact(optimized_contract);
Ok(JsCompileResult::new(compile_output))
} else {
let compiled_program = compile_main(&mut context, crate_id, &compile_options, None)
Ok(JsCompileProgramResult::new(optimized_program.into(), warnings))
}

#[wasm_bindgen]
pub fn compile_contract(
entry_point: String,
dependency_graph: Option<JsDependencyGraph>,
file_source_map: PathToFileSourceMap,
) -> Result<JsCompileContractResult, JsCompileError> {
console_error_panic_hook::set_once();
let (crate_id, mut context) = prepare_context(entry_point, dependency_graph, file_source_map)?;

let compile_options = CompileOptions::default();
// For now we default to a bounded width of 3, though we can add it as a parameter
let expression_width = acvm::acir::circuit::ExpressionWidth::Bounded { width: 3 };

let compiled_contract =
noirc_driver::compile_contract(&mut context, crate_id, &compile_options)
.map_err(|errs| {
CompileError::with_file_diagnostics(
"Failed to compile program",
"Failed to compile contract",
errs,
&context.file_manager,
)
})?
.0;

let optimized_program = nargo::ops::transform_program(compiled_program, expression_width);
let optimized_contract = nargo::ops::transform_contract(compiled_contract, expression_width);

let compile_output = generate_program_artifact(optimized_program);
Ok(JsCompileResult::new(compile_output))
}
let functions =
optimized_contract.functions.into_iter().map(ContractFunctionArtifact::from).collect();

let contract_artifact = ContractArtifact {
noir_version: String::from(NOIR_ARTIFACT_VERSION_STRING),
name: optimized_contract.name,
functions,
events: optimized_contract.events,
file_map: optimized_contract.file_map,
};

Ok(JsCompileContractResult::new(contract_artifact, optimized_contract.warnings))
}

fn prepare_context(
entry_point: String,
dependency_graph: Option<JsDependencyGraph>,
file_source_map: PathToFileSourceMap,
) -> Result<(CrateId, Context<'static, 'static>), JsCompileError> {
let dependency_graph: DependencyGraph = if let Some(dependency_graph) = dependency_graph {
<JsValue as JsValueSerdeExt>::into_serde(&JsValue::from(dependency_graph))
.map_err(|err| err.to_string())?
} else {
DependencyGraph { root_dependencies: vec![], library_dependencies: HashMap::new() }
};

let fm = file_manager_with_source_map(file_source_map);
let parsed_files = parse_all(&fm);
let mut context = Context::new(fm, parsed_files);

let path = Path::new(&entry_point);
let crate_id = prepare_crate(&mut context, path);

process_dependency_graph(&mut context, dependency_graph);

Ok((crate_id, context))
}

// Create a new FileManager with the given source map
Expand Down Expand Up @@ -270,25 +307,6 @@ fn add_noir_lib(context: &mut Context, library_name: &CrateName) -> CrateId {
prepare_dependency(context, &path_to_lib)
}

pub(crate) fn generate_program_artifact(program: CompiledProgram) -> CompileResult {
let warnings = program.warnings.clone();
CompileResult::Program { program: program.into(), warnings }
}

pub(crate) fn generate_contract_artifact(contract: CompiledContract) -> CompileResult {
let functions = contract.functions.into_iter().map(ContractFunctionArtifact::from).collect();

let contract_artifact = ContractArtifact {
noir_version: String::from(NOIR_ARTIFACT_VERSION_STRING),
name: contract.name,
functions,
events: contract.events,
file_map: contract.file_map,
};

CompileResult::Contract { contract: contract_artifact, warnings: contract.warnings }
}

#[cfg(test)]
mod test {
use noirc_driver::prepare_crate;
Expand Down
Loading

0 comments on commit 7cd5fdb

Please sign in to comment.