-
Notifications
You must be signed in to change notification settings - Fork 387
feat: Add context-centric based API for noir_wasm #3798
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
Merged
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
7e0a3d1
make `find_module` be an immutable reference
kevaundray b2ff05b
add method in nargo that pre-populates the FileManager
kevaundray 5592348
change add_file to `name_to_id` -- we assume that the file manager ha…
kevaundray d5fd4e0
cargo
kevaundray 2f137b7
Update tooling/nargo/src/lib.rs
kevaundray 41ae81f
add a method in file manager that allows us to add a file with its so…
kevaundray c7f576c
add deprecation TODO to file_reader
kevaundray a2460e6
add stdlib file in noirc_driver that returns the stdlib paths alongsi…
kevaundray 270a0f4
add the contents of the stdlib whenever we call prepare_crate
kevaundray 52d33a5
cargo
kevaundray a38c27c
cargo fmt
kevaundray 6a5bf33
move tempfile to dev dependencies
kevaundray f72ce99
add files into file manager in test since find_module does not add fi…
kevaundray b30ad54
insert all files for this packages dependencies into the file manager…
kevaundray 15a94ee
cargo fmt
kevaundray 4599361
remove un-needed fully qualified path
kevaundray f20109e
Add note on stdlib
kevaundray 4413350
cargo fmt
kevaundray d6a24e8
add comment to process_dep_graph and fix setup_test_context
kevaundray 552a4dd
replace expect with unwrap_or_else: expect doesn't allow you to add p…
kevaundray 3f8551b
try: add a `PathToFileSourceMap` object
kevaundray dd94b46
remove extraneous forward slash
kevaundray 3495de5
add file_manager_with_source_map method so we ensure that FileManager…
kevaundray 851fab1
add expect
kevaundray 3cd35fd
fix node tests
kevaundray d2590d5
fix browser tests and naming nit
kevaundray 5b867d5
chore!: Remove `add_file` and `file_reader` from FileManager (#3762)
kevaundray 5ebf83a
Update compiler/fm/src/lib.rs
kevaundray 7a787d7
Merge branch 'master' into kw/make-fm-read-only
kevaundray 419273e
file_reader is no longer being used
kevaundray d120852
Merge branch 'master' into kw/make-fm-read-only
kevaundray 1e9ac97
chore: add simple doc comments for `add_file_with_source` methods
TomAFrench 2350034
add `compile_new` file which uses a more context based API
kevaundray f472188
modify visibility so we can re-use methods in compile.rs
kevaundray dd29cf9
cargo fmt
kevaundray 4f18e19
expose API
kevaundray 403bcde
Rename structs so we don't have the Wrapper postfix
kevaundray 5d8bd90
cargo fmt
kevaundray 9e0e5a5
allow program width to be configurable
kevaundray 994e1c2
Merge remote-tracking branch 'origin/master' into kw/add-context-base…
kevaundray 97b9325
fix bad merge
kevaundray 4b5a348
Merge remote-tracking branch 'origin/master' into kw/add-context-base…
kevaundray e282753
rename ContextWrapper to CompilerContext
kevaundray 067bc71
rename export
kevaundray 915f309
add js test
kevaundray 0d42d09
cargo fmt
kevaundray 9ab0dac
fix: typo
kevaundray 6dd1764
make DependencyGraph pub(crate) visibility, so it can be used to make…
kevaundray 4de1c7f
add compile_ method
kevaundray 54ed150
expose compile_ method
kevaundray 9e99c4d
add test for compile_
kevaundray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,336 @@ | ||
| use crate::compile::{ | ||
| file_manager_with_source_map, preprocess_contract, preprocess_program, JsCompileResult, | ||
| PathToFileSourceMap, | ||
| }; | ||
| use crate::errors::{CompileError, JsCompileError}; | ||
| use noirc_driver::{ | ||
| add_dep, compile_contract, compile_main, prepare_crate, prepare_dependency, CompileOptions, | ||
| }; | ||
| use noirc_frontend::{ | ||
| graph::{CrateGraph, CrateId, CrateName}, | ||
| hir::Context, | ||
| }; | ||
| use std::path::Path; | ||
| use wasm_bindgen::prelude::wasm_bindgen; | ||
|
|
||
| /// This is a wrapper class that is wasm-bindgen compatible | ||
| /// We do not use js_name and rename it like CrateId because | ||
| /// then the impl block is not picked up in javascript. | ||
| #[wasm_bindgen] | ||
| pub struct CompilerContext { | ||
| context: Context, | ||
| } | ||
|
|
||
| #[wasm_bindgen(js_name = "CrateId")] | ||
| #[derive(Debug, Copy, Clone)] | ||
| pub struct CrateIDWrapper(CrateId); | ||
|
kevaundray marked this conversation as resolved.
|
||
|
|
||
| #[wasm_bindgen] | ||
| impl CompilerContext { | ||
| #[wasm_bindgen(constructor)] | ||
| pub fn new(source_map: PathToFileSourceMap) -> CompilerContext { | ||
| console_error_panic_hook::set_once(); | ||
|
|
||
| let fm = file_manager_with_source_map(source_map); | ||
| let graph = CrateGraph::default(); | ||
| CompilerContext { context: Context::new(fm, graph) } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| pub(crate) fn crate_graph(&self) -> &CrateGraph { | ||
| &self.context.crate_graph | ||
| } | ||
| #[cfg(test)] | ||
| pub(crate) fn root_crate_id(&self) -> CrateIDWrapper { | ||
| CrateIDWrapper(*self.context.root_crate_id()) | ||
| } | ||
|
|
||
| // Processes the root crate by adding it to the package graph and automatically | ||
| // importing the stdlib as a dependency for it. | ||
| // | ||
| // Its ID in the package graph is returned | ||
| pub fn process_root_crate(&mut self, path_to_crate: String) -> CrateIDWrapper { | ||
| let path_to_crate = Path::new(&path_to_crate); | ||
|
|
||
| // Adds the root crate to the crate graph and returns its crate id | ||
| CrateIDWrapper(prepare_crate(&mut self.context, path_to_crate)) | ||
| } | ||
|
|
||
| pub fn process_dependency_crate(&mut self, path_to_crate: String) -> CrateIDWrapper { | ||
| let path_to_crate = Path::new(&path_to_crate); | ||
|
|
||
| // Adds the root crate to the crate graph and returns its crate id | ||
| CrateIDWrapper(prepare_dependency(&mut self.context, path_to_crate)) | ||
| } | ||
|
|
||
| // Adds a named edge from one crate to the other. | ||
| // | ||
| // For example, lets say we have two crates CrateId1 and CrateId2 | ||
| // This function will add an edge from CrateId1 to CrateId2 and the edge will be named `crate_name` | ||
| // | ||
| // This essentially says that CrateId1 depends on CrateId2 and the dependency is named `crate_name` | ||
| // | ||
| // We pass references to &CrateIdWrapper even though it is a copy because Rust's move semantics are | ||
| // not respected once we use javascript. ie it will actually allocated a new object in javascript | ||
| // then deallocate that object if we do not pass as a reference. | ||
| pub fn add_dependency_edge( | ||
| &mut self, | ||
| crate_name: String, | ||
| from: &CrateIDWrapper, | ||
| to: &CrateIDWrapper, | ||
| ) { | ||
| let parsed_crate_name: CrateName = crate_name | ||
| .parse() | ||
| .unwrap_or_else(|_| panic!("Failed to parse crate name {}", crate_name)); | ||
| add_dep(&mut self.context, from.0, to.0, parsed_crate_name); | ||
| } | ||
|
|
||
| pub fn compile_program( | ||
| mut self, | ||
| program_width: usize, | ||
| ) -> Result<JsCompileResult, JsCompileError> { | ||
| let compile_options = CompileOptions::default(); | ||
| let np_language = acvm::Language::PLONKCSat { width: program_width }; | ||
|
|
||
| let root_crate_id = *self.context.root_crate_id(); | ||
|
|
||
| let compiled_program = | ||
| compile_main(&mut self.context, root_crate_id, &compile_options, None, true) | ||
| .map_err(|errs| { | ||
| CompileError::with_file_diagnostics( | ||
| "Failed to compile program", | ||
| errs, | ||
| &self.context.file_manager, | ||
| ) | ||
| })? | ||
| .0; | ||
|
|
||
| let optimized_program = nargo::ops::optimize_program(compiled_program, np_language); | ||
|
|
||
| let compile_output = preprocess_program(optimized_program); | ||
| Ok(JsCompileResult::new(compile_output)) | ||
| } | ||
|
|
||
| pub fn compile_contract( | ||
| mut self, | ||
| program_width: usize, | ||
| ) -> Result<JsCompileResult, JsCompileError> { | ||
| let compile_options = CompileOptions::default(); | ||
| let np_language = acvm::Language::PLONKCSat { width: program_width }; | ||
| let root_crate_id = *self.context.root_crate_id(); | ||
|
|
||
| let compiled_contract = | ||
| compile_contract(&mut self.context, root_crate_id, &compile_options) | ||
| .map_err(|errs| { | ||
| CompileError::with_file_diagnostics( | ||
| "Failed to compile contract", | ||
| errs, | ||
| &self.context.file_manager, | ||
| ) | ||
| })? | ||
| .0; | ||
|
|
||
| let optimized_contract = nargo::ops::optimize_contract(compiled_contract, np_language); | ||
|
|
||
| let compile_output = preprocess_contract(optimized_contract); | ||
| Ok(JsCompileResult::new(compile_output)) | ||
|
kevaundray marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| /// This is a method that exposes the same API as `compile` | ||
| /// But uses the Context based APi internally | ||
| #[wasm_bindgen] | ||
| pub fn compile_( | ||
| entry_point: String, | ||
| contracts: Option<bool>, | ||
| dependency_graph: Option<crate::compile::JsDependencyGraph>, | ||
| file_source_map: PathToFileSourceMap, | ||
| ) -> Result<JsCompileResult, JsCompileError> { | ||
| use std::collections::HashMap; | ||
|
|
||
| console_error_panic_hook::set_once(); | ||
|
|
||
| let dependency_graph: crate::compile::DependencyGraph = | ||
| if let Some(dependency_graph) = dependency_graph { | ||
| <wasm_bindgen::JsValue as gloo_utils::format::JsValueSerdeExt>::into_serde( | ||
| &wasm_bindgen::JsValue::from(dependency_graph), | ||
| ) | ||
| .map_err(|err| err.to_string())? | ||
| } else { | ||
| crate::compile::DependencyGraph::default() | ||
| }; | ||
|
|
||
| let mut compiler_context = CompilerContext::new(file_source_map); | ||
|
|
||
| // Set the root crate | ||
| let root_id = compiler_context.process_root_crate(entry_point.clone()); | ||
|
|
||
| let add_noir_lib = |context: &mut CompilerContext, lib_name: &CrateName| -> CrateIDWrapper { | ||
| let lib_name_string = lib_name.to_string(); | ||
| let path_to_lib = Path::new(&lib_name_string) | ||
| .join("lib.nr") | ||
| .to_str() | ||
| .expect("paths are expected to be valid utf-8") | ||
| .to_string(); | ||
| context.process_dependency_crate(path_to_lib) | ||
| }; | ||
|
|
||
| // Add the dependency graph | ||
| let mut crate_names: HashMap<CrateName, CrateIDWrapper> = HashMap::new(); | ||
| // | ||
| // Process the direct dependencies of the root | ||
| for lib_name in dependency_graph.root_dependencies { | ||
| let lib_name_string = lib_name.to_string(); | ||
|
|
||
| let crate_id = add_noir_lib(&mut compiler_context, &lib_name); | ||
|
|
||
| crate_names.insert(lib_name.clone(), crate_id); | ||
|
|
||
| // Add the dependency edges | ||
| compiler_context.add_dependency_edge(lib_name_string, &root_id, &crate_id); | ||
| } | ||
|
|
||
| // Process the transitive dependencies of the root | ||
| for (lib_name, dependencies) in &dependency_graph.library_dependencies { | ||
| // first create the library crate if needed | ||
| // this crate might not have been registered yet because of the order of the HashMap | ||
| // e.g. {root: [lib1], libs: { lib2 -> [lib3], lib1 -> [lib2] }} | ||
| let crate_id = *crate_names | ||
| .entry(lib_name.clone()) | ||
| .or_insert_with(|| add_noir_lib(&mut compiler_context, lib_name)); | ||
|
|
||
| for dependency_name in dependencies { | ||
| let dependency_name_string = dependency_name.to_string(); | ||
|
|
||
| let dep_crate_id = crate_names | ||
| .entry(dependency_name.clone()) | ||
| .or_insert_with(|| add_noir_lib(&mut compiler_context, dependency_name)); | ||
|
|
||
| compiler_context.add_dependency_edge(dependency_name_string, &crate_id, dep_crate_id); | ||
| } | ||
| } | ||
|
|
||
| let is_contract = contracts.unwrap_or(false); | ||
| let program_width = 3; | ||
|
|
||
| if is_contract { | ||
| compiler_context.compile_contract(program_width) | ||
| } else { | ||
| compiler_context.compile_program(program_width) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use noirc_driver::prepare_crate; | ||
| use noirc_frontend::{graph::CrateGraph, hir::Context}; | ||
|
|
||
| use crate::compile::{file_manager_with_source_map, PathToFileSourceMap}; | ||
|
|
||
| use std::path::Path; | ||
|
|
||
| use super::CompilerContext; | ||
|
|
||
| fn setup_test_context(source_map: PathToFileSourceMap) -> CompilerContext { | ||
| let mut fm = file_manager_with_source_map(source_map); | ||
| // Add this due to us calling prepare_crate on "/main.nr" below | ||
| fm.add_file_with_source(Path::new("/main.nr"), "fn foo() {}".to_string()); | ||
|
|
||
| let graph = CrateGraph::default(); | ||
| let mut context = Context::new(fm, graph); | ||
| prepare_crate(&mut context, Path::new("/main.nr")); | ||
|
|
||
| CompilerContext { context } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_works_with_empty_dependency_graph() { | ||
|
TomAFrench marked this conversation as resolved.
|
||
| let source_map = PathToFileSourceMap::default(); | ||
| let context = setup_test_context(source_map); | ||
|
|
||
| // one stdlib + one root crate | ||
| assert_eq!(context.crate_graph().number_of_crates(), 2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_works_with_root_dependencies() { | ||
| let source_map = PathToFileSourceMap( | ||
| vec![(Path::new("lib1/lib.nr").to_path_buf(), "fn foo() {}".to_string())] | ||
| .into_iter() | ||
| .collect(), | ||
| ); | ||
|
|
||
| let mut context = setup_test_context(source_map); | ||
| context.process_dependency_crate("lib1/lib.nr".to_string()); | ||
|
|
||
| assert_eq!(context.crate_graph().number_of_crates(), 3); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_works_with_duplicate_root_dependencies() { | ||
| let source_map = PathToFileSourceMap( | ||
| vec![(Path::new("lib1/lib.nr").to_path_buf(), "fn foo() {}".to_string())] | ||
| .into_iter() | ||
| .collect(), | ||
| ); | ||
| let mut context = setup_test_context(source_map); | ||
|
|
||
| let lib1_crate_id = context.process_dependency_crate("lib1/lib.nr".to_string()); | ||
| let root_crate_id = context.root_crate_id(); | ||
|
|
||
| context.add_dependency_edge("lib1".to_string(), &root_crate_id, &lib1_crate_id); | ||
| context.add_dependency_edge("lib1".to_string(), &root_crate_id, &lib1_crate_id); | ||
|
|
||
| assert_eq!(context.crate_graph().number_of_crates(), 3); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_works_with_transitive_dependencies() { | ||
| let source_map = PathToFileSourceMap( | ||
| vec![ | ||
| (Path::new("lib1/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| (Path::new("lib2/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| (Path::new("lib3/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| ] | ||
| .into_iter() | ||
| .collect(), | ||
| ); | ||
|
|
||
| let mut context = setup_test_context(source_map); | ||
|
|
||
| let lib1_crate_id = context.process_dependency_crate("lib1/lib.nr".to_string()); | ||
| let lib2_crate_id = context.process_dependency_crate("lib2/lib.nr".to_string()); | ||
| let lib3_crate_id = context.process_dependency_crate("lib3/lib.nr".to_string()); | ||
| let root_crate_id = context.root_crate_id(); | ||
|
|
||
| context.add_dependency_edge("lib1".to_string(), &root_crate_id, &lib1_crate_id); | ||
| context.add_dependency_edge("lib2".to_string(), &lib1_crate_id, &lib2_crate_id); | ||
| context.add_dependency_edge("lib3".to_string(), &lib2_crate_id, &lib3_crate_id); | ||
|
|
||
| assert_eq!(context.crate_graph().number_of_crates(), 5); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_works_with_missing_dependencies() { | ||
| let source_map = PathToFileSourceMap( | ||
| vec![ | ||
| (Path::new("lib1/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| (Path::new("lib2/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| (Path::new("lib3/lib.nr").to_path_buf(), "fn foo() {}".to_string()), | ||
| ] | ||
| .into_iter() | ||
| .collect(), | ||
| ); | ||
| let mut context = setup_test_context(source_map); | ||
|
|
||
| let lib1_crate_id = context.process_dependency_crate("lib1/lib.nr".to_string()); | ||
| let lib2_crate_id = context.process_dependency_crate("lib2/lib.nr".to_string()); | ||
| let lib3_crate_id = context.process_dependency_crate("lib3/lib.nr".to_string()); | ||
| let root_crate_id = context.root_crate_id(); | ||
|
|
||
| context.add_dependency_edge("lib1".to_string(), &root_crate_id, &lib1_crate_id); | ||
| context.add_dependency_edge("lib3".to_string(), &lib2_crate_id, &lib3_crate_id); | ||
|
|
||
| assert_eq!(context.crate_graph().number_of_crates(), 5); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.