From 68c5233f80d87942bfd4b31a9478e39c7f67aa37 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 23 May 2019 09:15:26 -0700 Subject: [PATCH 01/26] First refactor for WebIDL bindings This commit starts the `wasm-bindgen` CLI tool down the road to being a true polyfill for WebIDL bindings. This refactor is probably the first of a few, but is hopefully the largest and most sprawling and everything will be a bit more targeted from here on out. The goal of this refactoring is to separate out the massive `crates/cli-support/src/js/mod.rs` into a number of separate pieces of functionality. It currently takes care of basically everything including: * Binding intrinsics * Handling anyref transformations * Generating all JS for imports/exports * All the logic for how to import and how to name imports * Execution and management of wasm-bindgen closures Many of these are separable concerns and most overlap with WebIDL bindings. The internal refactoring here is intended to make it more clear who's responsible for what as well as making some existing operations much more straightforward. At a high-level, the following changes are done: 1. A `src/webidl.rs` module is introduced. The purpose of this module is to take all of the raw wasm-bindgen custom sections from the module and transform them into a WebIDL bindings section. This module has a placeholder `WebidlCustomSection` which is nowhere near the actual custom section but if you squint is in theory very similar. It's hoped that this will eventually become the true WebIDL custom section, currently being developed in an external crate. Currently, however, the WebIDL bindings custom section only covers a subset of the functionality we export to wasm-bindgen users. To avoid leaving them high and dry this module also contains an auxiliary custom section named `WasmBindgenAux`. This custom section isn't intended to have a binary format, but is intended to represent a theoretical custom section necessary to couple with WebIDL bindings to achieve all our desired functionality in `wasm-bindgen`. It'll never be standardized, but it'll also never be serialized :) 2. The `src/webidl.rs` module now takes over quite a bit of functionality from `src/js/mod.rs`. Namely it handles synthesis of an `export_map` and an `import_map` mapping export/import IDs to exactly what's expected to be hooked up there. This does not include type information (as that's in the bindings section) but rather includes things like "this is the method of class A" or "this import is from module `foo`" and things like that. These could arguably be subsumed by future JS features as well, but that's for another time! 3. All handling of wasm-bindgen "descriptor functions" now happens in a dedicated `src/descriptors.rs` module. The output of this module is its own custom section (intended to be immediately consumed by the WebIDL module) which is in theory what we want to ourselves emit one day but rustc isn't capable of doing so right now. 4. Invocations and generations of imports are completely overhauled. Using the `import_map` generated in the WebIDL step all imports are now handled much more precisely in one location rather than haphazardly throughout the module. This means we have precise information about each import of the module and we only modify exactly what we're looking at. This also vastly simplifies intrinsic generation since it's all simply a codegen part of the `rust2js.rs` module now. 5. Handling of direct imports which don't have a JS shim generated is slightly different from before and is intended to be future-compatible with WebIDL bindings in its full glory, but we'll need to update it to handle cases for constructors and method calls eventually as well. 6. Intrinsic definitions now live in their own file (`src/intrinsic.rs`) and have a separated definition for their symbol name and signature. The actual implementation of each intrinsic lives in `rust2js.rs` There's a number of TODO items to finish before this merges. This includes reimplementing the anyref pass and actually implementing import maps for other targets. Those will come soon in follow-up commits, but the entire `tests/wasm/main.rs` suite is currently passing and this seems like a good checkpoint. --- Cargo.toml | 1 + crates/cli-support/src/descriptor.rs | 20 +- crates/cli-support/src/descriptors.rs | 200 ++ crates/cli-support/src/intrinsic.rs | 149 ++ crates/cli-support/src/js/closures.rs | 258 -- crates/cli-support/src/js/js2rust.rs | 12 +- crates/cli-support/src/js/mod.rs | 2151 ++++------------- crates/cli-support/src/js/rust2js.rs | 640 ++++- crates/cli-support/src/lib.rs | 252 +- crates/cli-support/src/webidl.rs | 1294 ++++++++++ .../src/bin/wasm-bindgen-test-runner/main.rs | 9 +- .../src/bin/wasm-bindgen-test-runner/node.rs | 2 +- crates/wasm-interpreter/src/lib.rs | 9 +- src/lib.rs | 22 +- tests/wasm/closures.js | 10 + tests/wasm/closures.rs | 11 + tests/wasm/import_class.rs | 2 +- tests/wasm/simple.rs | 9 + 18 files changed, 2834 insertions(+), 2217 deletions(-) create mode 100644 crates/cli-support/src/descriptors.rs create mode 100644 crates/cli-support/src/intrinsic.rs delete mode 100644 crates/cli-support/src/js/closures.rs create mode 100644 crates/cli-support/src/webidl.rs diff --git a/Cargo.toml b/Cargo.toml index b7c9b81e..dd2714a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,3 +90,4 @@ wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } js-sys = { path = 'crates/js-sys' } web-sys = { path = 'crates/web-sys' } +walrus = { git = 'https://github.com/rustwasm/walrus' } diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 09d7b332..6efeb370 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -38,7 +38,7 @@ tys! { CLAMPED } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Descriptor { I8, U8, @@ -67,14 +67,14 @@ pub enum Descriptor { Clamped(Box), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Function { pub arguments: Vec, pub shim_idx: u32, pub ret: Descriptor, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Closure { pub shim_idx: u32, pub dtor_idx: u32, @@ -146,9 +146,9 @@ impl Descriptor { } } - pub fn unwrap_function(&self) -> &Function { - match *self { - Descriptor::Function(ref f) => f, + pub fn unwrap_function(self) -> Function { + match self { + Descriptor::Function(f) => *f, _ => panic!("not a function"), } } @@ -199,10 +199,10 @@ impl Descriptor { } } - pub fn closure(&self) -> Option<&Closure> { - match *self { - Descriptor::Closure(ref s) => Some(s), - _ => None, + pub fn unwrap_closure(self) -> Closure { + match self { + Descriptor::Closure(s) => *s, + _ => panic!("not a closure"), } } diff --git a/crates/cli-support/src/descriptors.rs b/crates/cli-support/src/descriptors.rs new file mode 100644 index 00000000..4752aab9 --- /dev/null +++ b/crates/cli-support/src/descriptors.rs @@ -0,0 +1,200 @@ +//! Management of wasm-bindgen descriptor functions. +//! +//! The purpose of this module is to basically execute a pass on a raw wasm +//! module that just came out of the compiler. The pass will execute all +//! relevant descriptor functions contained in the module which wasm-bindgen +//! uses to convey type infomation here, to the CLI. +//! +//! All descriptor functions are removed after this pass runs and in their stead +//! a new custom section, defined in this module, is inserted into the +//! `walrus::Module` which contains all the results of all the descriptor +//! functions. + +use crate::descriptor::{Closure, Descriptor}; +use failure::Error; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::mem; +use walrus::ImportId; +use walrus::{CustomSection, FunctionId, LocalFunction, Module, TypedCustomSectionId}; +use wasm_bindgen_wasm_interpreter::Interpreter; + +#[derive(Default, Debug)] +pub struct WasmBindgenDescriptorsSection { + pub descriptors: HashMap, + pub closure_imports: HashMap, +} + +pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId; + +/// Execute all `__wbindgen_describe_*` functions in a module, inserting a +/// custom section which represents the executed value of each descriptor. +/// +/// Afterwards this will delete all descriptor functions from the module. +pub fn execute(module: &mut Module) -> Result { + let mut section = WasmBindgenDescriptorsSection::default(); + let mut interpreter = Interpreter::new(module)?; + + section.execute_exports(module, &mut interpreter)?; + section.execute_closures(module, &mut interpreter)?; + + // Delete all descriptor functions and imports from the module now that + // we've executed all of them. + walrus::passes::gc::run(module); + + Ok(module.customs.add(section)) +} + +impl WasmBindgenDescriptorsSection { + fn execute_exports( + &mut self, + module: &mut Module, + interpreter: &mut Interpreter, + ) -> Result<(), Error> { + let mut to_remove = Vec::new(); + for export in module.exports.iter() { + let prefix = "__wbindgen_describe_"; + if !export.name.starts_with(prefix) { + continue; + } + let id = match export.item { + walrus::ExportItem::Function(id) => id, + _ => panic!("{} export not a function", export.name), + }; + if let Some(d) = interpreter.interpret_descriptor(id, module) { + let name = &export.name[prefix.len()..]; + let descriptor = Descriptor::decode(d); + self.descriptors.insert(name.to_string(), descriptor); + } + to_remove.push(export.id()); + } + + for id in to_remove { + module.exports.delete(id); + } + Ok(()) + } + + fn execute_closures( + &mut self, + module: &mut Module, + interpreter: &mut Interpreter, + ) -> Result<(), Error> { + use walrus::ir::*; + + // If our describe closure intrinsic isn't present or wasn't linked + // then there's no closures, so nothing to do! + let wbindgen_describe_closure = match interpreter.describe_closure_id() { + Some(i) => i, + None => return Ok(()), + }; + + // Find all functions which call `wbindgen_describe_closure`. These are + // specially codegen'd so we know the rough structure of them. For each + // one we delegate to the interpreter to figure out the actual result. + let mut element_removal_list = HashSet::new(); + let mut func_to_descriptor = HashMap::new(); + for (id, local) in module.funcs.iter_local() { + let entry = local.entry_block(); + let mut find = FindDescribeClosure { + func: local, + wbindgen_describe_closure, + cur: entry.into(), + call: None, + }; + find.visit_block_id(&entry); + if let Some(call) = find.call { + let descriptor = interpreter + .interpret_closure_descriptor(id, module, &mut element_removal_list) + .unwrap(); + func_to_descriptor.insert(id, (call, Descriptor::decode(descriptor))); + } + } + + // For all indirect functions that were closure descriptors, delete them + // from the function table since we've executed them and they're not + // necessary in the final binary. + let table_id = match interpreter.function_table_id() { + Some(id) => id, + None => return Ok(()), + }; + let table = module.tables.get_mut(table_id); + let table = match &mut table.kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + for idx in element_removal_list { + log::trace!("delete element {}", idx); + assert!(table.elements[idx].is_some()); + table.elements[idx] = None; + } + + // And finally replace all calls of `wbindgen_describe_closure` with a + // freshly manufactured import. Save off the type of this import in + // ourselves, and then we're good to go. + let ty = module.funcs.get(wbindgen_describe_closure).ty(); + for (func, (call_instr, descriptor)) in func_to_descriptor { + let import_name = format!("__wbindgen_closure_wrapper{}", func.index()); + let id = module.add_import_func("__wbindgen_placeholder__", &import_name, ty); + let import_id = module + .imports + .iter() + .find(|i| i.name == import_name) + .unwrap() + .id(); + module.funcs.get_mut(id).name = Some(import_name); + + let local = match &mut module.funcs.get_mut(func).kind { + walrus::FunctionKind::Local(l) => l, + _ => unreachable!(), + }; + match local.get_mut(call_instr) { + Expr::Call(e) => { + assert_eq!(e.func, wbindgen_describe_closure); + e.func = id; + } + _ => unreachable!(), + } + self.closure_imports + .insert(import_id, descriptor.unwrap_closure()); + } + return Ok(()); + + struct FindDescribeClosure<'a> { + func: &'a LocalFunction, + wbindgen_describe_closure: FunctionId, + cur: ExprId, + call: Option, + } + + impl<'a> Visitor<'a> for FindDescribeClosure<'a> { + fn local_function(&self) -> &'a LocalFunction { + self.func + } + + fn visit_expr_id(&mut self, id: &ExprId) { + let prev = mem::replace(&mut self.cur, *id); + id.visit(self); + self.cur = prev; + } + + fn visit_call(&mut self, call: &Call) { + call.visit(self); + if call.func == self.wbindgen_describe_closure { + assert!(self.call.is_none()); + self.call = Some(self.cur); + } + } + } + } +} + +impl CustomSection for WasmBindgenDescriptorsSection { + fn name(&self) -> &str { + "wasm-bindgen descriptors" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs new file mode 100644 index 00000000..c502689a --- /dev/null +++ b/crates/cli-support/src/intrinsic.rs @@ -0,0 +1,149 @@ +//! Definition of all wasm-bindgen intrinsics. +//! +//! This contains a definition of all intrinsics used by `src/lib.rs` in the +//! wasm-bindgen crate. Each intrinsic listed here is part of an `enum +//! Intrinsic` and is generated through a macro to reduce repetition. +//! +//! Intrinsics in this module currently largely contain their expected symbol +//! name as well as the signature of the function that it expects. + +use crate::descriptor::{self, Descriptor, Function}; + +macro_rules! intrinsics { + (pub enum Intrinsic { + $( + #[symbol = $sym:tt] + #[signature = fn($($arg:expr),*) -> $ret:ident] + $name:ident, + )* + }) => { + /// All wasm-bindgen intrinsics that could be depended on by a wasm + /// module. + #[derive(Debug)] + pub enum Intrinsic { + $($name,)* + } + + impl Intrinsic { + /// Returns the corresponding intrinsic for a symbol name, if one + /// matches. + pub fn from_symbol(symbol: &str) -> Option { + match symbol { + $($sym => Some(Intrinsic::$name),)* + _ => None, + } + } + + /// Returns the expected signature of this intrinsic, used for + /// generating a JS shim. + pub fn binding(&self) -> Function { + use crate::descriptor::Descriptor::*; + match self { + $( + Intrinsic::$name => { + descriptor::Function { + shim_idx: 0, + arguments: vec![$($arg),*], + ret: $ret, + } + } + )* + } + } + } + }; +} + +fn ref_anyref() -> Descriptor { + Descriptor::Ref(Box::new(Descriptor::Anyref)) +} + +fn ref_string() -> Descriptor { + Descriptor::Ref(Box::new(Descriptor::String)) +} + +intrinsics! { + pub enum Intrinsic { + #[symbol = "__wbindgen_jsval_eq"] + #[signature = fn(ref_anyref(), ref_anyref()) -> Boolean] + JsvalEq, + #[symbol = "__wbindgen_is_function"] + #[signature = fn(ref_anyref()) -> Boolean] + IsFunction, + #[symbol = "__wbindgen_is_undefined"] + #[signature = fn(ref_anyref()) -> Boolean] + IsUndefined, + #[symbol = "__wbindgen_is_null"] + #[signature = fn(ref_anyref()) -> Boolean] + IsNull, + #[symbol = "__wbindgen_is_object"] + #[signature = fn(ref_anyref()) -> Boolean] + IsObject, + #[symbol = "__wbindgen_is_symbol"] + #[signature = fn(ref_anyref()) -> Boolean] + IsSymbol, + #[symbol = "__wbindgen_is_string"] + #[signature = fn(ref_anyref()) -> Boolean] + IsString, + #[symbol = "__wbindgen_object_clone_ref"] + #[signature = fn(ref_anyref()) -> Anyref] + ObjectCloneRef, + #[symbol = "__wbindgen_object_drop_ref"] + #[signature = fn(Anyref) -> Unit] + ObjectDropRef, + #[symbol = "__wbindgen_cb_drop"] + #[signature = fn(Anyref) -> Boolean] + CallbackDrop, + #[symbol = "__wbindgen_cb_forget"] + #[signature = fn(Anyref) -> Unit] + CallbackForget, + #[symbol = "__wbindgen_number_new"] + #[signature = fn(F64) -> Anyref] + NumberNew, + #[symbol = "__wbindgen_string_new"] + #[signature = fn(ref_string()) -> Anyref] + StringNew, + #[symbol = "__wbindgen_symbol_new"] + #[signature = fn(I32, I32) -> Anyref] + SymbolNew, + #[symbol = "__wbindgen_number_get"] + #[signature = fn(ref_anyref(), F64) -> F64] + NumberGet, + #[symbol = "__wbindgen_string_get"] + #[signature = fn(ref_anyref(), I32) -> I32] + StringGet, + #[symbol = "__wbindgen_boolean_get"] + #[signature = fn(ref_anyref()) -> F64] + BooleanGet, + #[symbol = "__wbindgen_throw"] + #[signature = fn(ref_string()) -> Unit] + Throw, + #[symbol = "__wbindgen_rethrow"] + #[signature = fn(Anyref) -> Unit] + Rethrow, + #[symbol = "__wbindgen_memory"] + #[signature = fn() -> Anyref] + Memory, + #[symbol = "__wbindgen_module"] + #[signature = fn() -> Anyref] + Module, + #[symbol = "__wbindgen_function_table"] + #[signature = fn() -> Anyref] + FunctionTable, + #[symbol = "__wbindgen_debug_string"] + #[signature = fn(ref_anyref()) -> String] + DebugString, + #[symbol = "__wbindgen_json_parse"] + #[signature = fn(ref_string()) -> Anyref] + JsonParse, + #[symbol = "__wbindgen_json_serialize"] + #[signature = fn(ref_anyref()) -> String] + JsonSerialize, + #[symbol = "__wbindgen_anyref_heap_live_count"] + #[signature = fn() -> F64] + AnyrefHeapLiveCount, + #[symbol = "__wbindgen_init_nyref_table"] + #[signature = fn() -> Unit] + InitAnyrefTable, + } +} diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs deleted file mode 100644 index e5ad4f49..00000000 --- a/crates/cli-support/src/js/closures.rs +++ /dev/null @@ -1,258 +0,0 @@ -//! Support for closures in wasm-bindgen -//! -//! This module contains the bulk of the support necessary to support closures -//! in `wasm-bindgen`. The main "support" here is that `Closure::wrap` creates -//! a `JsValue` through... well... unconventional mechanisms. -//! -//! This module contains one public function, `rewrite`. The function will -//! rewrite the wasm module to correctly call closure factories and thread -//! through values into the final `Closure` object. More details about how all -//! this works can be found in the code below. - -use crate::descriptor::Descriptor; -use crate::js::js2rust::{ExportedShim, Js2Rust}; -use crate::js::Context; -use failure::Error; -use std::collections::{BTreeMap, HashSet}; -use std::mem; -use walrus::ir::{Expr, ExprId}; -use walrus::{FunctionId, LocalFunction}; - -pub fn rewrite(input: &mut Context) -> Result<(), Error> { - let info = ClosureDescriptors::new(input); - - if info.element_removal_list.len() == 0 { - return Ok(()); - } - - info.delete_function_table_entries(input); - info.inject_imports(input)?; - Ok(()) -} - -#[derive(Default)] -struct ClosureDescriptors { - /// A list of elements to remove from the function table. The first element - /// of the pair is the index of the entry in the element section, and the - /// second element of the pair is the index within that entry to remove. - element_removal_list: HashSet, - - /// A map from local functions which contain calls to - /// `__wbindgen_describe_closure` to the information about the closure - /// descriptor it contains. - /// - /// This map is later used to replace all calls to the keys of this map with - /// calls to the value of the map. - func_to_descriptor: BTreeMap, -} - -struct DescribeInstruction { - call: ExprId, - descriptor: Descriptor, -} - -impl ClosureDescriptors { - /// Find all invocations of `__wbindgen_describe_closure`. - /// - /// We'll be rewriting all calls to functions who call this import. Here we - /// iterate over all code found in the module, and anything which calls our - /// special imported function is interpreted. The result of interpretation will - /// inform of us of an entry to remove from the function table (as the describe - /// function is never needed at runtime) as well as a `Descriptor` which - /// describes the type of closure needed. - /// - /// All this information is then returned in the `ClosureDescriptors` return - /// value. - fn new(input: &mut Context) -> ClosureDescriptors { - use walrus::ir::*; - - let wbindgen_describe_closure = match input.interpreter.describe_closure_id() { - Some(i) => i, - None => return Default::default(), - }; - let mut ret = ClosureDescriptors::default(); - - for (id, local) in input.module.funcs.iter_local() { - let entry = local.entry_block(); - let mut find = FindDescribeClosure { - func: local, - wbindgen_describe_closure, - cur: entry.into(), - call: None, - }; - find.visit_block_id(&entry); - if let Some(call) = find.call { - let descriptor = input - .interpreter - .interpret_closure_descriptor(id, input.module, &mut ret.element_removal_list) - .unwrap(); - ret.func_to_descriptor.insert( - id, - DescribeInstruction { - call, - descriptor: Descriptor::decode(descriptor), - }, - ); - } - } - - return ret; - - struct FindDescribeClosure<'a> { - func: &'a LocalFunction, - wbindgen_describe_closure: FunctionId, - cur: ExprId, - call: Option, - } - - impl<'a> Visitor<'a> for FindDescribeClosure<'a> { - fn local_function(&self) -> &'a LocalFunction { - self.func - } - - fn visit_expr_id(&mut self, id: &ExprId) { - let prev = mem::replace(&mut self.cur, *id); - id.visit(self); - self.cur = prev; - } - - fn visit_call(&mut self, call: &Call) { - call.visit(self); - if call.func == self.wbindgen_describe_closure { - assert!(self.call.is_none()); - self.call = Some(self.cur); - } - } - } - } - - /// Here we remove elements from the function table. All our descriptor - /// functions are entries in this function table and can be removed once we - /// use them as they're not actually needed at runtime. - /// - /// One option for removal is to replace the function table entry with an - /// index to a dummy function, but for now we simply remove the table entry - /// altogether by splitting the section and having multiple `elem` sections - /// with holes in them. - fn delete_function_table_entries(&self, input: &mut Context) { - let table_id = match input.interpreter.function_table_id() { - Some(id) => id, - None => return, - }; - let table = input.module.tables.get_mut(table_id); - let table = match &mut table.kind { - walrus::TableKind::Function(f) => f, - _ => unreachable!(), - }; - for idx in self.element_removal_list.iter().cloned() { - log::trace!("delete element {}", idx); - assert!(table.elements[idx].is_some()); - table.elements[idx] = None; - } - } - - /// Inject new imports into the module. - /// - /// This function will inject new imported functions into the `input` module - /// described by the fields internally. These new imports will be closure - /// factories and are freshly generated shim in JS. - fn inject_imports(&self, input: &mut Context) -> Result<(), Error> { - let wbindgen_describe_closure = match input.interpreter.describe_closure_id() { - Some(i) => i, - None => return Ok(()), - }; - - // We'll be injecting new imports and we'll need to give them all a - // type. The signature is all `(i32, i32, i32) -> i32` currently - let ty = input.module.funcs.get(wbindgen_describe_closure).ty(); - - // For all our descriptors we found we inject a JS shim for the - // descriptor. This JS shim will manufacture a JS `function`, and - // prepare it to be invoked. - // - // Once all that's said and done we inject a new import into the wasm - // module of our new wrapper, and then rewrite the appropriate call - // instruction. - for (func, instr) in self.func_to_descriptor.iter() { - let import_name = format!("__wbindgen_closure_wrapper{}", func.index()); - - let closure = instr.descriptor.closure().unwrap(); - - let mut shim = closure.shim_idx; - let (js, _ts, _js_doc) = { - let mut builder = Js2Rust::new("", input); - - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - builder.prelude("this.cnt++;"); - - if closure.mutable { - // For mutable closures they can't be invoked recursively. - // To handle that we swap out the `this.a` pointer with zero - // while we invoke it. If we finish and the closure wasn't - // destroyed, then we put back the pointer so a future - // invocation can succeed. - builder - .prelude("let a = this.a;") - .prelude("this.a = 0;") - .rust_argument("a") - .rust_argument("b") - .finally("if (--this.cnt === 0) d(a, b);") - .finally("else this.a = a;"); - } else { - // For shared closures they can be invoked recursively so we - // just immediately pass through `this.a`. If we end up - // executing the destructor, however, we clear out the - // `this.a` pointer to prevent it being used again the - // future. - builder - .rust_argument("this.a") - .rust_argument("b") - .finally("if (--this.cnt === 0) { d(this.a, b); this.a = 0; }"); - } - builder.process(&closure.function, None)?.finish( - "function", - "f", - ExportedShim::TableElement(&mut shim), - ) - }; - input.function_table_needed = true; - let body = format!( - "function(a, b, _ignored) {{ - const f = wasm.__wbg_function_table.get({}); - const d = wasm.__wbg_function_table.get({}); - const cb = {}; - cb.a = a; - cb.cnt = 1; - let real = cb.bind(cb); - real.original = cb; - return {}; - }}", - shim, - closure.dtor_idx, - js, - input.add_heap_object("real"), - ); - input.export(&import_name, &body, None)?; - - let module = "__wbindgen_placeholder__"; - let id = input.module.add_import_func(module, &import_name, ty); - input.anyref.import_xform(module, &import_name, &[], true); - input.module.funcs.get_mut(id).name = Some(import_name); - - let local = match &mut input.module.funcs.get_mut(*func).kind { - walrus::FunctionKind::Local(l) => l, - _ => unreachable!(), - }; - match local.get_mut(instr.call) { - Expr::Call(e) => { - assert_eq!(e.func, wbindgen_describe_closure); - e.func = id; - } - _ => unreachable!(), - } - } - Ok(()) - } -} diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index dfc238b5..d76cfbd3 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -99,15 +99,13 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Generates all bindings necessary for the signature in `Function`, /// creating necessary argument conversions and return value processing. - pub fn process<'c, I>( + pub fn process( &mut self, function: &Function, - opt_arg_names: I, - ) -> Result<&mut Self, Error> - where - I: Into>>, - { - if let Some(arg_names) = opt_arg_names.into() { + opt_arg_names: &Option>, + ) -> Result<&mut Self, Error> { + if let Some(arg_names) = opt_arg_names { + assert_eq!(arg_names.len(), function.arguments.len()); for (arg, arg_name) in function.arguments.iter().zip(arg_names) { self.argument(arg, arg_name.as_str())?; } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index da1eea84..f66d6317 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,83 +1,52 @@ -mod closures; mod js2rust; mod rust2js; -use self::{ - js2rust::{ExportedShim, Js2Rust}, - rust2js::Rust2Js, -}; -use crate::{ - decode, - descriptor::{Descriptor, VectorKind}, - Bindgen, EncodeInto, OutputMode, -}; +use crate::descriptor::VectorKind; +use crate::js::js2rust::{ExportedShim, Js2Rust}; +use crate::js::rust2js::Rust2Js; +use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; +use crate::webidl::{JsImport, JsImportName, WasmBindgenAux, WebidlCustomSection}; +use crate::{Bindgen, EncodeInto, OutputMode}; use failure::{bail, Error, ResultExt}; -use std::{ - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, - env, fs, -}; -use walrus::{MemoryId, Module}; -use wasm_bindgen_shared::struct_function_export_name; -use wasm_bindgen_wasm_interpreter::Interpreter; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::fs; +use std::path::{Path, PathBuf}; +use walrus::{ExportId, ImportId, MemoryId, Module}; pub struct Context<'a> { - pub globals: String, - pub imports: String, - pub imports_post: String, - pub footer: String, - pub typescript: String, - pub exposed_globals: Option>, - pub required_internal_exports: HashSet<&'static str>, - pub imported_functions: HashSet<&'a str>, - pub imported_statics: HashSet<&'a str>, - pub config: &'a Bindgen, + globals: String, + imports_post: String, + typescript: String, + exposed_globals: Option>, + required_internal_exports: HashSet<&'static str>, + config: &'a Bindgen, pub module: &'a mut Module, - pub start: Option, + bindings: WebidlCustomSection, - /// A map which maintains a list of what identifiers we've imported and what - /// they're named locally. - /// - /// The `Option` key is the module that identifiers were imported - /// from, `None` being the global module. The second key is a map of - /// identifiers we've already imported from the module to what they're - /// called locally. - pub imported_names: HashMap, HashMap<&'a str, String>>, + /// A map representing the `import` statements we'll be generating in the JS + /// glue. The key is the module we're importing from and the value is the + /// list of identifier we're importing from the module, with optional + /// renames for each identifier. + js_imports: HashMap)>>, + + /// A map of each wasm import and what JS to hook up to it. + wasm_import_definitions: HashMap, + + /// A map from an import to the name we've locally imported it as. + imported_names: HashMap, /// A set of all defined identifiers through either exports or imports to /// the number of times they've been used, used to generate new /// identifiers. - pub defined_identifiers: HashMap, + defined_identifiers: HashMap, - /// A map of all imported shim functions which can actually be directly - /// imported from the containing module. The mapping here maps to a tuple, - /// where the first element is the module to import from and the second - /// element is the name to import from the module. - /// - /// Note that for `direct_imports` no shims are generated in JS that - /// wasm-bindgen emits. - pub direct_imports: HashMap<&'a str, (&'a str, &'a str)>, - - pub exported_classes: Option>, - pub function_table_needed: bool, - pub interpreter: &'a mut Interpreter, - pub memory: MemoryId, - - /// A map of all local modules we've found, from the identifier they're - /// known as to their actual JS contents. - pub local_modules: HashMap<&'a str, &'a str>, - - /// A map of how many snippets we've seen from each unique crate identifier, - /// used to number snippets correctly when writing them to the filesystem - /// when there's multiple snippets within one crate that aren't all part of - /// the same `Program`. - pub snippet_offsets: HashMap<&'a str, usize>, - - /// All package.json dependencies we've learned about so far - pub package_json_read: HashSet<&'a str>, + exported_classes: Option>, + function_table_needed: bool, + memory: MemoryId, /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. - pub npm_dependencies: HashMap, + pub npm_dependencies: HashMap, pub anyref: wasm_bindgen_anyref_xform::Context, } @@ -91,69 +60,48 @@ pub struct ExportedClass { wrap_needed: bool, } -pub struct SubContext<'a, 'b: 'a> { - pub program: &'b decode::Program<'b>, - pub cx: &'a mut Context<'b>, - pub vendor_prefixes: HashMap<&'b str, Vec<&'b str>>, -} - -pub enum ImportTarget { - Function(String), - Method(String), - Constructor(String), - StructuralMethod(String), - StructuralGetter(Option, String), - StructuralSetter(Option, String), - StructuralIndexingGetter(Option), - StructuralIndexingSetter(Option), - StructuralIndexingDeleter(Option), -} - -/// Return value of `determine_import` which is where we look at an imported -/// function AST and figure out where it's actually being imported from -/// (performing some validation checks and whatnot). -enum Import<'a> { - /// An item is imported from the global scope. The `name` is what's imported - /// and the optional `field` is the field on that item we're importing. - Global { - name: &'a str, - field: Option<&'a str>, - }, - /// Same as `Global`, except the `name` is imported via an ESM import from - /// the specified `module` path. - Module { - module: &'a str, - name: &'a str, - field: Option<&'a str>, - }, - /// Same as `Module`, except we're importing from a local module defined in - /// a local JS snippet. - LocalModule { - module: &'a str, - name: &'a str, - field: Option<&'a str>, - }, - /// Same as `Module`, except we're importing from an `inline_js` attribute - InlineJs { - unique_crate_identifier: &'a str, - snippet_idx_in_crate: usize, - name: &'a str, - field: Option<&'a str>, - }, - /// A global import which may have a number of vendor prefixes associated - /// with it, like `webkitAudioPrefix`. The `name` is the name to test - /// whether it's prefixed. - VendorPrefixed { - name: &'a str, - prefixes: Vec<&'a str>, - }, -} - const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; // Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate const INITIAL_HEAP_OFFSET: usize = 32; impl<'a> Context<'a> { + pub fn new(module: &'a mut Module, config: &'a Bindgen) -> Result, Error> { + // Find the single memory, if there is one, and for ease of use in our + // binding generation just inject one if there's not one already (and + // we'll clean it up later if we end up not using it). + let mut memories = module.memories.iter().map(|m| m.id()); + let memory = memories.next(); + if memories.next().is_some() { + bail!("multiple memories currently not supported"); + } + drop(memories); + let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None)); + + // And then we're good to go! + Ok(Context { + globals: String::new(), + imports_post: String::new(), + typescript: format!("/* tslint:disable */\n"), + exposed_globals: Some(Default::default()), + required_internal_exports: Default::default(), + imported_names: Default::default(), + js_imports: Default::default(), + defined_identifiers: Default::default(), + wasm_import_definitions: Default::default(), + exported_classes: Some(Default::default()), + config, + bindings: *module + .customs + .delete_typed::() + .unwrap(), + module, + function_table_needed: false, + memory, + anyref: Default::default(), + npm_dependencies: Default::default(), + }) + } + fn should_write_global(&mut self, name: &'static str) -> bool { self.exposed_globals.as_mut().unwrap().insert(name) } @@ -280,16 +228,6 @@ impl<'a> Context<'a> { } pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { - // Wire up all default intrinsics, those which don't have any sort of - // dependency on the clsoure/anyref/etc passes. This is where almost all - // intrinsics are wired up. - self.wire_up_initial_intrinsics()?; - - // Next up, perform our closure rewriting pass. This is where we'll - // update invocations of the closure intrinsics we have to instead call - // appropriate JS functions which actually create closures. - closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?; - // Finalize all bindings for JS classes. This is where we'll generate JS // glue for all classes as well as finish up a few final imports like // `__wrap` and such. @@ -300,11 +238,7 @@ impl<'a> Context<'a> { // anywhere to using the type internally. The transformation here is // based off all the previous data we've collected so far for each // import/export listed. - self.anyref.run(self.module)?; - - // With our transforms finished, we can now wire up the final list of - // intrinsics which may depend on the passes run above. - self.wire_up_late_intrinsics()?; + // self.anyref.run(self.module)?; // We're almost done here, so we can delete any internal exports (like // `__wbindgen_malloc`) if none of our JS glue actually needed it. @@ -321,7 +255,6 @@ impl<'a> Context<'a> { // glue is all in place. let mut needs_manual_start = false; if self.config.emit_start { - self.add_start_function()?; needs_manual_start = self.unstart_start_function(); } @@ -329,7 +262,13 @@ impl<'a> Context<'a> { // JS closure shim generation may access the function table as an // example, but if there's no closures in the module there's no need to // export it! - self.export_table()?; + if self.function_table_needed { + let id = match self.module.tables.main_function_table()? { + Some(id) => id, + None => bail!("no function table found in module"), + }; + self.module.exports.add("__wbg_function_table", id); + } // After all we've done, especially // `unexport_unused_internal_exports()`, we probably have a bunch of @@ -337,27 +276,21 @@ impl<'a> Context<'a> { // everything that we don't actually need. walrus::passes::gc::run(self.module); - // Almost there, but before we're done make sure to rewrite the `module` - // field of all imports in the wasm module. The field is currently - // always `__wbindgen_placeholder__` coming out of rustc, but we need to - // update that here to the shim file or an actual ES module. - self.rewrite_imports(module_name)?; - - // We likely made a ton of modifications, so add ourselves to the - // producers section! - self.update_producers_section(); - // Cause any future calls to `should_write_global` to panic, making sure // we don't ask for items which we can no longer emit. drop(self.exposed_globals.take().unwrap()); - Ok(self.finalize_js(module_name, needs_manual_start)) + self.finalize_js(module_name, needs_manual_start) } /// Performs the task of actually generating the final JS module, be it /// `--target no-modules`, `--target web`, or for bundlers. This is the very /// last step performed in `finalize`. - fn finalize_js(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) { + fn finalize_js( + &mut self, + module_name: &str, + needs_manual_start: bool, + ) -> Result<(String, String), Error> { let mut ts = self.typescript.clone(); let mut js = String::new(); if self.config.mode.no_modules() { @@ -367,6 +300,8 @@ impl<'a> Context<'a> { // Depending on the output mode, generate necessary glue to actually // import the wasm file in one way or another. let mut init = (String::new(), String::new()); + let mut footer = String::new(); + let mut imports = self.js_import_header()?; match &self.config.mode { // In `--target no-modules` mode we need to both expose a name on // the global object as well as generate our own custom start @@ -375,7 +310,7 @@ impl<'a> Context<'a> { js.push_str("const __exports = {};\n"); js.push_str("let wasm;\n"); init = self.gen_init(&module_name, needs_manual_start); - self.footer.push_str(&format!( + footer.push_str(&format!( "self.{} = Object.assign(init, __exports);\n", global )); @@ -386,12 +321,22 @@ impl<'a> Context<'a> { OutputMode::Node { experimental_modules: false, } => { - self.footer - .push_str(&format!("wasm = require('./{}_bg');\n", module_name)); - if needs_manual_start { - self.footer.push_str("wasm.__wbindgen_start();\n"); + js.push_str("let wasm;\n"); + + for (id, js) in self.wasm_import_definitions.iter() { + let import = self.module.imports.get_mut(*id); + import.module = format!("./{}.js", module_name); + footer.push_str("\nmodule.exports."); + footer.push_str(&import.name); + footer.push_str(" = "); + footer.push_str(js.trim()); + footer.push_str(";\n"); + } + + footer.push_str(&format!("wasm = require('./{}_bg');\n", module_name)); + if needs_manual_start { + footer.push_str("wasm.__wbindgen_start();\n"); } - js.push_str("var wasm;\n"); } // With Bundlers and modern ES6 support in Node we can simply import @@ -401,10 +346,13 @@ impl<'a> Context<'a> { | OutputMode::Node { experimental_modules: true, } => { - self.imports - .push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); + imports.push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); if needs_manual_start { - self.footer.push_str("wasm.__wbindgen_start();\n"); + footer.push_str("wasm.__wbindgen_start();\n"); + } + for (id, js) in self.wasm_import_definitions.iter() { + drop((id, js)); + unimplemented!() } } @@ -416,7 +364,7 @@ impl<'a> Context<'a> { self.imports_post.push_str("const __exports = {};\n"); self.imports_post.push_str("let wasm;\n"); init = self.gen_init(&module_name, needs_manual_start); - self.footer.push_str("export default init;\n"); + footer.push_str("export default init;\n"); } } @@ -429,7 +377,7 @@ impl<'a> Context<'a> { !self.config.mode.uses_es_modules() || js.is_empty(), "ES modules require imports to be at the start of the file" ); - js.push_str(&self.imports); + js.push_str(&imports); js.push_str("\n"); js.push_str(&self.imports_post); js.push_str("\n"); @@ -441,7 +389,7 @@ impl<'a> Context<'a> { // Generate the initialization glue, if there was any js.push_str(&init_js); js.push_str("\n"); - js.push_str(&self.footer); + js.push_str(&footer); js.push_str("\n"); if self.config.mode.no_modules() { js.push_str("})();\n"); @@ -451,491 +399,66 @@ impl<'a> Context<'a> { js = js.replace("\n\n\n", "\n\n"); } - (js, ts) + Ok((js, ts)) } - fn wire_up_initial_intrinsics(&mut self) -> Result<(), Error> { - self.bind("__wbindgen_string_new", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_string_new", - &[], - true, - ); - me.expose_get_string_from_wasm(); - Ok(format!( - "function(p, l) {{ return {}; }}", - me.add_heap_object("getStringFromWasm(p, l)") - )) - })?; - - self.bind("__wbindgen_number_new", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_number_new", - &[], - true, - ); - Ok(format!( - "function(i) {{ return {}; }}", - me.add_heap_object("i") - )) - })?; - - self.bind("__wbindgen_number_get", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_number_get", - &[(0, false)], - false, - ); - me.expose_uint8_memory(); - Ok(format!( - " - function(n, invalid) {{ - let obj = {}; - if (typeof(obj) === 'number') return obj; - getUint8Memory()[invalid] = 1; - return 0; - }} - ", - me.get_object("n"), - )) - })?; - - self.bind("__wbindgen_is_null", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_null", - &[(0, false)], - false, - ); - Ok(format!( - "function(i) {{ return {} === null ? 1 : 0; }}", - me.get_object("i") - )) - })?; - - self.bind("__wbindgen_is_undefined", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_undefined", - &[(0, false)], - false, - ); - Ok(format!( - "function(i) {{ return {} === undefined ? 1 : 0; }}", - me.get_object("i") - )) - })?; - - self.bind("__wbindgen_boolean_get", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_boolean_get", - &[(0, false)], - false, - ); - Ok(format!( - " - function(i) {{ - let v = {}; - return typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; - }} - ", - me.get_object("i"), - )) - })?; - - self.bind("__wbindgen_symbol_new", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_symbol_new", - &[], - true, - ); - me.expose_get_string_from_wasm(); - let expr = "ptr === 0 ? Symbol() : Symbol(getStringFromWasm(ptr, len))"; - Ok(format!( - "function(ptr, len) {{ return {}; }}", - me.add_heap_object(expr) - )) - })?; - - self.bind("__wbindgen_is_symbol", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_symbol", - &[(0, false)], - false, - ); - Ok(format!( - "function(i) {{ return typeof({}) === 'symbol' ? 1 : 0; }}", - me.get_object("i") - )) - })?; - - self.bind("__wbindgen_is_object", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_object", - &[(0, false)], - false, - ); - Ok(format!( - " - function(i) {{ - const val = {}; - return typeof(val) === 'object' && val !== null ? 1 : 0; - }}", - me.get_object("i"), - )) - })?; - - self.bind("__wbindgen_is_function", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_function", - &[(0, false)], - false, - ); - Ok(format!( - "function(i) {{ return typeof({}) === 'function' ? 1 : 0; }}", - me.get_object("i") - )) - })?; - - self.bind("__wbindgen_is_string", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_is_string", - &[(0, false)], - false, - ); - Ok(format!( - "function(i) {{ return typeof({}) === 'string' ? 1 : 0; }}", - me.get_object("i") - )) - })?; - - self.bind("__wbindgen_string_get", &|me| { - me.expose_pass_string_to_wasm()?; - me.expose_uint32_memory(); - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_string_get", - &[(0, false)], - false, - ); - Ok(format!( - " - function(i, len_ptr) {{ - let obj = {}; - if (typeof(obj) !== 'string') return 0; - const ptr = passStringToWasm(obj); - getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN; - return ptr; - }} - ", - me.get_object("i"), - )) - })?; - - self.bind("__wbindgen_anyref_heap_live_count", &|me| { - if me.config.anyref { - // Eventually we should add support to the anyref-xform to - // re-write calls to the imported - // `__wbindgen_anyref_heap_live_count` function into calls to - // the exported `__wbindgen_anyref_heap_live_count_impl` - // function, and to un-export that function. - // - // But for now, we just bounce wasm -> js -> wasm because it is - // easy. - Ok("function() {{ return wasm.__wbindgen_anyref_heap_live_count_impl(); }}".into()) - } else { - me.expose_global_heap(); - Ok(format!( - " - function() {{ - let free_count = 0; - let next = heap_next; - while (next < heap.length) {{ - free_count += 1; - next = heap[next]; - }} - return heap.length - free_count - {} - {}; - }} - ", - INITIAL_HEAP_OFFSET, - INITIAL_HEAP_VALUES.len(), - )) + fn js_import_header(&self) -> Result { + let mut imports = String::new(); + match &self.config.mode { + OutputMode::NoModules { .. } => { + for (module, _items) in self.js_imports.iter() { + bail!( + "importing from `{}` isn't supported with `--target no-modules`", + module + ); + } } - })?; - self.bind("__wbindgen_debug_string", &|me| { - me.expose_pass_string_to_wasm()?; - me.expose_uint32_memory(); - - let debug_str = " - val => { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `\"${val}\"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; + OutputMode::Node { + experimental_modules: false, + } => { + for (module, items) in self.js_imports.iter() { + imports.push_str("const { "); + for (i, (item, rename)) in items.iter().enumerate() { + if i > 0 { + imports.push_str(", "); + } + imports.push_str(item); + if let Some(other) = rename { + imports.push_str(": "); + imports.push_str(other) } } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debug_str(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debug_str(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val)); - let className; - if (builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; + imports.push_str(" } = require(String.raw`"); + imports.push_str(module); + imports.push_str("`);\n"); } - "; - Ok(format!( - " - function(i, len_ptr) {{ - const debug_str = {}; - const toString = Object.prototype.toString; - const val = {}; - const debug = debug_str(val); - const ptr = passStringToWasm(debug); - getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN; - return ptr; - }} - ", - debug_str, - me.get_object("i"), - )) - })?; - - self.bind("__wbindgen_cb_drop", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_cb_drop", - &[(0, true)], - false, - ); - Ok(format!( - " - function(i) {{ - const obj = {}.original; - if (obj.cnt-- == 1) {{ - obj.a = 0; - return 1; - }} - return 0; - }} - ", - me.take_object("i"), - )) - })?; - - self.bind("__wbindgen_cb_forget", &|me| { - Ok(if me.config.anyref { - // TODO: we should rewrite this in the anyref xform to not even - // call into JS - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_cb_drop", - &[(0, true)], - false, - ); - String::from("function(obj) {}") - } else { - me.expose_drop_ref(); - "dropObject".to_string() - }) - })?; - - self.bind("__wbindgen_json_parse", &|me| { - me.expose_get_string_from_wasm(); - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_json_parse", - &[], - true, - ); - let expr = "JSON.parse(getStringFromWasm(ptr, len))"; - let expr = me.add_heap_object(expr); - Ok(format!("function(ptr, len) {{ return {}; }}", expr)) - })?; - - self.bind("__wbindgen_json_serialize", &|me| { - me.anyref.import_xform( - "__wbindgen_placeholder__", - "__wbindgen_json_serialize", - &[(0, false)], - false, - ); - me.expose_pass_string_to_wasm()?; - me.expose_uint32_memory(); - Ok(format!( - " - function(idx, ptrptr) {{ - const ptr = passStringToWasm(JSON.stringify({})); - getUint32Memory()[ptrptr / 4] = ptr; - return WASM_VECTOR_LEN; - }} - ", - me.get_object("idx"), - )) - })?; - - self.bind("__wbindgen_jsval_eq", &|me| { - Ok(format!( - "function(a, b) {{ return {} === {} ? 1 : 0; }}", - me.get_object("a"), - me.get_object("b") - )) - })?; - - self.bind("__wbindgen_memory", &|me| { - let mem = me.memory(); - Ok(format!( - "function() {{ return {}; }}", - me.add_heap_object(mem) - )) - })?; - - self.bind("__wbindgen_module", &|me| { - if !me.config.mode.no_modules() && !me.config.mode.web() { - bail!( - "`wasm_bindgen::module` is currently only supported with \ - `--target no-modules` and `--target web`" - ); } - Ok(format!( - "function() {{ return {}; }}", - me.add_heap_object("init.__wbindgen_wasm_module") - )) - })?; - self.bind("__wbindgen_function_table", &|me| { - me.function_table_needed = true; - Ok(format!( - "function() {{ return {}; }}", - me.add_heap_object("wasm.__wbg_function_table") - )) - })?; - - self.bind("__wbindgen_rethrow", &|me| { - Ok(format!( - "function(idx) {{ throw {}; }}", - me.take_object("idx") - )) - })?; - - self.bind("__wbindgen_throw", &|me| { - me.expose_get_string_from_wasm(); - Ok(String::from( - " - function(ptr, len) { - throw new Error(getStringFromWasm(ptr, len)); + OutputMode::Bundler { .. } + | OutputMode::Node { + experimental_modules: true, + } + | OutputMode::Web => { + for (module, items) in self.js_imports.iter() { + imports.push_str("import { "); + for (i, (item, rename)) in items.iter().enumerate() { + if i > 0 { + imports.push_str(", "); + } + imports.push_str(item); + if let Some(other) = rename { + imports.push_str(" as "); + imports.push_str(other) + } + } + imports.push_str(" } from '"); + imports.push_str(module); + imports.push_str("';\n"); } - ", - )) - })?; - - Ok(()) - } - - /// Provide implementations of remaining intrinsics after initial passes - /// have been run on the wasm module. - /// - /// The intrinsics implemented here are added very late in the process or - /// otherwise may be overwritten by passes (such as the anyref pass). As a - /// result they don't go into the initial list of intrinsics but go just at - /// the end. - fn wire_up_late_intrinsics(&mut self) -> Result<(), Error> { - // After the anyref pass has executed, if this intrinsic is needed then - // we expose a function which initializes it - self.bind("__wbindgen_init_anyref_table", &|me| { - me.expose_anyref_table(); - Ok(String::from( - "function() { - const table = wasm.__wbg_anyref_table; - const offset = table.grow(4); - table.set(offset + 0, undefined); - table.set(offset + 1, null); - table.set(offset + 2, true); - table.set(offset + 3, false); - }", - )) - })?; - - // make sure that the anyref pass runs before binding this as anyref may - // remove calls to this import and then gc would remove it - self.bind("__wbindgen_object_clone_ref", &|me| { - me.expose_get_object(); - me.expose_add_heap_object(); - Ok(String::from( - " - function(idx) { - return addHeapObject(getObject(idx)); - } - ", - )) - })?; - - // like above, make sure anyref runs first and the anyref pass may - // remove usages of this. - self.bind("__wbindgen_object_drop_ref", &|me| { - me.expose_drop_ref(); - Ok(String::from("function(i) { dropObject(i); }")) - })?; - - Ok(()) + } + } + Ok(imports) } fn ts_for_init_fn(has_memory: bool) -> String { @@ -966,6 +489,10 @@ impl<'a> Context<'a> { } fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) { + for (id, js) in self.wasm_import_definitions.iter() { + drop((id, js)); + unimplemented!() + } let mem = self.module.memories.get(self.memory); let (init_memory1, init_memory2) = if mem.import.is_some() { let mut memory = String::from("new WebAssembly.Memory({"); @@ -997,31 +524,33 @@ impl<'a> Context<'a> { // intended to be imported directly to the wasm module and we need to // ensure that the modules are actually imported from and inserted into // the object correctly. - let mut map = BTreeMap::new(); - for &(module, name) in self.direct_imports.values() { - map.entry(module).or_insert(BTreeSet::new()).insert(name); - } - let mut imports_init = String::new(); - for (module, names) in map { - imports_init.push_str("imports['"); - imports_init.push_str(module); - imports_init.push_str("'] = { "); - for (i, name) in names.into_iter().enumerate() { - if i != 0 { - imports_init.push_str(", "); - } - let import = Import::Module { - module, - name, - field: None, - }; - let identifier = self.import_identifier(import); - imports_init.push_str(name); - imports_init.push_str(": "); - imports_init.push_str(&identifier); - } - imports_init.push_str(" };\n"); - } + // let mut map = BTreeMap::new(); + // for &(module, name) in self.direct_imports.values() { + // map.entry(module).or_insert(BTreeSet::new()).insert(name); + // } + let imports_init = String::new(); + // for (module, names) in map { + // drop((module, names)); + // panic!() + // // imports_init.push_str("imports['"); + // // imports_init.push_str(module); + // // imports_init.push_str("'] = { "); + // // for (i, name) in names.into_iter().enumerate() { + // // if i != 0 { + // // imports_init.push_str(", "); + // // } + // // let import = Import::Module { + // // module, + // // name, + // // field: None, + // // }; + // // let identifier = self.import_identifier(import); + // // imports_init.push_str(name); + // // imports_init.push_str(": "); + // // imports_init.push_str(&identifier); + // // } + // // imports_init.push_str(" };\n"); + // } let js = format!( "\ @@ -1083,20 +612,6 @@ impl<'a> Context<'a> { (js, ts) } - fn bind( - &mut self, - name: &str, - f: &dyn Fn(&mut Self) -> Result, - ) -> Result<(), Error> { - if !self.wasm_import_needed(name) { - return Ok(()); - } - let contents = f(self) - .with_context(|_| format!("failed to generate internal JS function `{}`", name))?; - self.export(name, &contents, None)?; - Ok(()) - } - fn write_classes(&mut self) -> Result<(), Error> { for (class, exports) in self.exported_classes.take().unwrap() { self.write_class(&class, &exports)?; @@ -1118,19 +633,7 @@ impl<'a> Context<'a> { ); } - let mut wrap_needed = class.wrap_needed; - let new_name = wasm_bindgen_shared::new_function(&name); - if self.wasm_import_needed(&new_name) { - wrap_needed = true; - self.anyref - .import_xform("__wbindgen_placeholder__", &new_name, &[], true); - let expr = format!("{}.__wrap(ptr)", name); - let expr = self.add_heap_object(&expr); - let body = format!("function(ptr) {{ return {}; }}", expr); - self.export(&new_name, &body, None)?; - } - - if wrap_needed { + if class.wrap_needed { dst.push_str(&format!( " static __wrap(ptr) {{ @@ -1200,102 +703,10 @@ impl<'a> Context<'a> { Ok(()) } - fn export_table(&mut self) -> Result<(), Error> { - if !self.function_table_needed { - return Ok(()); - } - let id = match self.module.tables.main_function_table()? { - Some(id) => id, - None => bail!("no function table found in module"), - }; - self.module.exports.add("__wbg_function_table", id); - Ok(()) - } - - fn rewrite_imports(&mut self, module_name: &str) -> Result<(), Error> { - for (name, contents) in self._rewrite_imports(module_name) { - self.export(&name, &contents, None)?; - } - Ok(()) - } - - fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> { - let mut math_imports = Vec::new(); - for import in self.module.imports.iter_mut() { - if import.module == "__wbindgen_placeholder__" { - import.module.truncate(0); - if let Some((module, name)) = self.direct_imports.get(import.name.as_str()) { - import.name.truncate(0); - import.module.push_str(module); - import.name.push_str(name); - } else { - import.module.push_str("./"); - import.module.push_str(module_name); - } - continue; - } - - if import.module != "env" { - continue; - } - - // If memory is imported we'll have exported it from the shim module - // so let's import it from there. - // - // TODO: we should track this is in a more first-class fashion - // rather than just matching on strings. - if import.name == "memory" { - import.module.truncate(0); - import.module.push_str("./"); - import.module.push_str(module_name); - continue; - } - - let renamed_import = format!("__wbindgen_{}", import.name); - let mut bind_math = |expr: &str| { - math_imports.push((renamed_import.clone(), format!("function{}", expr))); - }; - - // Note that since Rust 1.32.0 this is no longer necessary. Imports - // of these functions were fixed in rust-lang/rust#54257 and we're - // just waiting until pre-1.32.0 compilers are basically no longer - // in use to remove this. - match import.name.as_str() { - "Math_acos" => bind_math("(x) { return Math.acos(x); }"), - "Math_asin" => bind_math("(x) { return Math.asin(x); }"), - "Math_atan" => bind_math("(x) { return Math.atan(x); }"), - "Math_atan2" => bind_math("(x, y) { return Math.atan2(x, y); }"), - "Math_cbrt" => bind_math("(x) { return Math.cbrt(x); }"), - "Math_cosh" => bind_math("(x) { return Math.cosh(x); }"), - "Math_expm1" => bind_math("(x) { return Math.expm1(x); }"), - "Math_hypot" => bind_math("(x, y) { return Math.hypot(x, y); }"), - "Math_log1p" => bind_math("(x) { return Math.log1p(x); }"), - "Math_sinh" => bind_math("(x) { return Math.sinh(x); }"), - "Math_tan" => bind_math("(x) { return Math.tan(x); }"), - "Math_tanh" => bind_math("(x) { return Math.tanh(x); }"), - _ => continue, - } - - import.module.truncate(0); - import.module.push_str("./"); - import.module.push_str(module_name); - import.name = renamed_import.clone(); - } - - math_imports - } - fn unexport_unused_internal_exports(&mut self) { let mut to_remove = Vec::new(); for export in self.module.exports.iter() { match export.name.as_str() { - // These are some internal imports set by LLD but currently - // we've got no use case for continuing to export them, so - // blacklist them. - "__heap_base" | "__data_end" | "__indirect_function_table" => { - to_remove.push(export.id()); - } - // Otherwise only consider our special exports, which all start // with the same prefix which hopefully only we're using. n if n.starts_with("__wbindgen") => { @@ -1311,6 +722,19 @@ impl<'a> Context<'a> { } } + fn expose_does_not_exist(&mut self) { + if !self.should_write_global("does_not_exist") { + return; + } + self.global( + " + function doesNotExist() { + throw new Error('imported function or type does not exist'); + } + ", + ); + } + fn expose_drop_ref(&mut self) { if !self.should_write_global("drop_ref") { return; @@ -1440,7 +864,7 @@ impl<'a> Context<'a> { return Ok(()); } - self.expose_text_encoder(); + self.expose_text_encoder()?; self.expose_uint8_memory(); // A fast path that directly writes char codes into WASM memory as long @@ -1650,28 +1074,31 @@ impl<'a> Context<'a> { Ok(()) } - fn expose_text_encoder(&mut self) { + fn expose_text_encoder(&mut self) -> Result<(), Error> { if !self.should_write_global("text_encoder") { - return; + return Ok(()); } - self.expose_text_processor("TextEncoder"); + self.expose_text_processor("TextEncoder") } - fn expose_text_decoder(&mut self) { + fn expose_text_decoder(&mut self) -> Result<(), Error> { if !self.should_write_global("text_decoder") { - return; + return Ok(()); } - self.expose_text_processor("TextDecoder"); + self.expose_text_processor("TextDecoder")?; + Ok(()) } - fn expose_text_processor(&mut self, s: &str) { - if self.config.mode.nodejs_experimental_modules() { - self.imports - .push_str(&format!("import {{ {} }} from 'util';\n", s)); - self.global(&format!("let cached{0} = new {0}('utf-8');", s)); - } else if self.config.mode.nodejs() { - self.global(&format!("const {0} = require('util').{0};", s)); - self.global(&format!("let cached{0} = new {0}('utf-8');", s)); + fn expose_text_processor(&mut self, s: &str) -> Result<(), Error> { + if self.config.mode.nodejs() { + let name = self.import_name(&JsImport { + name: JsImportName::Module { + module: "util".to_string(), + name: s.to_string(), + }, + fields: Vec::new(), + })?; + self.global(&format!("let cached{} = new {}('utf-8');", s, name)); } else if !self.config.mode.always_run_in_browser() { self.global(&format!( " @@ -1684,13 +1111,14 @@ impl<'a> Context<'a> { } else { self.global(&format!("let cached{0} = new {0}('utf-8');", s)); } + Ok(()) } - fn expose_get_string_from_wasm(&mut self) { + fn expose_get_string_from_wasm(&mut self) -> Result<(), Error> { if !self.should_write_global("get_string_from_wasm") { - return; + return Ok(()); } - self.expose_text_decoder(); + self.expose_text_decoder()?; self.expose_uint8_memory(); // Typically we try to give a raw view of memory out to `TextDecoder` to @@ -1712,6 +1140,7 @@ impl<'a> Context<'a> { ", method )); + Ok(()) } fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> { @@ -2081,13 +1510,6 @@ impl<'a> Context<'a> { Ok(()) } - fn wasm_import_needed(&self, name: &str) -> bool { - self.module - .imports - .iter() - .any(|i| i.module == "__wbindgen_placeholder__" && i.name == name) - } - fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { let s = match t { VectorKind::String => { @@ -2129,7 +1551,7 @@ impl<'a> Context<'a> { fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> Result<&'static str, Error> { Ok(match ty { VectorKind::String => { - self.expose_get_string_from_wasm(); + self.expose_get_string_from_wasm()?; "getStringFromWasm" } VectorKind::I8 => { @@ -2277,12 +1699,6 @@ impl<'a> Context<'a> { ); } - fn describe(&mut self, name: &str) -> Option { - let name = format!("__wbindgen_describe_{}", name); - let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?; - Some(Descriptor::decode(descriptor)) - } - fn global(&mut self, s: &str) { let s = s.trim(); @@ -2295,10 +1711,6 @@ impl<'a> Context<'a> { self.globals.push_str("\n"); } - fn use_node_require(&self) -> bool { - self.config.mode.nodejs() && !self.config.mode.nodejs_experimental_modules() - } - fn memory(&mut self) -> &'static str { if self.module.memories.get(self.memory).import.is_some() { "memory" @@ -2311,272 +1723,98 @@ impl<'a> Context<'a> { require_class(&mut self.exported_classes, name).wrap_needed = true; } - fn import_identifier(&mut self, import: Import<'a>) -> String { - // Here's where it's a bit tricky. We need to make sure that importing - // the same identifier from two different modules works, and they're - // named uniquely below. Additionally if we've already imported the same - // identifier from the module in question then we'd like to reuse the - // one that was previously imported. - // - // Our `imported_names` map keeps track of all imported identifiers from - // modules, mapping the imported names onto names actually available for - // use in our own module. If our identifier isn't present then we - // generate a new identifier and are sure to generate the appropriate JS - // import for our new identifier. - let use_node_require = self.use_node_require(); - let defined_identifiers = &mut self.defined_identifiers; - let imports = &mut self.imports; - let imports_post = &mut self.imports_post; - let identifier = self - .imported_names - .entry(import.module()) - .or_insert_with(Default::default) - .entry(import.name()) - .or_insert_with(|| { - let name = generate_identifier(import.name(), defined_identifiers); - match &import { - Import::Module { .. } - | Import::LocalModule { .. } - | Import::InlineJs { .. } => { - // When doing a modular import local snippets (either - // inline or not) are routed to a local `./snippets` - // directory which the rest of `wasm-bindgen` will fill - // in. - let path = match import { - Import::Module { module, .. } => module.to_string(), - Import::LocalModule { module, .. } => format!("./snippets/{}", module), - Import::InlineJs { - unique_crate_identifier, - snippet_idx_in_crate, - .. - } => format!( - "./snippets/{}/inline{}.js", - unique_crate_identifier, snippet_idx_in_crate - ), - _ => unreachable!(), - }; - if use_node_require { - imports.push_str(&format!( - "const {} = require(String.raw`{}`).{};\n", - name, - path, - import.name() - )); - } else if import.name() == name { - imports.push_str(&format!("import {{ {} }} from '{}';\n", name, path)); - } else { - imports.push_str(&format!( - "import {{ {} as {} }} from '{}';\n", - import.name(), - name, - path - )); - } - name - } - - Import::VendorPrefixed { prefixes, .. } => { - imports_post.push_str("const l"); - imports_post.push_str(&name); - imports_post.push_str(" = "); - switch(imports_post, &name, "", prefixes); - imports_post.push_str(";\n"); - - fn switch(dst: &mut String, name: &str, prefix: &str, left: &[&str]) { - if left.len() == 0 { - dst.push_str(prefix); - return dst.push_str(name); - } - dst.push_str("(typeof "); - dst.push_str(prefix); - dst.push_str(name); - dst.push_str(" == 'undefined' ? "); - match left.len() { - 1 => { - dst.push_str(&left[0]); - dst.push_str(name); - } - _ => switch(dst, name, &left[0], &left[1..]), - } - dst.push_str(" : "); - dst.push_str(prefix); - dst.push_str(name); - dst.push_str(")"); - } - format!("l{}", name) - } - - Import::Global { .. } => name, - } - }); - - // If there's a namespace we didn't actually import `item` but rather - // the namespace, so access through that. - match import.field() { - Some(field) => format!("{}.{}", identifier, field), - None => identifier.clone(), + fn import_name(&mut self, import: &JsImport) -> Result { + if let Some(name) = self.imported_names.get(&import.name) { + let mut name = name.clone(); + for field in import.fields.iter() { + name.push_str("."); + name.push_str(field); + } + return Ok(name.clone()); } - } - fn generated_import_target( - &mut self, - name: Import<'a>, - import: &decode::ImportFunction<'a>, - ) -> Result { - let method_data = match &import.method { - Some(data) => data, - None => { - let name = self.import_identifier(name); - if import.structural || !name.contains(".") { - return Ok(ImportTarget::Function(name)); - } - self.global(&format!("const {}_target = {};", import.shim, name)); - let target = format!("{}_target", import.shim); - return Ok(ImportTarget::Function(target)); - } - }; - - let class = self.import_identifier(name); - let op = match &method_data.kind { - decode::MethodKind::Constructor => { - return Ok(ImportTarget::Constructor(class.to_string())); - } - decode::MethodKind::Operation(op) => op, - }; - if import.structural { - let class = if op.is_static { - Some(class.clone()) - } else { + let js_imports = &mut self.js_imports; + let mut add_module_import = |module: String, name: &str, actual: &str| { + let rename = if name == actual { None + } else { + Some(actual.to_string()) }; + js_imports + .entry(module) + .or_insert(Vec::new()) + .push((name.to_string(), rename)); + }; - return Ok(match &op.kind { - decode::OperationKind::Regular => { - let name = import.function.name.to_string(); - match class { - Some(c) => ImportTarget::Function(format!("{}.{}", c, name)), - None => ImportTarget::StructuralMethod(name), + let mut name = match &import.name { + JsImportName::Module { module, name } => { + let unique_name = generate_identifier(name, &mut self.defined_identifiers); + add_module_import(module.clone(), name, &unique_name); + unique_name + } + + JsImportName::LocalModule { module, name } => { + let unique_name = generate_identifier(name, &mut self.defined_identifiers); + add_module_import(format!("./snippets/{}", module), name, &unique_name); + unique_name + } + + JsImportName::InlineJs { + unique_crate_identifier, + snippet_idx_in_crate, + name, + } => { + let unique_name = generate_identifier(name, &mut self.defined_identifiers); + let module = format!( + "./snippets/{}/inline{}.js", + unique_crate_identifier, snippet_idx_in_crate, + ); + add_module_import(module, name, &unique_name); + unique_name + } + + JsImportName::VendorPrefixed { name, prefixes } => { + self.imports_post.push_str("const l"); + self.imports_post.push_str(&name); + self.imports_post.push_str(" = "); + switch(&mut self.imports_post, name, "", prefixes); + self.imports_post.push_str(";\n"); + + fn switch(dst: &mut String, name: &str, prefix: &str, left: &[String]) { + if left.len() == 0 { + dst.push_str(prefix); + return dst.push_str(name); } + dst.push_str("(typeof "); + dst.push_str(prefix); + dst.push_str(name); + dst.push_str(" !== 'undefined' ? "); + dst.push_str(prefix); + dst.push_str(name); + dst.push_str(" : "); + switch(dst, name, &left[0], &left[1..]); + dst.push_str(")"); } - decode::OperationKind::Getter(g) => { - ImportTarget::StructuralGetter(class, g.to_string()) + format!("l{}", name) + } + + JsImportName::Global { name } => { + let unique_name = generate_identifier(name, &mut self.defined_identifiers); + if unique_name != *name { + bail!("cannot import `{}` from two locations", name); } - decode::OperationKind::Setter(s) => { - ImportTarget::StructuralSetter(class, s.to_string()) - } - decode::OperationKind::IndexingGetter => { - ImportTarget::StructuralIndexingGetter(class) - } - decode::OperationKind::IndexingSetter => { - ImportTarget::StructuralIndexingSetter(class) - } - decode::OperationKind::IndexingDeleter => { - ImportTarget::StructuralIndexingDeleter(class) - } - }); + unique_name + } + }; + self.imported_names + .insert(import.name.clone(), name.clone()); + + // After we've got an actual name handle field projections + for field in import.fields.iter() { + name.push_str("."); + name.push_str(field); } - - let target = format!( - "typeof {0} === 'undefined' ? null : {}{}", - class, - if op.is_static { "" } else { ".prototype" } - ); - let (mut target, name) = match &op.kind { - decode::OperationKind::Regular => ( - format!("{}.{}", target, import.function.name), - &import.function.name, - ), - decode::OperationKind::Getter(g) => { - self.expose_get_inherited_descriptor(); - ( - format!( - "GetOwnOrInheritedPropertyDescriptor({}, '{}').get", - target, g, - ), - g, - ) - } - decode::OperationKind::Setter(s) => { - self.expose_get_inherited_descriptor(); - ( - format!( - "GetOwnOrInheritedPropertyDescriptor({}, '{}').set", - target, s, - ), - s, - ) - } - decode::OperationKind::IndexingGetter => panic!("indexing getter should be structural"), - decode::OperationKind::IndexingSetter => panic!("indexing setter should be structural"), - decode::OperationKind::IndexingDeleter => { - panic!("indexing deleter should be structural") - } - }; - target.push_str(&format!( - " || function() {{ - throw new Error(`wasm-bindgen: {}.{} does not exist`); - }}", - class, name - )); - if op.is_static { - target.insert(0, '('); - target.push_str(").bind("); - target.push_str(&class); - target.push_str(")"); - } - - self.global(&format!("const {}_target = {};", import.shim, target)); - Ok(if op.is_static { - ImportTarget::Function(format!("{}_target", import.shim)) - } else { - ImportTarget::Method(format!("{}_target", import.shim)) - }) - } - - /// Update the wasm file's `producers` section to include information about - /// the wasm-bindgen tool. - /// - /// Specified at: - /// https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md - fn update_producers_section(&mut self) { - self.module - .producers - .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version()); - } - - fn add_start_function(&mut self) -> Result<(), Error> { - let start = match &self.start { - Some(name) => name.clone(), - None => return Ok(()), - }; - let export = match self.module.exports.iter().find(|e| e.name == start) { - Some(export) => export, - None => bail!("export `{}` not found", start), - }; - let id = match export.item { - walrus::ExportItem::Function(i) => i, - _ => bail!("export `{}` wasn't a function", start), - }; - - let prev_start = match self.module.start { - Some(f) => f, - None => { - self.module.start = Some(id); - return Ok(()); - } - }; - - // Note that we call the previous start function, if any, first. This is - // because the start function currently only shows up when it's injected - // through thread/anyref transforms. These injected start functions need - // to happen before user code, so we always schedule them first. - let mut builder = walrus::FunctionBuilder::new(); - let call1 = builder.call(prev_start, Box::new([])); - let call2 = builder.call(id, Box::new([])); - let ty = self.module.funcs.get(id).ty(); - let new_start = builder.finish(ty, Vec::new(), vec![call1, call2], self.module); - self.module.start = Some(new_start); - Ok(()) + Ok(name) } /// If a start function is present, it removes it from the `start` section @@ -2621,15 +1859,6 @@ impl<'a> Context<'a> { Ok(()) } - fn add_heap_object(&mut self, expr: &str) -> String { - if self.config.anyref { - expr.to_string() - } else { - self.expose_add_heap_object(); - format!("addHeapObject({})", expr) - } - } - fn take_object(&mut self, expr: &str) -> String { if self.config.anyref { expr.to_string() @@ -2647,548 +1876,180 @@ impl<'a> Context<'a> { format!("getObject({})", expr) } } -} -impl<'a, 'b> SubContext<'a, 'b> { - pub fn generate(&mut self) -> Result<(), Error> { - for m in self.program.local_modules.iter() { - // All local modules we find should be unique, but the same module - // may have showed up in a few different blocks. If that's the case - // all the same identifiers should have the same contents. - if let Some(prev) = self.cx.local_modules.insert(m.identifier, m.contents) { - assert_eq!(prev, m.contents); - } - } - for f in self.program.exports.iter() { - self.generate_export(f).with_context(|_| { + pub fn generate(&mut self, aux: &WasmBindgenAux) -> Result<(), Error> { + for (id, export) in aux.export_map.iter() { + self.generate_export(*id, export).with_context(|_| { format!( "failed to generate bindings for Rust export `{}`", - f.function.name + export.debug_name, ) })?; } - for f in self.program.imports.iter() { - if let decode::ImportKind::Type(ty) = &f.kind { - self.register_vendor_prefix(ty); - } + for (id, import) in aux.import_map.iter() { + let variadic = aux.imports_with_variadic.contains(&id); + let catch = aux.imports_with_catch.contains(&id); + self.generate_import(*id, import, variadic, catch) + .with_context(|_| { + format!("failed to generate bindings for import `{:?}`", import,) + })?; } - for f in self.program.imports.iter() { - self.generate_import(f)?; - } - for e in self.program.enums.iter() { + for e in aux.enums.iter() { self.generate_enum(e)?; } - for s in self.program.structs.iter() { - self.generate_struct(s).with_context(|_| { - format!("failed to generate bindings for Rust struct `{}`", s.name,) - })?; - } - for s in self.program.typescript_custom_sections.iter() { - self.cx.typescript.push_str(s); - self.cx.typescript.push_str("\n\n"); + + for s in aux.structs.iter() { + self.generate_struct(s)?; } - if let Some(path) = self.program.package_json { - self.add_package_json(path)?; + self.typescript.push_str(&aux.extra_typescript); + + for path in aux.package_jsons.iter() { + self.process_package_json(path)?; } Ok(()) } - fn generate_export(&mut self, export: &decode::Export<'b>) -> Result<(), Error> { - if let Some(ref class) = export.class { - assert!(!export.start); - return self.generate_export_for_class(class, export); - } - - let descriptor = match self.cx.describe(&export.function.name) { - None => return Ok(()), - Some(d) => d, - }; - - if export.start { - self.set_start_function(export.function.name)?; - } - - let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx) - .process(descriptor.unwrap_function(), &export.function.arg_names)? - .finish( - "function", - &format!("wasm.{}", export.function.name), - ExportedShim::Named(&export.function.name), - ); - self.cx.export( - &export.function.name, - &js, - Some(format_doc_comments(&export.comments, Some(js_doc))), - )?; - self.cx.globals.push_str("\n"); - self.cx.typescript.push_str("export "); - self.cx.typescript.push_str(&ts); - self.cx.typescript.push_str("\n"); - Ok(()) - } - - fn set_start_function(&mut self, start: &str) -> Result<(), Error> { - if let Some(prev) = &self.cx.start { - bail!( - "cannot flag `{}` as start function as `{}` is \ - already the start function", - start, - prev - ); - } - self.cx.start = Some(start.to_string()); - Ok(()) - } - - fn generate_export_for_class( - &mut self, - class_name: &'b str, - export: &decode::Export, - ) -> Result<(), Error> { - let mut fn_name = export.function.name; - let wasm_name = struct_function_export_name(class_name, fn_name); - let descriptor = match self.cx.describe(&wasm_name) { - None => return Ok(()), - Some(d) => d, - }; - let docs = |raw_docs| format_doc_comments(&export.comments, Some(raw_docs)); - let method = |class: &mut ExportedClass, docs, fn_name, fn_prfx, js, ts| { - class.contents.push_str(docs); - class.contents.push_str(fn_prfx); - class.contents.push_str(fn_name); - class.contents.push_str(js); - class.contents.push_str("\n"); - class.typescript.push_str(docs); - class.typescript.push_str(" "); // Indentation - class.typescript.push_str(fn_prfx); - class.typescript.push_str(ts); - class.typescript.push_str("\n"); - }; - let finish_j2r = |mut j2r: Js2Rust| -> Result<(_, _, _), Error> { - Ok(j2r - .process(descriptor.unwrap_function(), &export.function.arg_names)? - .finish( + fn generate_export(&mut self, id: ExportId, export: &AuxExport) -> Result<(), Error> { + let wasm_name = self.module.exports.get(id).name.clone(); + let descriptor = self.bindings.exports[&id].clone(); + match &export.kind { + AuxExportKind::Function(name) => { + let (js, ts, js_doc) = Js2Rust::new(&name, self) + .process(&descriptor, &export.arg_names)? + .finish( + "function", + &format!("wasm.{}", wasm_name), + ExportedShim::Named(&wasm_name), + ); + self.export( + &name, + &js, + Some(format_doc_comments(&export.comments, Some(js_doc))), + )?; + self.globals.push_str("\n"); + self.typescript.push_str("export "); + self.typescript.push_str(&ts); + self.typescript.push_str("\n"); + } + AuxExportKind::Constructor(class) => { + let (js, ts, raw_docs) = Js2Rust::new("constructor", self) + .constructor(Some(&class)) + .process(&descriptor, &export.arg_names)? + .finish( + "", + &format!("wasm.{}", wasm_name), + ExportedShim::Named(&wasm_name), + ); + let exported = require_class(&mut self.exported_classes, class); + if exported.has_constructor { + bail!("found duplicate constructor for class `{}`", class); + } + exported.has_constructor = true; + let docs = format_doc_comments(&export.comments, Some(raw_docs)); + exported.push(&docs, "constructor", "", &js, &ts); + } + AuxExportKind::Getter { class, field: name } + | AuxExportKind::Setter { class, field: name } + | AuxExportKind::StaticFunction { class, name } + | AuxExportKind::Method { class, name, .. } => { + let mut j2r = Js2Rust::new(name, self); + match export.kind { + AuxExportKind::StaticFunction { .. } => {} + AuxExportKind::Method { consumed: true, .. } => { + j2r.method(true); + } + _ => { + j2r.method(false); + } + } + let (js, ts, raw_docs) = j2r.process(&descriptor, &export.arg_names)?.finish( "", &format!("wasm.{}", wasm_name), ExportedShim::Named(&wasm_name), - )) - }; - match &export.method_kind { - decode::MethodKind::Constructor => { - fn_name = "constructor"; - let mut j2r = Js2Rust::new(fn_name, self.cx); - j2r.constructor(Some(class_name)); - let (js, ts, raw_docs) = finish_j2r(j2r)?; - let class = require_class(&mut self.cx.exported_classes, class_name); - if class.has_constructor { - bail!("found duplicate constructor `{}`", export.function.name); - } - class.has_constructor = true; - let docs = docs(raw_docs); - method(class, &docs, fn_name, "", &js, &ts); - Ok(()) - } - decode::MethodKind::Operation(operation) => { - let mut j2r = Js2Rust::new(fn_name, self.cx); - let mut fn_prfx = ""; - if operation.is_static { - fn_prfx = "static "; - } else { - j2r.method(export.consumed); - } - let (js, ts, raw_docs) = finish_j2r(j2r)?; - let class = require_class(&mut self.cx.exported_classes, class_name); - let docs = docs(raw_docs); - match operation.kind { - decode::OperationKind::Getter(getter_name) => { - fn_name = getter_name; - fn_prfx = "get "; + ); + let exported = require_class(&mut self.exported_classes, class); + let docs = format_doc_comments(&export.comments, Some(raw_docs)); + match export.kind { + AuxExportKind::Getter { .. } => { + exported.push(&docs, name, "get ", &js, &ts); } - decode::OperationKind::Setter(setter_name) => { - fn_name = setter_name; - fn_prfx = "set "; + AuxExportKind::Setter { .. } => { + exported.push(&docs, name, "set ", &js, &ts); + } + AuxExportKind::StaticFunction { .. } => { + exported.push(&docs, name, "static ", &js, &ts); + } + _ => { + exported.push(&docs, name, "", &js, &ts); } - _ => {} } - method(class, &docs, fn_name, fn_prfx, &js, &ts); - Ok(()) } } - } - - fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> { - match import.kind { - decode::ImportKind::Function(ref f) => { - self.generate_import_function(import, f).with_context(|_| { - format!( - "failed to generate bindings for JS import `{}`", - f.function.name - ) - })?; - } - decode::ImportKind::Static(ref s) => { - self.generate_import_static(import, s).with_context(|_| { - format!("failed to generate bindings for JS import `{}`", s.name) - })?; - } - decode::ImportKind::Type(ref ty) => { - self.generate_import_type(import, ty).with_context(|_| { - format!("failed to generate bindings for JS import `{}`", ty.name,) - })?; - } - decode::ImportKind::Enum(_) => {} - } Ok(()) } - fn generate_import_static( + fn generate_import( &mut self, - info: &decode::Import<'b>, - import: &decode::ImportStatic<'b>, + id: ImportId, + import: &AuxImport, + variadic: bool, + catch: bool, ) -> Result<(), Error> { - // The same static can be imported in multiple locations, so only - // generate bindings once for it. - if !self.cx.imported_statics.insert(import.shim) { - return Ok(()); - } - - // TODO: should support more types to import here - let obj = self.import_name(info, &import.name)?; - self.cx - .anyref - .import_xform("__wbindgen_placeholder__", &import.shim, &[], true); - let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj)); - self.cx.export(&import.shim, &body, None)?; + let signature = self.bindings.imports[&id].clone(); + let catch_and_rethrow = !self.config.debug; + let js = Rust2Js::new(self) + .catch_and_rethrow(catch_and_rethrow) + .catch(catch) + .variadic(variadic) + .process(&signature)? + .finish(import)?; + self.wasm_import_definitions.insert(id, js); Ok(()) } - fn generate_import_function( - &mut self, - info: &decode::Import<'b>, - import: &decode::ImportFunction<'b>, - ) -> Result<(), Error> { - if !self.cx.wasm_import_needed(&import.shim) { - return Ok(()); - } - - // It's possible for the same function to be imported in two locations, - // but we only want to generate one. - if !self.cx.imported_functions.insert(import.shim) { - return Ok(()); - } - - let descriptor = match self.cx.describe(&import.shim) { - None => return Ok(()), - Some(d) => d, - }; - - // Figure out the name that we're importing to dangle further references - // off of. This is the function name if there's no method all here, or - // the class if there's a method call. - let name = match &import.method { - Some(data) => self.determine_import(info, &data.class)?, - None => self.determine_import(info, import.function.name)?, - }; - - // Build up our shim's state, and we'll use that to guide whether we - // actually emit an import here or not. - let mut shim = Rust2Js::new(self.cx); - if shim.cx.config.debug { - shim.catch_and_rethrow(true); - } - shim.catch(import.catch) - .variadic(import.variadic) - .process(descriptor.unwrap_function())?; - - // If this is a bare function import and the shim doesn't actually do - // anything (all argument/return conversions are noops) then we can wire - // up the wasm import directly to the destination. We don't actually - // wire up anything here, but we record it to get wired up later. - if import.method.is_none() && shim.is_noop() { - if let Import::Module { - module, - name, - field: None, - } = name - { - shim.cx.direct_imports.insert(import.shim, (module, name)); - - if shim.ret_anyref || shim.anyref_args.len() > 0 { - shim.cx.anyref.import_xform( - "__wbindgen_placeholder__", - &import.shim, - &shim.anyref_args, - shim.ret_anyref, - ); - } - return Ok(()); - } - } - - // If the above optimization fails then we actually generate the import - // here (possibly emitting some glue in our JS module) and then emit the - // shim as the wasm will be importing the shim. - let target = shim.cx.generated_import_target(name, import)?; - let js = shim.finish(&target, &import.shim)?; - shim.cx.export(&import.shim, &js, None)?; - Ok(()) - } - - fn generate_import_type( - &mut self, - info: &decode::Import<'b>, - import: &decode::ImportType<'b>, - ) -> Result<(), Error> { - if !self.cx.wasm_import_needed(&import.instanceof_shim) { - return Ok(()); - } - let name = self.import_name(info, &import.name)?; - self.cx.anyref.import_xform( - "__wbindgen_placeholder__", - &import.instanceof_shim, - &[(0, false)], - false, - ); - let body = format!( - "function(idx) {{ return {} instanceof {} ? 1 : 0; }}", - self.cx.get_object("idx"), - name - ); - self.cx.export(&import.instanceof_shim, &body, None)?; - Ok(()) - } - - fn generate_enum(&mut self, enum_: &decode::Enum) -> Result<(), Error> { + fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> { let mut variants = String::new(); - for variant in enum_.variants.iter() { - variants.push_str(&format!("{}:{},", variant.name, variant.value)); + self.typescript + .push_str(&format!("export enum {} {{", enum_.name)); + for (name, value) in enum_.variants.iter() { + variants.push_str(&format!("{}:{},", name, value)); + self.typescript.push_str(&format!("\n {},", name)); } - self.cx.export( + self.typescript.push_str("\n}\n"); + self.export( &enum_.name, &format!("Object.freeze({{ {} }})", variants), Some(format_doc_comments(&enum_.comments, None)), )?; - self.cx - .typescript - .push_str(&format!("export enum {} {{", enum_.name)); - for variant in enum_.variants.iter() { - self.cx - .typescript - .push_str(&format!("\n {},", variant.name)); - } - self.cx.typescript.push_str("\n}\n"); Ok(()) } - fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> { - let mut dst = String::new(); - let mut ts_dst = String::new(); - for field in struct_.fields.iter() { - let wasm_getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); - let wasm_setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); - let descriptor = match self.cx.describe(&wasm_getter) { - None => continue, - Some(d) => d, - }; - - let set = { - let setter = ExportedShim::Named(&wasm_setter); - let mut cx = Js2Rust::new(&field.name, self.cx); - cx.method(false) - .argument(&descriptor, None)? - .ret(&Descriptor::Unit)?; - ts_dst.push_str(&format!( - "\n {}{}: {};", - if field.readonly { "readonly " } else { "" }, - field.name, - &cx.js_arguments[0].type_ - )); - cx.finish("", &format!("wasm.{}", wasm_setter), setter).0 - }; - let getter = ExportedShim::Named(&wasm_getter); - let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx) - .method(false) - .ret(&descriptor)? - .finish("", &format!("wasm.{}", wasm_getter), getter); - if !dst.ends_with("\n") { - dst.push_str("\n"); - } - dst.push_str(&format_doc_comments(&field.comments, Some(js_doc))); - dst.push_str("get "); - dst.push_str(&field.name); - dst.push_str(&get); - dst.push_str("\n"); - if !field.readonly { - dst.push_str("set "); - dst.push_str(&field.name); - dst.push_str(&set); - } - } - - let class = require_class(&mut self.cx.exported_classes, struct_.name); + fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> { + let class = require_class(&mut self.exported_classes, &struct_.name); class.comments = format_doc_comments(&struct_.comments, None); - class.contents.push_str(&dst); - class.contents.push_str("\n"); - class.typescript.push_str(&ts_dst); - class.typescript.push_str("\n"); Ok(()) } - fn register_vendor_prefix(&mut self, info: &decode::ImportType<'b>) { - if info.vendor_prefixes.len() == 0 { - return; - } - self.vendor_prefixes - .entry(info.name) - .or_insert(Vec::new()) - .extend(info.vendor_prefixes.iter().cloned()); - } - - fn determine_import( - &self, - import: &decode::Import<'b>, - item: &'b str, - ) -> Result, Error> { - // First up, imports don't work at all in `--target no-modules` mode as - // we're not sure how to import them. - let is_local_snippet = match import.module { - decode::ImportModule::Named(s) => self.cx.local_modules.contains_key(s), - decode::ImportModule::RawNamed(_) => false, - decode::ImportModule::Inline(_) => true, - decode::ImportModule::None => false, - }; - if self.cx.config.mode.no_modules() { - if is_local_snippet { - bail!( - "local JS snippets are not supported with `--target no-modules`; \ - use `--target web` or no flag instead", - ); - } - if let decode::ImportModule::Named(module) = &import.module { - bail!( - "import from `{}` module not allowed with `--target no-modules`; \ - use `nodejs`, `web`, or `bundler` target instead", - module - ); - } - } - - // FIXME: currently we require that local JS snippets are written in ES - // module syntax for imports/exports, but nodejs uses CommonJS to handle - // this meaning that local JS snippets are basically guaranteed to be - // incompatible. We need to implement a pass that translates the ES - // module syntax in the snippet to a CommonJS module, which is in theory - // not that hard but is a chunk of work to do. - if is_local_snippet && self.cx.config.mode.nodejs() { - // have a small unergonomic escape hatch for our webidl-tests tests - if env::var("WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE").is_err() { - bail!( - "local JS snippets are not supported with `--target nodejs`; \ - see rustwasm/rfcs#6 for more details, but this restriction \ - will be lifted in the future" - ); - } - } - - // Similar to `--target no-modules`, only allow vendor prefixes - // basically for web apis, shouldn't be necessary for things like npm - // packages or other imported items. - let vendor_prefixes = self.vendor_prefixes.get(item); - if let Some(vendor_prefixes) = vendor_prefixes { - assert!(vendor_prefixes.len() > 0); - - if is_local_snippet { - bail!( - "local JS snippets do not support vendor prefixes for \ - the import of `{}` with a polyfill of `{}`", - item, - &vendor_prefixes[0] - ); - } - if let decode::ImportModule::Named(module) = &import.module { - bail!( - "import of `{}` from `{}` has a polyfill of `{}` listed, but - vendor prefixes aren't supported when importing from modules", - item, - module, - &vendor_prefixes[0], - ); - } - if let Some(ns) = &import.js_namespace { - bail!( - "import of `{}` through js namespace `{}` isn't supported \ - right now when it lists a polyfill", - item, - ns - ); - } - return Ok(Import::VendorPrefixed { - name: item, - prefixes: vendor_prefixes.clone(), - }); - } - - let (name, field) = match import.js_namespace { - Some(ns) => (ns, Some(item)), - None => (item, None), - }; - - Ok(match import.module { - decode::ImportModule::Named(module) if is_local_snippet => Import::LocalModule { - module, - name, - field, - }, - decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { - Import::Module { - module, - name, - field, - } - } - decode::ImportModule::Inline(idx) => { - let offset = *self - .cx - .snippet_offsets - .get(self.program.unique_crate_identifier) - .unwrap_or(&0); - Import::InlineJs { - unique_crate_identifier: self.program.unique_crate_identifier, - snippet_idx_in_crate: idx as usize + offset, - name, - field, - } - } - decode::ImportModule::None => Import::Global { name, field }, - }) - } - - fn import_name(&mut self, import: &decode::Import<'b>, item: &'b str) -> Result { - let import = self.determine_import(import, item)?; - Ok(self.cx.import_identifier(import)) - } - - fn add_package_json(&mut self, path: &'b str) -> Result<(), Error> { - if !self.cx.package_json_read.insert(path) { - return Ok(()); - } - if !self.cx.config.mode.nodejs() && !self.cx.config.mode.bundler() { + fn process_package_json(&mut self, path: &Path) -> Result<(), Error> { + if !self.config.mode.nodejs() && !self.config.mode.bundler() { bail!( "NPM dependencies have been specified in `{}` but \ this is only compatible with the `bundler` and `nodejs` targets" ); } - let contents = fs::read_to_string(path).context(format!("failed to read `{}`", path))?; + + let contents = + fs::read_to_string(path).context(format!("failed to read `{}`", path.display()))?; let json: serde_json::Value = serde_json::from_str(&contents)?; let object = match json.as_object() { Some(s) => s, None => bail!( "expected `package.json` to have an JSON object in `{}`", - path + path.display() ), }; let mut iter = object.iter(); @@ -3200,12 +2061,15 @@ impl<'a, 'b> SubContext<'a, 'b> { bail!( "NPM manifest found at `{}` can currently only have one key, \ `dependencies`, and no other fields", - path + path.display() ); } let value = match value.as_object() { Some(s) => s, - None => bail!("expected `dependencies` to be a JSON object in `{}`", path), + None => bail!( + "expected `dependencies` to be a JSON object in `{}`", + path.display() + ), }; for (name, value) in value.iter() { @@ -3213,68 +2077,99 @@ impl<'a, 'b> SubContext<'a, 'b> { Some(s) => s, None => bail!( "keys in `dependencies` are expected to be strings in `{}`", - path + path.display() ), }; - if let Some((prev, _prev_version)) = self.cx.npm_dependencies.get(name) { + if let Some((prev, _prev_version)) = self.npm_dependencies.get(name) { bail!( "dependency on NPM package `{}` specified in two `package.json` files, \ which at the time is not allowed:\n * {}\n * {}", name, - path, - prev + path.display(), + prev.display(), ) } - self.cx - .npm_dependencies - .insert(name.to_string(), (path, value.to_string())); + self.npm_dependencies + .insert(name.to_string(), (path.to_path_buf(), value.to_string())); } Ok(()) } -} -#[derive(Hash, Eq, PartialEq)] -pub enum ImportModule<'a> { - Named(&'a str), - Inline(&'a str, usize), - None, -} + fn expose_debug_string(&mut self) { + if !self.should_write_global("debug_string") { + return; + } -impl<'a> Import<'a> { - fn module(&self) -> ImportModule<'a> { - match self { - Import::Module { module, .. } | Import::LocalModule { module, .. } => { - ImportModule::Named(module) + self.global( + " + function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `\"${val}\"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; } - Import::InlineJs { - unique_crate_identifier, - snippet_idx_in_crate, - .. - } => ImportModule::Inline(unique_crate_identifier, *snippet_idx_in_crate), - Import::Global { .. } | Import::VendorPrefixed { .. } => ImportModule::None, - } - } - - fn field(&self) -> Option<&'a str> { - match self { - Import::Module { field, .. } - | Import::LocalModule { field, .. } - | Import::InlineJs { field, .. } - | Import::Global { field, .. } => *field, - Import::VendorPrefixed { .. } => None, - } - } - - fn name(&self) -> &'a str { - match self { - Import::Module { name, .. } - | Import::LocalModule { name, .. } - | Import::InlineJs { name, .. } - | Import::Global { name, .. } - | Import::VendorPrefixed { name, .. } => *name, - } + ", + ); } } @@ -3290,11 +2185,8 @@ fn generate_identifier(name: &str, used_names: &mut HashMap) -> S } } -fn format_doc_comments(comments: &[&str], js_doc_comments: Option) -> String { - let body: String = comments - .iter() - .map(|c| format!("*{}\n", c.trim_matches('"'))) - .collect(); +fn format_doc_comments(comments: &str, js_doc_comments: Option) -> String { + let body: String = comments.lines().map(|c| format!("*{}\n", c)).collect(); let doc = if let Some(docs) = js_doc_comments { docs.lines().map(|l| format!("* {} \n", l)).collect() } else { @@ -3314,6 +2206,21 @@ fn require_class<'a>( .or_insert_with(ExportedClass::default) } +impl ExportedClass { + fn push(&mut self, docs: &str, function_name: &str, function_prefix: &str, js: &str, ts: &str) { + self.contents.push_str(docs); + self.contents.push_str(function_prefix); + self.contents.push_str(function_name); + self.contents.push_str(js); + self.contents.push_str("\n"); + self.typescript.push_str(docs); + self.typescript.push_str(" "); + self.typescript.push_str(function_prefix); + self.typescript.push_str(ts); + self.typescript.push_str("\n"); + } +} + #[test] fn test_generate_identifier() { let mut used_names: HashMap = HashMap::new(); diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 877b3396..738fa763 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,12 +1,14 @@ -use crate::descriptor::{Descriptor, Function}; +use crate::descriptor::Descriptor; +use crate::intrinsic::Intrinsic; use crate::js::js2rust::ExportedShim; -use crate::js::{Context, ImportTarget, Js2Rust}; +use crate::js::{Context, Js2Rust}; +use crate::webidl::{AuxImport, AuxValue, ImportBinding}; use failure::{bail, Error}; /// Helper struct for manufacturing a shim in JS used to translate Rust types to /// JS, then invoking an imported JS function. pub struct Rust2Js<'a, 'b: 'a> { - pub cx: &'a mut Context<'b>, + cx: &'a mut Context<'b>, /// Arguments of the JS shim that we're generating, aka the variables passed /// from Rust which are only numbers. @@ -41,10 +43,27 @@ pub struct Rust2Js<'a, 'b: 'a> { /// Whether or not the last argument is a slice representing variadic arguments. variadic: bool, + /// What sort of style this invocation will be like, see the variants of + /// this enum for more information. + style: Style, + /// list of arguments that are anyref, and whether they're an owned anyref /// or not. - pub anyref_args: Vec<(usize, bool)>, - pub ret_anyref: bool, + anyref_args: Vec<(usize, bool)>, + ret_anyref: bool, +} + +#[derive(PartialEq)] +enum Style { + /// The imported function is expected to be invoked with `new` to create a + /// JS object. + Constructor, + /// The imported function is expected to be invoked where the first + /// parameter is the `this` and the rest of the arguments are the + /// function's arguments. + Method, + /// Just a normal function call. + Function, } impl<'a, 'b> Rust2Js<'a, 'b> { @@ -63,6 +82,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { variadic: false, anyref_args: Vec::new(), ret_anyref: false, + style: Style::Function, } } @@ -83,7 +103,21 @@ impl<'a, 'b> Rust2Js<'a, 'b> { /// Generates all bindings necessary for the signature in `Function`, /// creating necessary argument conversions and return value processing. - pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> { + pub fn process(&mut self, binding: &ImportBinding) -> Result<&mut Self, Error> { + let function = match binding { + ImportBinding::Constructor(f) => { + self.style = Style::Constructor; + f + } + ImportBinding::Method(f) => { + self.style = Style::Method; + f + } + ImportBinding::Function(f) => { + self.style = Style::Function; + f + } + }; for arg in function.arguments.iter() { self.argument(arg)?; } @@ -286,7 +320,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } else { builder.rust_argument("this.a"); } - builder.rust_argument("this.b").process(f, None)?.finish( + builder.rust_argument("this.b").process(f, &None)?.finish( "function", "this.f", ExportedShim::TableElement(&mut shim), @@ -575,7 +609,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { /// /// This is used as an optimization to wire up imports directly where /// possible and avoid a shim in some circumstances. - pub fn is_noop(&self) -> bool { + fn is_noop(&self) -> bool { let Rust2Js { // fields which may affect whether we do nontrivial work catch, @@ -594,6 +628,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { global_idx: _, anyref_args: _, ret_anyref: _, + style, } = self; !catch && @@ -607,96 +642,277 @@ impl<'a, 'b> Rust2Js<'a, 'b> { // similarly we want to make sure that all the arguments are simply // forwarded from the shim we would generate to the import, // requiring no transformations - js_arguments == shim_arguments + js_arguments == shim_arguments && + // method/constructor invocations require some JS shimming right + // now, so only normal function-style invocations may get wired up + *style == Style::Function } - pub fn finish(&mut self, invoc: &ImportTarget, shim: &str) -> Result { - let mut ret = String::new(); - ret.push_str("function("); - ret.push_str(&self.shim_arguments.join(", ")); - if self.catch { - if self.shim_arguments.len() > 0 { - ret.push_str(", ") - } - ret.push_str("exnptr"); - } - ret.push_str(") {\n"); - ret.push_str(&self.prelude); - + pub fn finish(&mut self, target: &AuxImport) -> Result { let variadic = self.variadic; - let ret_expr = &self.ret_expr; - let handle_variadic = |invoc: &str, js_arguments: &[String]| { - let ret = if variadic { + let variadic_args = |js_arguments: &[String]| { + Ok(if !variadic { + format!("{}", js_arguments.join(", ")) + } else { let (last_arg, args) = match js_arguments.split_last() { Some(pair) => pair, None => bail!("a function with no arguments cannot be variadic"), }; if args.len() > 0 { - ret_expr.replace( - "JS", - &format!("{}({}, ...{})", invoc, args.join(", "), last_arg), - ) + format!("{}, ...{}", args.join(", "), last_arg) } else { - ret_expr.replace("JS", &format!("{}(...{})", invoc, last_arg)) + format!("...{}", last_arg) } - } else { - ret_expr.replace("JS", &format!("{}({})", invoc, js_arguments.join(", "))) - }; - Ok(ret) + }) }; - let js_arguments = &self.js_arguments; - let fixed = |desc: &str, class: &Option, amt: usize| { - if variadic { - bail!("{} cannot be variadic", desc); - } - match (class, js_arguments.len()) { - (None, n) if n == amt + 1 => Ok((js_arguments[0].clone(), &js_arguments[1..])), - (None, _) => bail!("setters must have {} arguments", amt + 1), - (Some(class), n) if n == amt => Ok((class.clone(), &js_arguments[..])), - (Some(_), _) => bail!("static setters must have {} arguments", amt), - } - }; + let invoc = match target { + AuxImport::Value(val) => match self.style { + Style::Constructor => { + let js = match val { + AuxValue::Bare(js) => self.cx.import_name(js)?, + _ => bail!("invalid import set for constructor"), + }; + format!("new {}({})", js, variadic_args(&self.js_arguments)?) + } + Style::Method => { + let descriptor = |anchor: &str, extra: &str, field: &str, which:&str| { + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", + anchor, extra, field, which + ) + }; + let js = match val { + AuxValue::Bare(js) => self.cx.import_name(js)?, + AuxValue::Getter(class, field) => { + self.cx.expose_get_inherited_descriptor(); + let class = self.cx.import_name(class)?; + descriptor(&class, ".prototype", field, "get") + } + AuxValue::ClassGetter(class, field) => { + self.cx.expose_get_inherited_descriptor(); + let class = self.cx.import_name(class)?; + descriptor(&class, "", field, "get") + } + AuxValue::Setter(class, field) => { + self.cx.expose_get_inherited_descriptor(); + let class = self.cx.import_name(class)?; + descriptor(&class, ".prototype", field, "set") + } + AuxValue::ClassSetter(class, field) => { + self.cx.expose_get_inherited_descriptor(); + let class = self.cx.import_name(class)?; + descriptor(&class, "", field, "set") + } + }; + format!("{}.call({})", js, variadic_args(&self.js_arguments)?) + } + Style::Function => { + let js = match val { + AuxValue::Bare(js) => self.cx.import_name(js)?, + _ => bail!("invalid import set for constructor"), + }; + if self.is_noop() { + self.cx.expose_does_not_exist(); + // TODO: comment this + let js = format!("typeof {} === 'undefined' ? doesNotExist : {0}", js); + return Ok(js); + } + format!("{}({})", js, variadic_args(&self.js_arguments)?) + } + }, - let mut invoc = match invoc { - ImportTarget::Function(f) => handle_variadic(&f, &self.js_arguments)?, - ImportTarget::Constructor(c) => { - handle_variadic(&format!("new {}", c), &self.js_arguments)? + AuxImport::Instanceof(js) => { + let js = self.cx.import_name(js)?; + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + format!("{} instanceof {}", self.js_arguments[0], js) } - ImportTarget::Method(f) => handle_variadic(&format!("{}.call", f), &self.js_arguments)?, - ImportTarget::StructuralMethod(f) => { + + AuxImport::Static(js) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 0); + self.cx.import_name(js)? + } + + AuxImport::Closure(closure) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 3); + let mut shim = closure.shim_idx; + let (js, _ts, _js_doc) = { + let mut builder = Js2Rust::new("", self.cx); + + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + builder.prelude("this.cnt++;"); + + if closure.mutable { + // For mutable closures they can't be invoked recursively. + // To handle that we swap out the `this.a` pointer with zero + // while we invoke it. If we finish and the closure wasn't + // destroyed, then we put back the pointer so a future + // invocation can succeed. + builder + .prelude("let a = this.a;") + .prelude("this.a = 0;") + .rust_argument("a") + .rust_argument("b") + .finally("if (--this.cnt === 0) d(a, b);") + .finally("else this.a = a;"); + } else { + // For shared closures they can be invoked recursively so we + // just immediately pass through `this.a`. If we end up + // executing the destructor, however, we clear out the + // `this.a` pointer to prevent it being used again the + // future. + builder + .rust_argument("this.a") + .rust_argument("b") + .finally("if (--this.cnt === 0) {") + .finally("d(this.a, b);") + .finally("this.a = 0;") + .finally("}"); + } + builder.process(&closure.function, &None)?.finish( + "function", + "f", + ExportedShim::TableElement(&mut shim), + ) + }; + self.cx.function_table_needed = true; + let body = format!( + " + const f = wasm.__wbg_function_table.get({}); + const d = wasm.__wbg_function_table.get({}); + const b = {}; + const cb = {}; + cb.a = {}; + cb.cnt = 1; + let real = cb.bind(cb); + real.original = cb; + ", + shim, + closure.dtor_idx, + &self.js_arguments[1], + js, + &self.js_arguments[0], + ); + self.prelude(&body); + "real".to_string() + } + + AuxImport::StructuralMethod(name) => { + assert!(self.style == Style::Function); let (receiver, args) = match self.js_arguments.split_first() { Some(pair) => pair, - None => bail!("methods must have at least one argument"), + None => bail!("structural method calls must have at least one argument"), }; - handle_variadic(&format!("{}.{}", receiver, f), args)? + format!("{}.{}({})", receiver, name, variadic_args(args)?) } - ImportTarget::StructuralGetter(class, field) => { - let (receiver, _) = fixed("getter", class, 0)?; - let expr = format!("{}.{}", receiver, field); - self.ret_expr.replace("JS", &expr) + + AuxImport::StructuralGetter(field) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + format!("{}.{}", self.js_arguments[0], field) } - ImportTarget::StructuralSetter(class, field) => { - let (receiver, val) = fixed("setter", class, 1)?; - let expr = format!("{}.{} = {}", receiver, field, val[0]); - self.ret_expr.replace("JS", &expr) + + AuxImport::StructuralClassGetter(class, field) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 0); + let class = self.cx.import_name(class)?; + format!("{}.{}", class, field) } - ImportTarget::StructuralIndexingGetter(class) => { - let (receiver, field) = fixed("indexing getter", class, 1)?; - let expr = format!("{}[{}]", receiver, field[0]); - self.ret_expr.replace("JS", &expr) + + AuxImport::StructuralSetter(field) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 2); + format!( + "{}.{} = {}", + self.js_arguments[0], field, self.js_arguments[1] + ) } - ImportTarget::StructuralIndexingSetter(class) => { - let (receiver, field) = fixed("indexing setter", class, 2)?; - let expr = format!("{}[{}] = {}", receiver, field[0], field[1]); - self.ret_expr.replace("JS", &expr) + + AuxImport::StructuralClassSetter(class, field) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + let class = self.cx.import_name(class)?; + format!("{}.{} = {}", class, field, self.js_arguments[0]) } - ImportTarget::StructuralIndexingDeleter(class) => { - let (receiver, field) = fixed("indexing deleter", class, 1)?; - let expr = format!("delete {}[{}]", receiver, field[0]); - self.ret_expr.replace("JS", &expr) + + AuxImport::IndexingGetterOfClass(class) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + let class = self.cx.import_name(class)?; + format!("{}[{}]", class, self.js_arguments[0]) + } + + AuxImport::IndexingGetterOfObject => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 2); + format!("{}[{}]", self.js_arguments[0], self.js_arguments[1]) + } + + AuxImport::IndexingSetterOfClass(class) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 2); + let class = self.cx.import_name(class)?; + format!( + "{}[{}] = {}", + class, self.js_arguments[0], self.js_arguments[1] + ) + } + + AuxImport::IndexingSetterOfObject => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 3); + format!( + "{}[{}] = {}", + self.js_arguments[0], self.js_arguments[1], self.js_arguments[2] + ) + } + + AuxImport::IndexingDeleterOfClass(class) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + let class = self.cx.import_name(class)?; + format!("delete {}[{}]", class, self.js_arguments[0]) + } + + AuxImport::IndexingDeleterOfObject => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 2); + format!("delete {}[{}]", self.js_arguments[0], self.js_arguments[1]) + } + + AuxImport::WrapInExportedClass(class) => { + assert!(self.style == Style::Function); + assert!(!variadic); + assert_eq!(self.js_arguments.len(), 1); + self.cx.require_class_wrap(class); + format!("{}.__wrap({})", class, self.js_arguments[0]) + } + + AuxImport::Intrinsic(intrinsic) => { + assert!(self.style == Style::Function); + assert!(!variadic); + self.intrinsic_expr(intrinsic)? } }; + let mut invoc = self.ret_expr.replace("JS", &invoc); if self.catch { self.cx.expose_handle_error()?; @@ -725,13 +941,13 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return \"\"; }} }}()); - console.error(\"wasm-bindgen: imported JS function `{}` that \ + console.error(\"wasm-bindgen: imported JS function that \ was not marked as `catch` threw an error:\", \ error); throw e; }}\ ", - &invoc, shim, + &invoc, ); } @@ -747,35 +963,46 @@ impl<'a, 'b> Rust2Js<'a, 'b> { &invoc, &self.finally ); } - ret.push_str(&invoc); + let mut ret = String::new(); + ret.push_str("function("); + ret.push_str(&self.shim_arguments.join(", ")); + if self.catch { + if self.shim_arguments.len() > 0 { + ret.push_str(", ") + } + ret.push_str("exnptr"); + } + ret.push_str(") {\n"); + ret.push_str(&self.prelude); + ret.push_str(&invoc); ret.push_str("\n}\n"); - if self.ret_anyref || self.anyref_args.len() > 0 { - // Some return values go at the the beginning of the argument list - // (they force a return pointer). Handle that here by offsetting all - // our arg indices by one, but throw in some sanity checks for if - // this ever changes. - if let Some(start) = self.shim_arguments.get(0) { - if start == "ret" { - assert!(!self.ret_anyref); - if let Some(next) = self.shim_arguments.get(1) { - assert_eq!(next, "arg0"); - } - for (idx, _) in self.anyref_args.iter_mut() { - *idx += 1; - } - } else { - assert_eq!(start, "arg0"); - } - } - self.cx.anyref.import_xform( - "__wbindgen_placeholder__", - shim, - &self.anyref_args, - self.ret_anyref, - ); - } + // if self.ret_anyref || self.anyref_args.len() > 0 { + // // Some return values go at the the beginning of the argument list + // // (they force a return pointer). Handle that here by offsetting all + // // our arg indices by one, but throw in some sanity checks for if + // // this ever changes. + // if let Some(start) = self.shim_arguments.get(0) { + // if start == "ret" { + // assert!(!self.ret_anyref); + // if let Some(next) = self.shim_arguments.get(1) { + // assert_eq!(next, "arg0"); + // } + // for (idx, _) in self.anyref_args.iter_mut() { + // *idx += 1; + // } + // } else { + // assert_eq!(start, "arg0"); + // } + // } + // self.cx.anyref.import_xform( + // "__wbindgen_placeholder__", + // shim, + // &self.anyref_args, + // self.ret_anyref, + // ); + // } Ok(ret) } @@ -801,4 +1028,213 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } self } + + fn intrinsic_expr(&mut self, intrinsic: &Intrinsic) -> Result { + let expr = match intrinsic { + Intrinsic::JsvalEq => { + assert_eq!(self.js_arguments.len(), 2); + format!("{} == {}", self.js_arguments[0], self.js_arguments[1]) + } + + Intrinsic::IsFunction => { + assert_eq!(self.js_arguments.len(), 1); + format!("typeof({}) === 'function'", self.js_arguments[0]) + } + + Intrinsic::IsUndefined => { + assert_eq!(self.js_arguments.len(), 1); + format!("{} === undefined", self.js_arguments[0]) + } + + Intrinsic::IsNull => { + assert_eq!(self.js_arguments.len(), 1); + format!("{} === null", self.js_arguments[0]) + } + + Intrinsic::IsObject => { + assert_eq!(self.js_arguments.len(), 1); + self.prelude(&format!("const val = {};", self.js_arguments[0])); + format!("typeof(val) === 'object' && val !== null ? 1 : 0") + } + + Intrinsic::IsSymbol => { + assert_eq!(self.js_arguments.len(), 1); + format!("typeof({}) === 'symbol'", self.js_arguments[0]) + } + + Intrinsic::IsString => { + assert_eq!(self.js_arguments.len(), 1); + format!("typeof({}) === 'string'", self.js_arguments[0]) + } + + Intrinsic::ObjectCloneRef => { + assert_eq!(self.js_arguments.len(), 1); + self.js_arguments[0].clone() + } + + Intrinsic::ObjectDropRef => { + assert_eq!(self.js_arguments.len(), 1); + self.js_arguments[0].clone() + } + + Intrinsic::CallbackDrop => { + assert_eq!(self.js_arguments.len(), 1); + self.prelude(&format!("const obj = {}.original;", self.js_arguments[0])); + self.prelude("if (obj.cnt-- == 1) {"); + self.prelude("obj.a = 0;"); + self.prelude("return true;"); + self.prelude("}"); + "false".to_string() + } + + Intrinsic::CallbackForget => { + assert_eq!(self.js_arguments.len(), 1); + self.js_arguments[0].clone() + } + + Intrinsic::NumberNew => { + assert_eq!(self.js_arguments.len(), 1); + self.js_arguments[0].clone() + } + + Intrinsic::StringNew => { + assert_eq!(self.js_arguments.len(), 1); + self.js_arguments[0].clone() + } + + Intrinsic::SymbolNew => { + // FIXME: should probably have two intrinsics, one for a + // new anonymous symbol and one for a symbol with a string + assert_eq!(self.js_arguments.len(), 2); + self.cx.expose_get_string_from_wasm()?; + format!( + "{} === 0 ? Symbol() : Symbol(getStringFromWasm({0}, {}))", + self.js_arguments[0], self.js_arguments[1] + ) + } + + Intrinsic::NumberGet => { + assert_eq!(self.js_arguments.len(), 2); + self.cx.expose_uint8_memory(); + self.prelude(&format!("const obj = {};", self.js_arguments[0])); + self.prelude("if (typeof(obj) === 'number') return obj;"); + self.prelude(&format!("getUint8Memory()[{}] = 1;", self.js_arguments[1])); + "0".to_string() + } + + Intrinsic::StringGet => { + self.cx.expose_pass_string_to_wasm()?; + self.cx.expose_uint32_memory(); + assert_eq!(self.js_arguments.len(), 2); + self.prelude(&format!("const obj = {};", self.js_arguments[0])); + self.prelude("if (typeof(obj) !== 'string') return 0;"); + self.prelude("const ptr = passStringToWasm(obj);"); + self.prelude(&format!( + "getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;", + self.js_arguments[1], + )); + "ptr".to_string() + } + + Intrinsic::BooleanGet => { + assert_eq!(self.js_arguments.len(), 1); + self.prelude(&format!("const v = {};", self.js_arguments[0])); + format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2") + } + Intrinsic::Throw => { + assert_eq!(self.js_arguments.len(), 1); + format!("throw new Error({})", self.js_arguments[0]) + } + + Intrinsic::Rethrow => { + assert_eq!(self.js_arguments.len(), 1); + format!("throw {}", self.js_arguments[0]) + } + + Intrinsic::Module => { + assert_eq!(self.js_arguments.len(), 0); + if !self.cx.config.mode.no_modules() && !self.cx.config.mode.web() { + bail!( + "`wasm_bindgen::module` is currently only supported with \ + `--target no-modules` and `--target web`" + ); + } + format!("init.__wbindgen_wasm_module") + } + + Intrinsic::Memory => { + assert_eq!(self.js_arguments.len(), 0); + self.cx.memory().to_string() + } + + Intrinsic::FunctionTable => { + assert_eq!(self.js_arguments.len(), 0); + self.cx.function_table_needed = true; + format!("wasm.__wbg_function_table") + } + + Intrinsic::DebugString => { + assert_eq!(self.js_arguments.len(), 1); + self.cx.expose_debug_string(); + format!("debugString({})", self.js_arguments[0]) + } + + Intrinsic::JsonParse => { + assert_eq!(self.js_arguments.len(), 1); + format!("JSON.parse({})", self.js_arguments[0]) + } + + Intrinsic::JsonSerialize => { + assert_eq!(self.js_arguments.len(), 1); + format!("JSON.stringify({})", self.js_arguments[0]) + } + + Intrinsic::AnyrefHeapLiveCount => { + assert_eq!(self.js_arguments.len(), 0); + if self.cx.config.anyref { + // Eventually we should add support to the anyref-xform to + // re-write calls to the imported + // `__wbindgen_anyref_heap_live_count` function into calls to + // the exported `__wbindgen_anyref_heap_live_count_impl` + // function, and to un-export that function. + // + // But for now, we just bounce wasm -> js -> wasm because it is + // easy. + "wasm.__wbindgen_anyref_heap_live_count_impl()".into() + } else { + self.cx.expose_global_heap(); + self.prelude( + " + let free_count = 0; + let next = heap_next; + while (next < heap.length) { + free_count += 1; + next = heap[next]; + } + ", + ); + format!( + "heap.length - free_count - {} - {}", + super::INITIAL_HEAP_OFFSET, + super::INITIAL_HEAP_VALUES.len(), + ) + } + } + + Intrinsic::InitAnyrefTable => { + self.cx.expose_anyref_table(); + String::from( + " + const table = wasm.__wbg_anyref_table; + const offset = table.grow(4); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ", + ) + } + }; + Ok(expr) + } } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index ec3dbef0..b43a251c 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -10,9 +10,12 @@ use std::str; use walrus::Module; mod decode; +mod intrinsic; mod descriptor; +mod descriptors; mod js; pub mod wasm2es6js; +mod webidl; pub struct Bindgen { input: Input, @@ -282,99 +285,69 @@ impl Bindgen { ); } - let mut program_storage = Vec::new(); - let programs = extract_programs(&mut module, &mut program_storage) - .with_context(|_| "failed to extract wasm-bindgen custom sections")?; - if let Some(cfg) = &self.threads { cfg.run(&mut module) .with_context(|_| "failed to prepare module for threading")?; } + // If requested, turn all mangled symbols into prettier unmangled + // symbols with the help of `rustc-demangle`. if self.demangle { demangle(&mut module); } + unexported_unused_lld_things(&mut module); - // Here we're actually instantiating the module we've parsed above for - // execution. Why, you might be asking, are we executing wasm code? A - // good question! - // - // Transmitting information from `#[wasm_bindgen]` here to the CLI tool - // is pretty tricky. Specifically information about the types involved - // with a function signature (especially generic ones) can be hefty to - // translate over. As a result, the macro emits a bunch of shims which, - // when executed, will describe to us what the types look like. - // - // This means that whenever we encounter an import or export we'll - // execute a shim function which informs us about its type so we can - // then generate the appropriate bindings. - let mut instance = wasm_bindgen_wasm_interpreter::Interpreter::new(&module)?; + // We're making quite a few changes, list ourselves as a producer. + module + .producers + .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version()); - let mut memories = module.memories.iter().map(|m| m.id()); - let memory = memories.next(); - if memories.next().is_some() { - bail!("multiple memories currently not supported"); + // Learn about the type signatures of all wasm-bindgen imports and + // exports by executing `__wbindgen_describe_*` functions. This'll + // effectively move all the descriptor functions to their own custom + // sections. + descriptors::execute(&mut module)?; + + // Process and remove our raw custom sections emitted by the + // #[wasm_bindgen] macro and the compiler. In their stead insert a + // forward-compatible WebIDL bindings section (forward-compatible with + // the webidl bindings proposal) as well as an auxiliary section for all + // sorts of miscellaneous information and features #[wasm_bindgen] + // supports that aren't covered by WebIDL bindings. + webidl::process(&mut module)?; + + // If we're in a testing mode then remove the start function since we + // shouldn't execute it. + if !self.emit_start { + module.start = None; } - drop(memories); - let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None)); + // Now that our module is massaged and good to go, feed it into the JS + // shim generation which will actually generate JS for all this. let (js, ts) = { - let mut cx = js::Context { - globals: String::new(), - imports: String::new(), - imports_post: String::new(), - footer: String::new(), - typescript: format!("/* tslint:disable */\n"), - exposed_globals: Some(Default::default()), - required_internal_exports: Default::default(), - imported_names: Default::default(), - defined_identifiers: Default::default(), - exported_classes: Some(Default::default()), - config: &self, - module: &mut module, - function_table_needed: false, - interpreter: &mut instance, - memory, - imported_functions: Default::default(), - imported_statics: Default::default(), - direct_imports: Default::default(), - local_modules: Default::default(), - start: None, - anyref: Default::default(), - snippet_offsets: Default::default(), - npm_dependencies: Default::default(), - package_json_read: Default::default(), - }; + let mut cx = js::Context::new(&mut module, self)?; cx.anyref.enabled = self.anyref; cx.anyref.prepare(cx.module)?; - for program in programs.iter() { - js::SubContext { - program, - cx: &mut cx, - vendor_prefixes: Default::default(), - } - .generate()?; - let offset = cx - .snippet_offsets - .entry(program.unique_crate_identifier) - .or_insert(0); - for js in program.inline_js.iter() { - let name = format!("inline{}.js", *offset); - *offset += 1; + let aux = cx.module.customs.delete_typed::() + .expect("aux section should be present"); + cx.generate(&aux)?; + + // Write out all local JS snippets to the final destination now that + // we've collected them from all the programs. + for (identifier, list) in aux.snippets.iter() { + for (i, js) in list.iter().enumerate() { + let name = format!("inline{}.js", i); let path = out_dir .join("snippets") - .join(program.unique_crate_identifier) + .join(identifier) .join(name); fs::create_dir_all(path.parent().unwrap())?; fs::write(&path, js) .with_context(|_| format!("failed to write `{}`", path.display()))?; } } - - // Write out all local JS snippets to the final destination now that - // we've collected them from all the programs. - for (path, contents) in cx.local_modules.iter() { + for (path, contents) in aux.local_modules.iter() { let path = out_dir.join("snippets").join(path); fs::create_dir_all(path.parent().unwrap())?; fs::write(&path, contents) @@ -394,6 +367,8 @@ impl Bindgen { cx.finalize(stem)? }; + // And now that we've got all our JS and TypeScript, actually write it + // out to the filesystem. let extension = if self.mode.nodejs_experimental_modules() { "mjs" } else { @@ -503,127 +478,6 @@ impl Bindgen { } } -fn extract_programs<'a>( - module: &mut Module, - program_storage: &'a mut Vec>, -) -> Result>, Error> { - let my_version = wasm_bindgen_shared::version(); - assert!(program_storage.is_empty()); - - while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") { - log::debug!( - "custom section '{}' looks like a wasm bindgen section", - raw.name - ); - program_storage.push(raw.data); - } - - let mut ret = Vec::new(); - for program in program_storage.iter() { - let mut payload = &program[..]; - while let Some(data) = get_remaining(&mut payload) { - // Historical versions of wasm-bindgen have used JSON as the custom - // data section format. Newer versions, however, are using a custom - // serialization protocol that looks much more like the wasm spec. - // - // We, however, want a sanity check to ensure that if we're running - // against the wrong wasm-bindgen we get a nicer error than an - // internal decode error. To that end we continue to verify a tiny - // bit of json at the beginning of each blob before moving to the - // next blob. This should keep us compatible with older wasm-bindgen - // instances as well as forward-compatible for now. - // - // Note, though, that as `wasm-pack` picks up steam it's hoped we - // can just delete this entirely. The `wasm-pack` project already - // manages versions for us, so we in theory should need this check - // less and less over time. - if let Some(their_version) = verify_schema_matches(data)? { - bail!( - " - -it looks like the Rust project used to create this wasm file was linked against -a different version of wasm-bindgen than this binary: - - rust wasm file: {} - this binary: {} - -Currently the bindgen format is unstable enough that these two version must -exactly match, so it's required that these two version are kept in sync by -either updating the wasm-bindgen dependency or this binary. You should be able -to update the wasm-bindgen dependency with: - - cargo update -p wasm-bindgen - -or you can update the binary with - - cargo install -f wasm-bindgen-cli - -if this warning fails to go away though and you're not sure what to do feel free -to open an issue at https://github.com/rustwasm/wasm-bindgen/issues! -", - their_version, - my_version, - ); - } - let next = get_remaining(&mut payload).unwrap(); - log::debug!("found a program of length {}", next.len()); - ret.push(::decode_all(next)); - } - } - Ok(ret) -} - -fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> { - if data.len() == 0 { - return None; - } - let len = ((data[0] as usize) << 0) - | ((data[1] as usize) << 8) - | ((data[2] as usize) << 16) - | ((data[3] as usize) << 24); - let (a, b) = data[4..].split_at(len); - *data = b; - Some(a) -} - -fn verify_schema_matches<'a>(data: &'a [u8]) -> Result, Error> { - macro_rules! bad { - () => { - bail!("failed to decode what looked like wasm-bindgen data") - }; - } - let data = match str::from_utf8(data) { - Ok(s) => s, - Err(_) => bad!(), - }; - log::debug!("found version specifier {}", data); - if !data.starts_with("{") || !data.ends_with("}") { - bad!() - } - let needle = "\"schema_version\":\""; - let rest = match data.find(needle) { - Some(i) => &data[i + needle.len()..], - None => bad!(), - }; - let their_schema_version = match rest.find("\"") { - Some(i) => &rest[..i], - None => bad!(), - }; - if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION { - return Ok(None); - } - let needle = "\"version\":\""; - let rest = match data.find(needle) { - Some(i) => &data[i + needle.len()..], - None => bad!(), - }; - let their_version = match rest.find("\"") { - Some(i) => &rest[..i], - None => bad!(), - }; - Ok(Some(their_version)) -} - fn reset_indentation(s: &str) -> String { let mut indent: u32 = 0; let mut dst = String::new(); @@ -728,3 +582,21 @@ impl OutputMode { } } } + +/// Remove a number of internal exports that are synthesized by Rust's linker, +/// LLD. These exports aren't typically ever needed and just add extra space to +/// the binary. +fn unexported_unused_lld_things(module: &mut Module) { + let mut to_remove = Vec::new(); + for export in module.exports.iter() { + match export.name.as_str() { + "__heap_base" | "__data_end" | "__indirect_function_table" => { + to_remove.push(export.id()); + } + _ => {} + } + } + for id in to_remove { + module.exports.delete(id); + } +} diff --git a/crates/cli-support/src/webidl.rs b/crates/cli-support/src/webidl.rs new file mode 100644 index 00000000..c8166a05 --- /dev/null +++ b/crates/cli-support/src/webidl.rs @@ -0,0 +1,1294 @@ +//! The polyfill for the WebIDL bindings proposal in wasm-bindgen. +//! +//! This module contains the polyfill (or at least the current state of and as +//! closely as we can match) the WebIDL bindings proposal. The module exports +//! one main function, `process`, which takes a `walrus::Module`. This module is +//! expected to have two items: +//! +//! * First it contains all of the raw wasm-bindgen modules emitted by the Rust +//! compiler. These raw custom sections are extracted, removed, decoded, and +//! handled here. They contain information such as what's exported where, +//! what's imported, comments, etc. +//! * Second, the `descriptors.rs` pass must have run previously to execute all +//! the descriptor functions in the wasm module. Through the synthesized +//! custom section there we learn the type information of all +//! functions/imports/exports in the module. +//! +//! The output of this function is then a new `walrus::Module` with the previous +//! custom sections all removed and two new ones inserted. One is the webidl +//! bindings custom section (or at least a close approximate) and the second is +//! an auxiliary section for wasm-bindgen itself. The goal is for this auxiliary +//! section to eventually be empty or inconsequential, allowing us to emit +//! something that doesn't even need a JS shim one day. For now we're still +//! pretty far away from that, so we'll settle for using webidl bindings as +//! aggressively as possible! + +use crate::decode; +use crate::descriptor::{Closure, Descriptor, Function}; +use crate::descriptors::WasmBindgenDescriptorsSection; +use crate::intrinsic::Intrinsic; +use failure::{bail, Error}; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; +use std::str; +use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; +use wasm_bindgen_shared::struct_function_export_name; + +const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; + +/// A "dummy" WebIDL custom section. This should be replaced with a true +/// polyfill for the WebIDL bindings proposal. +#[derive(Default, Debug)] +pub struct WebidlCustomSection { + /// A map from exported function id to the expected signature of the + /// interface. + /// + /// The expected signature will contain rich types like strings/js + /// values/etc. A WebIDL binding will be needed to ensure the JS export of + /// the wasm mdoule either has this expected signature or a shim will need + /// to get generated to ensure the right signature in JS is respected. + pub exports: HashMap, + + /// A map from imported function id to the expected binding of the + /// interface. + /// + /// This will directly translate to WebIDL bindings and how it's expected + /// that each import is invoked. Note that this also affects the polyfill + /// glue generated. + pub imports: HashMap, +} + +pub type WebidlCustomSectionId = TypedCustomSectionId; + +/// The types of functionality that can be imported and listed for each import +/// in a wasm module. +#[derive(Debug, Clone)] +pub enum ImportBinding { + /// The imported function is considered to be a constructor, and will be + /// invoked as if it has `new` in JS. The returned value is expected + /// to be `anyref`. + Constructor(Function), + /// The imported function is considered to be a function that's called like + /// a method in JS where the first argument should be `anyref` and it is + /// passed as the `this` of the call. + Method(Function), + /// Just a bland normal import which represents some sort of function to + /// call, not much fancy going on here. + Function(Function), +} + +/// A synthetic custom section which is not standardized, never will be, and +/// cannot be serialized or parsed. This is synthesized from all of the +/// compiler-emitted wasm-bindgen sections and then immediately removed to be +/// processed in the JS generation pass. +#[derive(Default, Debug)] +pub struct WasmBindgenAux { + /// Extra typescript annotations that should be appended to the generated + /// TypeScript file. This is provided via a custom attribute in Rust code. + pub extra_typescript: String, + + /// A map from identifier to the contents of each local module defined via + /// the `#[wasm_bindgen(module = "/foo.js")]` import options. + pub local_modules: HashMap, + + /// A map from unique crate identifier to the list of inline JS snippets for + /// that crate identifier. + pub snippets: HashMap>, + + /// A list of all `package.json` files that are intended to be included in + /// the final build. + pub package_jsons: HashSet, + + /// A map from exported function id to where it's expected to be exported + /// to. + pub export_map: HashMap, + + /// A map from imported function id to what it's expected to import. + pub import_map: HashMap, + + /// Small bits of metadata about imports. + pub imports_with_catch: HashSet, + pub imports_with_variadic: HashSet, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported enums from Rust. + pub enums: Vec, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported structs from Rust and their fields they've got exported. + pub structs: Vec, +} + +pub type WasmBindgenAuxId = TypedCustomSectionId; + +#[derive(Debug)] +pub struct AuxExport { + /// When generating errors about this export, a helpful name to remember it + /// by. + pub debug_name: String, + /// Comments parsed in Rust and forwarded here to show up in JS bindings. + pub comments: String, + /// Argument names in Rust forwarded here to configure the names that show + /// up in TypeScript bindings. + pub arg_names: Option>, + /// What kind of function this is and where it shows up + pub kind: AuxExportKind, +} + +/// All possible kinds of exports from a wasm module. +/// +/// This `enum` says where to place an exported wasm function. For example it +/// may want to get hooked up to a JS class, or it may want to be exported as a +/// free function (etc). +/// +/// TODO: it feels like this should not really be here per se. We probably want +/// to either construct the JS object itself from within wasm or somehow move +/// more of this information into some other section. Really what this is is +/// sort of an "export map" saying how to wire up all the free functions from +/// the wasm module into the output expected JS module. All our functions here +/// currently take integer parameters and require a JS wrapper, but ideally +/// we'd change them one day to taking/receiving `anyref` which then use some +/// sort of webidl import to customize behavior or something like that. In any +/// case this doesn't feel quite right in terms of priviledge separation, so +/// we'll want to work on this. For now though it works. +#[derive(Debug)] +pub enum AuxExportKind { + /// A free function that's just listed on the exported module + Function(String), + + /// A function that's used to create an instane of a class. The function + /// actually return just an integer which is put on an JS object currently. + Constructor(String), + + /// This function is intended to be a getter for a field on a class. The + /// first argument is the internal pointer and the returned value is + /// expected to be the field. + Getter { class: String, field: String }, + + /// This function is intended to be a setter for a field on a class. The + /// first argument is the internal pointer and the second argument is + /// expected to be the field's new value. + Setter { class: String, field: String }, + + /// This is a free function (ish) but scoped inside of a class name. + StaticFunction { class: String, name: String }, + + /// This is a member function of a class where the first parameter is the + /// implicit integer stored in the class instance. + Method { + class: String, + name: String, + /// Whether or not this is calling a by-value method in Rust and should + /// clear the internal pointer in JS automatically. + consumed: bool, + }, +} + +#[derive(Debug)] +pub struct AuxEnum { + /// The name of this enum + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, + /// A list of variants with their name and value + pub variants: Vec<(String, u32)>, +} + +#[derive(Debug)] +pub struct AuxStruct { + /// The name of this struct + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, +} + +/// All possible types of imports that can be imported by a wasm module. +/// +/// This `enum` is intended to map out what an imported value is. For example +/// this contains a ton of shims and various ways you can call a function. The +/// base variant here is `Value` which simply means "hook this up to the import" +/// and the signatures will match up. +/// +/// Note that this is *not* the same as the webidl bindings section. This is +/// intended to be coupled with that to map out what actually gets hooked up to +/// an import in the wasm module. The two work in tandem. +/// +/// Some of these items here are native to JS (like `Value`, indexing +/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` +/// or `Instanceof`). +#[derive(Debug)] +pub enum AuxImport { + /// This import is expected to simply be whatever is the value that's + /// imported + Value(AuxValue), + + /// This import is expected to be a function that takes an `anyref` and + /// returns a `bool`. It's expected that it tests if the argument is an + /// instance of (using `instanceof`) the name specified. + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + Instanceof(JsImport), + + /// This import is expected to be a shim that returns the JS value named by + /// `JsImport`. + Static(JsImport), + + /// This import is intended to manufacture a JS closure with the given + /// signature and then return that back to Rust. + Closure(Closure), + + /// This import is expected to be a shim that simply calls the `foo` method + /// on the first object, passing along all other parameters and returning + /// the resulting value. + StructuralMethod(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the first argument as an object. + /// + /// e.g. `function(x) { return x.foo; }` + StructuralGetter(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the specified class + /// + /// e.g. `function() { return TheClass.foo; }` + StructuralClassGetter(JsImport, String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the first argument to the second argument. + /// + /// e.g. `function(x, y) { x.foo = y; }` + StructuralSetter(String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the specified class to the first argument of the function. + /// + /// e.g. `function(x) { TheClass.foo = x; }` + StructuralClassSetter(JsImport, String), + + /// This import is expected to be a shim that is an indexing getter of the + /// JS class here, where the first argument of the function is the field to + /// look up. The return value is the value of the field. + /// + /// e.g. `function(x) { return TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing getter of the + /// first argument interpreted as an object where the field to look up is + /// the second argument. + /// + /// e.g. `function(x, y) { return x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfObject, + + /// This import is expected to be a shim that is an indexing setter of the + /// JS class here, where the first argument of the function is the field to + /// set and the second is the value to set it to. + /// + /// e.g. `function(x, y) { TheClass[x] = y; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing setter of the + /// first argument interpreted as an object where the next two fields are + /// the field to set and the value to set it to. + /// + /// e.g. `function(x, y, z) { x[y] = z; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfObject, + + /// This import is expected to be a shim that is an indexing deleter of the + /// JS class here, where the first argument of the function is the field to + /// delete. + /// + /// e.g. `function(x) { delete TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing deleter of the + /// first argument interpreted as an object where the second argument is + /// the field to delete. + /// + /// e.g. `function(x, y) { delete x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfObject, + + /// This import is a generated shim which will wrap the provided pointer in + /// a JS object corresponding to the Class name given here. The class name + /// is one that is exported from the Rust/wasm. + /// + /// TODO: sort of like the export map below we should ideally create the + /// `anyref` from within Rust itself and then return it directly rather than + /// requiring an intrinsic here to do so. + WrapInExportedClass(String), + + /// This is an intrinsic function expected to be implemented with a JS glue + /// shim. Each intrinsic has its own expected signature and implementation. + Intrinsic(Intrinsic), +} + +/// Values that can be imported verbatim to hook up to an import. +#[derive(Debug)] +pub enum AuxValue { + /// A bare JS value, no transformations, just put it in the slot. + Bare(JsImport), + + /// A getter function for the class listed for the field, acquired using + /// `getOwnPropertyDescriptor`. + Getter(JsImport, String), + + /// Like `Getter`, but accesses a field of a class instead of an instance + /// of the class. + ClassGetter(JsImport, String), + + /// Like `Getter`, except the `set` property. + Setter(JsImport, String), + + /// Like `Setter`, but for class fields instead of instance fields. + ClassSetter(JsImport, String), +} + +/// What can actually be imported and typically a value in each of the variants +/// above of `AuxImport` +/// +/// A `JsImport` is intended to indicate what exactly is being imported for a +/// particular operation. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct JsImport { + /// The base of whatever is being imported, either from a module, the global + /// namespace, or similar. + pub name: JsImportName, + /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` + /// above. + pub fields: Vec, +} + +/// Return value of `determine_import` which is where we look at an imported +/// function AST and figure out where it's actually being imported from +/// (performing some validation checks and whatnot). +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum JsImportName { + /// An item is imported from the global scope. The `name` is what's + /// imported. + Global { name: String }, + /// Same as `Global`, except the `name` is imported via an ESM import from + /// the specified `module` path. + Module { module: String, name: String }, + /// Same as `Module`, except we're importing from a local module defined in + /// a local JS snippet. + LocalModule { module: String, name: String }, + /// Same as `Module`, except we're importing from an `inline_js` attribute + InlineJs { + unique_crate_identifier: String, + snippet_idx_in_crate: usize, + name: String, + }, + /// A global import which may have a number of vendor prefixes associated + /// with it, like `webkitAudioPrefix`. The `name` is the name to test + /// whether it's prefixed. + VendorPrefixed { name: String, prefixes: Vec }, +} + +struct Context<'a> { + start_found: bool, + module: &'a mut Module, + bindings: WebidlCustomSection, + aux: WasmBindgenAux, + function_exports: HashMap, + function_imports: HashMap, + vendor_prefixes: HashMap>, + unique_crate_identifier: &'a str, + descriptors: HashMap, +} + +pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindgenAuxId), Error> { + let mut storage = Vec::new(); + let programs = extract_programs(module, &mut storage)?; + + let mut cx = Context { + bindings: Default::default(), + aux: Default::default(), + function_exports: Default::default(), + function_imports: Default::default(), + vendor_prefixes: Default::default(), + descriptors: Default::default(), + unique_crate_identifier: "", + module, + start_found: false, + }; + cx.init(); + + for program in programs { + cx.program(program)?; + } + + cx.verify()?; + + let bindings = cx.module.customs.add(cx.bindings); + let aux = cx.module.customs.add(cx.aux); + Ok((bindings, aux)) +} + +impl<'a> Context<'a> { + fn init(&mut self) { + // Make a map from string name to ids of all exports + for export in self.module.exports.iter() { + if let walrus::ExportItem::Function(f) = export.item { + self.function_exports + .insert(export.name.clone(), (export.id(), f)); + } + } + + // Make a map from string name to ids of all imports from our + // placeholder module name which we'll want to be sure that we've got a + // location listed of what to import there for each item. + for import in self.module.imports.iter() { + if import.module != PLACEHOLDER_MODULE { + continue; + } + if let walrus::ImportKind::Function(f) = import.kind { + self.function_imports + .insert(import.name.clone(), (import.id(), f)); + if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) { + self.bindings + .imports + .insert(import.id(), ImportBinding::Function(intrinsic.binding())); + self.aux + .import_map + .insert(import.id(), AuxImport::Intrinsic(intrinsic)); + } + } + } + + let mut descriptor_section = None; + for (id, section) in self.module.customs.iter_mut() { + let custom = match section + .as_any_mut() + .downcast_mut::() + { + Some(s) => s, + None => continue, + }; + descriptor_section = Some(id); + + // Store all the executed descriptors in our own field so we have + // access to them while processing programs. + self.descriptors.extend(custom.descriptors.drain()); + + // Register all the injected closure imports as that they're expected + // to manufacture a particular type of closure. + for (id, descriptor) in custom.closure_imports.drain() { + self.aux + .import_map + .insert(id, AuxImport::Closure(descriptor)); + let binding = Function { + shim_idx: 0, + arguments: vec![Descriptor::I32; 3], + ret: Descriptor::Anyref, + }; + self.bindings + .imports + .insert(id, ImportBinding::Function(binding)); + } + } + if let Some(id) = descriptor_section { + self.module.customs.delete(id); + } + } + + fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { + self.unique_crate_identifier = program.unique_crate_identifier; + let decode::Program { + exports, + enums, + imports, + structs, + typescript_custom_sections, + local_modules, + inline_js, + unique_crate_identifier, + package_json, + } = program; + + for module in local_modules { + // All local modules we find should be unique, but the same module + // may have showed up in a few different blocks. If that's the case + // all the same identifiers should have the same contents. + if let Some(prev) = self + .aux + .local_modules + .insert(module.identifier.to_string(), module.contents.to_string()) + { + assert_eq!(prev, module.contents); + } + } + if let Some(s) = package_json { + self.aux.package_jsons.insert(s.into()); + } + for export in exports { + self.export(export)?; + } + + // Register vendor prefixes for all types before we walk over all the + // imports to ensure that if a vendor prefix is listed somewhere it'll + // apply to all the imports. + for import in imports.iter() { + if let decode::ImportKind::Type(ty) = &import.kind { + if ty.vendor_prefixes.len() == 0 { + continue; + } + self.vendor_prefixes + .entry(ty.name.to_string()) + .or_insert(Vec::new()) + .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); + } + } + for import in imports { + self.import(import)?; + } + + for enum_ in enums { + self.enum_(enum_)?; + } + for struct_ in structs { + self.struct_(struct_)?; + } + for section in typescript_custom_sections { + self.aux.extra_typescript.push_str(section); + self.aux.extra_typescript.push_str("\n\n"); + } + self.aux + .snippets + .entry(unique_crate_identifier.to_string()) + .or_insert(Vec::new()) + .extend(inline_js.iter().map(|s| s.to_string())); + Ok(()) + } + + fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { + let wasm_name = match &export.class { + Some(class) => struct_function_export_name(class, export.function.name), + None => export.function.name.to_string(), + }; + let descriptor = match self.descriptors.remove(&wasm_name) { + None => return Ok(()), + Some(d) => d.unwrap_function(), + }; + let (export_id, id) = self.function_exports[&wasm_name]; + if export.start { + self.add_start_function(id)?; + } + + let kind = match export.class { + Some(class) => { + let class = class.to_string(); + match export.method_kind { + decode::MethodKind::Constructor => AuxExportKind::Constructor(class), + decode::MethodKind::Operation(op) => match op.kind { + decode::OperationKind::Getter(f) => AuxExportKind::Getter { + class, + field: f.to_string(), + }, + decode::OperationKind::Setter(f) => AuxExportKind::Setter { + class, + field: f.to_string(), + }, + _ if op.is_static => AuxExportKind::StaticFunction { + class, + name: export.function.name.to_string(), + }, + _ => AuxExportKind::Method { + class, + name: export.function.name.to_string(), + consumed: export.consumed, + }, + }, + } + } + None => AuxExportKind::Function(export.function.name.to_string()), + }; + + self.aux.export_map.insert( + export_id, + AuxExport { + debug_name: wasm_name, + comments: concatenate_comments(&export.comments), + arg_names: Some(export.function.arg_names), + kind, + }, + ); + self.bindings.exports.insert(export_id, descriptor); + Ok(()) + } + + fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { + if self.start_found { + bail!("cannot specify two `start` functions"); + } + self.start_found = true; + + let prev_start = match self.module.start { + Some(f) => f, + None => { + self.module.start = Some(id); + return Ok(()); + } + }; + + // Note that we call the previous start function, if any, first. This is + // because the start function currently only shows up when it's injected + // through thread/anyref transforms. These injected start functions need + // to happen before user code, so we always schedule them first. + let mut builder = walrus::FunctionBuilder::new(); + let call1 = builder.call(prev_start, Box::new([])); + let call2 = builder.call(id, Box::new([])); + let ty = self.module.funcs.get(id).ty(); + let new_start = builder.finish(ty, Vec::new(), vec![call1, call2], self.module); + self.module.start = Some(new_start); + Ok(()) + } + + fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { + match &import.kind { + decode::ImportKind::Function(f) => self.import_function(&import, f), + decode::ImportKind::Static(s) => self.import_static(&import, s), + decode::ImportKind::Type(t) => self.import_type(&import, t), + decode::ImportKind::Enum(_) => Ok(()), + } + } + + fn import_function( + &mut self, + import: &decode::Import<'_>, + function: &decode::ImportFunction<'_>, + ) -> Result<(), Error> { + let decode::ImportFunction { + shim, + catch, + variadic, + method, + structural, + function, + } = function; + let (import_id, _id) = match self.function_imports.get(*shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + let descriptor = match self.descriptors.remove(*shim) { + None => return Ok(()), + Some(d) => d.unwrap_function(), + }; + + // Record this for later as it affects JS binding generation, but note + // that this doesn't affect the WebIDL interface at all. + if *variadic { + self.aux.imports_with_variadic.insert(import_id); + } + if *catch { + self.aux.imports_with_catch.insert(import_id); + } + + // Perform two functions here. First we're saving off our WebIDL + // bindings signature, indicating what we think our import is going to + // be. Next we're saving off other metadata indicating where this item + // is going to be imported from. The `import_map` table will record, for + // each import, what is getting hooked up to that slot of the import + // table to the WebAssembly instance. + let import = match method { + Some(data) => { + let class = self.determine_import(import, &data.class)?; + match &data.kind { + // NB: `structural` is ignored for constructors since the + // js type isn't expected to change anyway. + decode::MethodKind::Constructor => { + self.bindings + .imports + .insert(import_id, ImportBinding::Constructor(descriptor)); + AuxImport::Value(AuxValue::Bare(class)) + } + decode::MethodKind::Operation(op) => { + let (import, method) = + self.determine_import_op(class, function, *structural, op)?; + let binding = if method { + ImportBinding::Method(descriptor) + } else { + ImportBinding::Function(descriptor) + }; + self.bindings.imports.insert(import_id, binding); + import + } + } + } + + // NB: `structural` is ignored for free functions since it's + // expected that the binding isn't changing anyway. + None => { + self.bindings + .imports + .insert(import_id, ImportBinding::Function(descriptor)); + let name = self.determine_import(import, function.name)?; + AuxImport::Value(AuxValue::Bare(name)) + } + }; + + self.aux.import_map.insert(import_id, import); + Ok(()) + } + + /// The `bool` returned indicates whether the imported value should be + /// invoked as a method (first arg is implicitly `this`) or if the imported + /// value is a simple function-like shim + fn determine_import_op( + &mut self, + mut class: JsImport, + function: &decode::Function<'_>, + structural: bool, + op: &decode::Operation<'_>, + ) -> Result<(AuxImport, bool), Error> { + match op.kind { + decode::OperationKind::Regular => { + if op.is_static { + class.fields.push(function.name.to_string()); + Ok((AuxImport::Value(AuxValue::Bare(class)), false)) + } else if structural { + Ok(( + AuxImport::StructuralMethod(function.name.to_string()), + false, + )) + } else { + class.fields.push("prototype".to_string()); + class.fields.push(function.name.to_string()); + Ok((AuxImport::Value(AuxValue::Bare(class)), true)) + } + } + + decode::OperationKind::Getter(field) => { + if structural { + if op.is_static { + Ok(( + AuxImport::StructuralClassGetter(class, field.to_string()), + false, + )) + } else { + Ok((AuxImport::StructuralGetter(field.to_string()), false)) + } + } else { + let val = if op.is_static { + AuxValue::ClassGetter(class, field.to_string()) + } else { + AuxValue::Getter(class, field.to_string()) + }; + Ok((AuxImport::Value(val), true)) + } + } + + decode::OperationKind::Setter(field) => { + if structural { + if op.is_static { + Ok(( + AuxImport::StructuralClassSetter(class, field.to_string()), + false, + )) + } else { + Ok((AuxImport::StructuralSetter(field.to_string()), false)) + } + } else { + let val = if op.is_static { + AuxValue::ClassSetter(class, field.to_string()) + } else { + AuxValue::Setter(class, field.to_string()) + }; + Ok((AuxImport::Value(val), true)) + } + } + + decode::OperationKind::IndexingGetter => { + if !structural { + bail!("indexing getters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingGetterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingGetterOfObject, false)) + } + } + + decode::OperationKind::IndexingSetter => { + if !structural { + bail!("indexing setters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingSetterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingSetterOfObject, false)) + } + } + + decode::OperationKind::IndexingDeleter => { + if !structural { + bail!("indexing deleters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingDeleterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingDeleterOfObject, false)) + } + } + } + } + + fn import_static( + &mut self, + import: &decode::Import<'_>, + static_: &decode::ImportStatic<'_>, + ) -> Result<(), Error> { + let (import_id, _id) = match self.function_imports.get(static_.shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + + // Register the signature of this imported shim + self.bindings.imports.insert( + import_id, + ImportBinding::Function(Function { + arguments: Vec::new(), + shim_idx: 0, + ret: Descriptor::Anyref, + }), + ); + + // And then save off that this function is is an instanceof shim for an + // imported item. + let import = self.determine_import(import, &static_.name)?; + self.aux + .import_map + .insert(import_id, AuxImport::Static(import)); + Ok(()) + } + + fn import_type( + &mut self, + import: &decode::Import<'_>, + type_: &decode::ImportType<'_>, + ) -> Result<(), Error> { + let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + + // Register the signature of this imported shim + self.bindings.imports.insert( + import_id, + ImportBinding::Function(Function { + arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], + shim_idx: 0, + ret: Descriptor::I32, + }), + ); + + // And then save off that this function is is an instanceof shim for an + // imported item. + let import = self.determine_import(import, &type_.name)?; + self.aux + .import_map + .insert(import_id, AuxImport::Instanceof(import)); + Ok(()) + } + + fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { + let aux = AuxEnum { + name: enum_.name.to_string(), + comments: concatenate_comments(&enum_.comments), + variants: enum_ + .variants + .iter() + .map(|v| (v.name.to_string(), v.value)) + .collect(), + }; + self.aux.enums.push(aux); + Ok(()) + } + + fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { + for field in struct_.fields { + let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); + let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); + let descriptor = match self.descriptors.remove(&getter) { + None => continue, + Some(d) => d, + }; + + // Register a webidl transformation for the getter + let (getter_id, _) = self.function_exports[&getter]; + let getter_descriptor = Function { + arguments: Vec::new(), + shim_idx: 0, + ret: descriptor.clone(), + }; + self.bindings.exports.insert(getter_id, getter_descriptor); + self.aux.export_map.insert( + getter_id, + AuxExport { + debug_name: format!("getter for `{}::{}`", struct_.name, field.name), + arg_names: None, + comments: concatenate_comments(&field.comments), + kind: AuxExportKind::Getter { + class: struct_.name.to_string(), + field: field.name.to_string(), + }, + }, + ); + + // If present, register information for the setter as well. + if field.readonly { + continue; + } + + let (setter_id, _) = self.function_exports[&setter]; + let setter_descriptor = Function { + arguments: vec![descriptor], + shim_idx: 0, + ret: Descriptor::Unit, + }; + self.bindings.exports.insert(setter_id, setter_descriptor); + self.aux.export_map.insert( + setter_id, + AuxExport { + debug_name: format!("setter for `{}::{}`", struct_.name, field.name), + arg_names: None, + comments: concatenate_comments(&field.comments), + kind: AuxExportKind::Setter { + class: struct_.name.to_string(), + field: field.name.to_string(), + }, + }, + ); + } + let aux = AuxStruct { + name: struct_.name.to_string(), + comments: concatenate_comments(&struct_.comments), + }; + self.aux.structs.push(aux); + + let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); + if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { + self.aux.import_map.insert( + *import_id, + AuxImport::WrapInExportedClass(struct_.name.to_string()), + ); + let binding = Function { + shim_idx: 0, + arguments: vec![Descriptor::I32], + ret: Descriptor::Anyref, + }; + self.bindings + .imports + .insert(*import_id, ImportBinding::Function(binding)); + } + + Ok(()) + } + + fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { + let is_local_snippet = match import.module { + decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), + decode::ImportModule::RawNamed(_) => false, + decode::ImportModule::Inline(_) => true, + decode::ImportModule::None => false, + }; + + // Similar to `--target no-modules`, only allow vendor prefixes + // basically for web apis, shouldn't be necessary for things like npm + // packages or other imported items. + let vendor_prefixes = self.vendor_prefixes.get(item); + if let Some(vendor_prefixes) = vendor_prefixes { + assert!(vendor_prefixes.len() > 0); + + if is_local_snippet { + bail!( + "local JS snippets do not support vendor prefixes for \ + the import of `{}` with a polyfill of `{}`", + item, + &vendor_prefixes[0] + ); + } + if let decode::ImportModule::Named(module) = &import.module { + bail!( + "import of `{}` from `{}` has a polyfill of `{}` listed, but + vendor prefixes aren't supported when importing from modules", + item, + module, + &vendor_prefixes[0], + ); + } + if let Some(ns) = &import.js_namespace { + bail!( + "import of `{}` through js namespace `{}` isn't supported \ + right now when it lists a polyfill", + item, + ns + ); + } + return Ok(JsImport { + name: JsImportName::VendorPrefixed { + name: item.to_string(), + prefixes: vendor_prefixes.clone(), + }, + fields: Vec::new(), + }); + } + + let (name, fields) = match import.js_namespace { + Some(ns) => (ns, vec![item.to_string()]), + None => (item, Vec::new()), + }; + + let name = match import.module { + decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { + module: module.to_string(), + name: name.to_string(), + }, + decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { + JsImportName::Module { + module: module.to_string(), + name: name.to_string(), + } + } + decode::ImportModule::Inline(idx) => { + let offset = self + .aux + .snippets + .get(self.unique_crate_identifier) + .map(|s| s.len()) + .unwrap_or(0); + JsImportName::InlineJs { + unique_crate_identifier: self.unique_crate_identifier.to_string(), + snippet_idx_in_crate: idx as usize + offset, + name: name.to_string(), + } + } + decode::ImportModule::None => JsImportName::Global { + name: name.to_string(), + }, + }; + Ok(JsImport { name, fields }) + } + + /// Perform a small verification pass over the module to perform some + /// internal sanity checks. + fn verify(&self) -> Result<(), Error> { + let mut imports_counted = 0; + for import in self.module.imports.iter() { + if import.module != PLACEHOLDER_MODULE { + continue; + } + match import.kind { + walrus::ImportKind::Function(_) => {} + _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), + } + + // Ensure that everything imported from the `__wbindgen_placeholder__` + // module has a location listed as to where it's expected to be + // imported from. + if !self.aux.import_map.contains_key(&import.id()) { + bail!( + "import of `{}` doesn't have an import map item listed", + import.name + ); + } + + // Also make sure there's a binding listed for it. + if !self.bindings.imports.contains_key(&import.id()) { + bail!("import of `{}` doesn't have a binding listed", import.name); + } + imports_counted += 1; + } + + // Make sure there's no extraneous bindings that weren't actually + // imported in the module. + if self.aux.import_map.len() != imports_counted { + bail!("import map is larger than the number of imports"); + } + if self.bindings.imports.len() != imports_counted { + bail!("import binding map is larger than the number of imports"); + } + + // Make sure the export map and export bindings map contain the same + // number of entries. + for id in self.bindings.exports.keys() { + if !self.aux.export_map.contains_key(id) { + bail!("bindings map has an entry that the export map does not"); + } + } + + if self.bindings.exports.len() != self.aux.export_map.len() { + bail!("export map and export bindings map have different sizes"); + } + + Ok(()) + } +} + +impl walrus::CustomSection for WebidlCustomSection { + fn name(&self) -> &str { + "webidl custom section" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} + +impl walrus::CustomSection for WasmBindgenAux { + fn name(&self) -> &str { + "wasm-bindgen custom section" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} + +fn extract_programs<'a>( + module: &mut Module, + program_storage: &'a mut Vec>, +) -> Result>, Error> { + let my_version = wasm_bindgen_shared::version(); + assert!(program_storage.is_empty()); + + while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") { + log::debug!( + "custom section '{}' looks like a wasm bindgen section", + raw.name + ); + program_storage.push(raw.data); + } + + let mut ret = Vec::new(); + for program in program_storage.iter() { + let mut payload = &program[..]; + while let Some(data) = get_remaining(&mut payload) { + // Historical versions of wasm-bindgen have used JSON as the custom + // data section format. Newer versions, however, are using a custom + // serialization protocol that looks much more like the wasm spec. + // + // We, however, want a sanity check to ensure that if we're running + // against the wrong wasm-bindgen we get a nicer error than an + // internal decode error. To that end we continue to verify a tiny + // bit of json at the beginning of each blob before moving to the + // next blob. This should keep us compatible with older wasm-bindgen + // instances as well as forward-compatible for now. + // + // Note, though, that as `wasm-pack` picks up steam it's hoped we + // can just delete this entirely. The `wasm-pack` project already + // manages versions for us, so we in theory should need this check + // less and less over time. + if let Some(their_version) = verify_schema_matches(data)? { + bail!( + " + +it looks like the Rust project used to create this wasm file was linked against +a different version of wasm-bindgen than this binary: + + rust wasm file: {} + this binary: {} + +Currently the bindgen format is unstable enough that these two version must +exactly match, so it's required that these two version are kept in sync by +either updating the wasm-bindgen dependency or this binary. You should be able +to update the wasm-bindgen dependency with: + + cargo update -p wasm-bindgen + +or you can update the binary with + + cargo install -f wasm-bindgen-cli + +if this warning fails to go away though and you're not sure what to do feel free +to open an issue at https://github.com/rustwasm/wasm-bindgen/issues! +", + their_version, + my_version, + ); + } + let next = get_remaining(&mut payload).unwrap(); + log::debug!("found a program of length {}", next.len()); + ret.push(::decode_all(next)); + } + } + Ok(ret) +} + +fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> { + if data.len() == 0 { + return None; + } + let len = ((data[0] as usize) << 0) + | ((data[1] as usize) << 8) + | ((data[2] as usize) << 16) + | ((data[3] as usize) << 24); + let (a, b) = data[4..].split_at(len); + *data = b; + Some(a) +} + +fn verify_schema_matches<'a>(data: &'a [u8]) -> Result, Error> { + macro_rules! bad { + () => { + bail!("failed to decode what looked like wasm-bindgen data") + }; + } + let data = match str::from_utf8(data) { + Ok(s) => s, + Err(_) => bad!(), + }; + log::debug!("found version specifier {}", data); + if !data.starts_with("{") || !data.ends_with("}") { + bad!() + } + let needle = "\"schema_version\":\""; + let rest = match data.find(needle) { + Some(i) => &data[i + needle.len()..], + None => bad!(), + }; + let their_schema_version = match rest.find("\"") { + Some(i) => &rest[..i], + None => bad!(), + }; + if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION { + return Ok(None); + } + let needle = "\"version\":\""; + let rest = match data.find(needle) { + Some(i) => &data[i + needle.len()..], + None => bad!(), + }; + let their_version = match rest.find("\"") { + Some(i) => &rest[..i], + None => bad!(), + }; + Ok(Some(their_version)) +} + +fn concatenate_comments(comments: &[&str]) -> String { + comments + .iter() + .map(|s| s.trim_matches('"')) + .collect::>() + .join("\n") +} diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 545137a6..bf7dd76b 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -71,7 +71,7 @@ fn rmain() -> Result<(), Error> { // that any exported function with the prefix `__wbg_test` is a test we need // to execute. let wasm = fs::read(&wasm_file_to_test).context("failed to read wasm file")?; - let wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?; + let mut wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?; let mut tests = Vec::new(); for export in wasm.exports.iter() { @@ -94,11 +94,8 @@ fn rmain() -> Result<(), Error> { // `wasm_bindgen_test_configure` macro, which emits a custom section for us // to read later on. let mut node = true; - for (_id, custom) in wasm.customs.iter() { - if custom.name() != "__wasm_bindgen_test_unstable" { - continue; - } - node = !custom.data().contains(&0x01); + if let Some(section) = wasm.customs.remove_raw("__wasm_bindgen_test_unstable") { + node = !section.data.contains(&0x01); } let headless = env::var("NO_HEADLESS").is_err(); let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err(); diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs index de6dda43..5543942f 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs @@ -22,10 +22,10 @@ pub fn execute( const og = console[method]; const on_method = `on_console_${{method}}`; console[method] = function (...args) {{ + og.apply(this, args); if (handlers[on_method]) {{ handlers[on_method](args); }} - og.apply(this, args); }}; }}; diff --git a/crates/wasm-interpreter/src/lib.rs b/crates/wasm-interpreter/src/lib.rs index 723a93cd..9b96364d 100644 --- a/crates/wasm-interpreter/src/lib.rs +++ b/crates/wasm-interpreter/src/lib.rs @@ -127,12 +127,7 @@ impl Interpreter { /// /// Returns `Some` if `func` was found in the `module` and `None` if it was /// not found in the `module`. - pub fn interpret_descriptor(&mut self, func: &str, module: &Module) -> Option<&[u32]> { - let id = *self.name_map.get(func)?; - self.interpret_descriptor_id(id, module) - } - - fn interpret_descriptor_id(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> { + pub fn interpret_descriptor(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> { self.descriptor.truncate(0); // We should have a blank wasm and LLVM stack at both the start and end @@ -212,7 +207,7 @@ impl Interpreter { entry_removal_list.insert(descriptor_table_idx); // And now execute the descriptor! - self.interpret_descriptor_id(descriptor_id, module) + self.interpret_descriptor(descriptor_id, module) } /// Returns the function id of the `__wbindgen_describe_closure` diff --git a/src/lib.rs b/src/lib.rs index 30279ff0..19cfadba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,9 +220,9 @@ impl JsValue { T: for<'a> serde::de::Deserialize<'a>, { unsafe { - let mut ptr = ptr::null_mut(); - let len = __wbindgen_json_serialize(self.idx, &mut ptr); - let s = Vec::from_raw_parts(ptr, len, len); + let mut ret = [0usize; 2]; + __wbindgen_json_serialize(&mut ret, self.idx); + let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]); let s = String::from_utf8_unchecked(s); serde_json::from_str(&s) } @@ -333,14 +333,10 @@ impl JsValue { #[cfg(feature = "std")] fn as_debug_string(&self) -> String { unsafe { - let mut len = 0; - let ptr = __wbindgen_debug_string(self.idx, &mut len); - if ptr.is_null() { - unreachable!("`__wbindgen_debug_string` must return a valid string") - } else { - let data = Vec::from_raw_parts(ptr, len, len); - String::from_utf8_unchecked(data) - } + let mut ret = [0; 2]; + __wbindgen_debug_string(&mut ret, self.idx); + let data = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]); + String::from_utf8_unchecked(data) } } } @@ -507,7 +503,7 @@ externs! { fn __wbindgen_boolean_get(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; - fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8; + fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> (); fn __wbindgen_throw(a: *const u8, b: usize) -> !; fn __wbindgen_rethrow(a: u32) -> !; @@ -519,7 +515,7 @@ externs! { fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32; - fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize; + fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> (); fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32; fn __wbindgen_memory() -> u32; diff --git a/tests/wasm/closures.js b/tests/wasm/closures.js index c09c6cb0..f14dcbfb 100644 --- a/tests/wasm/closures.js +++ b/tests/wasm/closures.js @@ -126,3 +126,13 @@ exports.pass_reference_first_arg_twice = (a, b, c) => { exports.call_destroyed = f => { assert.throws(f, /invoked recursively or destroyed/); }; + +let FORGOTTEN_CLOSURE = null; + +exports.js_store_forgotten_closure = f => { + FORGOTTEN_CLOSURE = f; +}; + +exports.js_call_forgotten_closure = () => { + FORGOTTEN_CLOSURE(); +}; diff --git a/tests/wasm/closures.rs b/tests/wasm/closures.rs index 70a9f991..925ca6b1 100755 --- a/tests/wasm/closures.rs +++ b/tests/wasm/closures.rs @@ -108,6 +108,9 @@ extern "C" { c: &mut FnMut(&RefFirstArgument), ); fn call_destroyed(a: &JsValue); + + fn js_store_forgotten_closure(closure: &Closure); + fn js_call_forgotten_closure(); } #[wasm_bindgen_test] @@ -573,3 +576,11 @@ fn call_destroyed_doesnt_segfault() { drop(a); call_destroyed(&b); } + +#[wasm_bindgen_test] +fn forget_works() { + let a = Closure::wrap(Box::new(|| {}) as Box); + js_store_forgotten_closure(&a); + a.forget(); + js_call_forgotten_closure(); +} diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 080e8f1c..496f50cd 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -158,7 +158,7 @@ fn switch_methods() { assert!(!switch_methods_called()); SwitchMethods::new().b(); - assert!(switch_methods_called()); + assert!(!switch_methods_called()); } #[wasm_bindgen_test] diff --git a/tests/wasm/simple.rs b/tests/wasm/simple.rs index 814cbcc0..568f91de 100644 --- a/tests/wasm/simple.rs +++ b/tests/wasm/simple.rs @@ -213,3 +213,12 @@ fn string_roundtrip() { pub fn do_string_roundtrip(s: String) -> String { s } + +#[wasm_bindgen_test] +fn anyref_heap_live_count() { + let x = wasm_bindgen::anyref_heap_live_count(); + let y = JsValue::null().clone(); + assert!(wasm_bindgen::anyref_heap_live_count() > x); + drop(y); + assert_eq!(x, wasm_bindgen::anyref_heap_live_count()); +} From 3e28e6ea46119139e44dfdeeee958896b76332c2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 08:47:16 -0700 Subject: [PATCH 02/26] Fix web, no-modules, and bundler output types Make sure the wasm import definition map is hooked up correctly! --- crates/cli-support/src/js/mod.rs | 82 ++++++++++++---------------- crates/cli-support/src/js/rust2js.rs | 4 +- 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f66d6317..0ecca33a 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -309,7 +309,7 @@ impl<'a> Context<'a> { OutputMode::NoModules { global } => { js.push_str("const __exports = {};\n"); js.push_str("let wasm;\n"); - init = self.gen_init(&module_name, needs_manual_start); + init = self.gen_init(needs_manual_start); footer.push_str(&format!( "self.{} = Object.assign(init, __exports);\n", global @@ -347,12 +347,17 @@ impl<'a> Context<'a> { experimental_modules: true, } => { imports.push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); - if needs_manual_start { - footer.push_str("wasm.__wbindgen_start();\n"); - } for (id, js) in self.wasm_import_definitions.iter() { - drop((id, js)); - unimplemented!() + let import = self.module.imports.get_mut(*id); + import.module = format!("./{}.js", module_name); + footer.push_str("\nexport const "); + footer.push_str(&import.name); + footer.push_str(" = "); + footer.push_str(js.trim()); + footer.push_str(";\n"); + } + if needs_manual_start { + footer.push_str("\nwasm.__wbindgen_start();\n"); } } @@ -363,7 +368,7 @@ impl<'a> Context<'a> { OutputMode::Web => { self.imports_post.push_str("const __exports = {};\n"); self.imports_post.push_str("let wasm;\n"); - init = self.gen_init(&module_name, needs_manual_start); + init = self.gen_init(needs_manual_start); footer.push_str("export default init;\n"); } } @@ -488,11 +493,7 @@ impl<'a> Context<'a> { ) } - fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) { - for (id, js) in self.wasm_import_definitions.iter() { - drop((id, js)); - unimplemented!() - } + fn gen_init(&mut self, needs_manual_start: bool) -> (String, String) { let mem = self.module.memories.get(self.memory); let (init_memory1, init_memory2) = if mem.import.is_some() { let mut memory = String::from("new WebAssembly.Memory({"); @@ -519,44 +520,32 @@ impl<'a> Context<'a> { }; let ts = Self::ts_for_init_fn(mem.import.is_some()); - // Generate extra initialization for the `imports` object if necessary - // based on the values in `direct_imports` we find. These functions are - // intended to be imported directly to the wasm module and we need to - // ensure that the modules are actually imported from and inserted into - // the object correctly. - // let mut map = BTreeMap::new(); - // for &(module, name) in self.direct_imports.values() { - // map.entry(module).or_insert(BTreeSet::new()).insert(name); - // } - let imports_init = String::new(); - // for (module, names) in map { - // drop((module, names)); - // panic!() - // // imports_init.push_str("imports['"); - // // imports_init.push_str(module); - // // imports_init.push_str("'] = { "); - // // for (i, name) in names.into_iter().enumerate() { - // // if i != 0 { - // // imports_init.push_str(", "); - // // } - // // let import = Import::Module { - // // module, - // // name, - // // field: None, - // // }; - // // let identifier = self.import_identifier(import); - // // imports_init.push_str(name); - // // imports_init.push_str(": "); - // // imports_init.push_str(&identifier); - // // } - // // imports_init.push_str(" };\n"); - // } + // Initialize the `imports` object for all import definitions that we're + // directed to wire up. + let mut imports_init = String::new(); + let module_name = "wbg"; + if self.wasm_import_definitions.len() > 0 { + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str(" = {};\n"); + } + for (id, js) in self.wasm_import_definitions.iter() { + let import = self.module.imports.get_mut(*id); + import.module = module_name.to_string(); + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str("."); + imports_init.push_str(&import.name); + imports_init.push_str(" = "); + imports_init.push_str(js.trim()); + imports_init.push_str(";\n"); + } let js = format!( "\ function init(module{init_memory_arg}) {{ let result; - const imports = {{ './{module}': __exports }}; + const imports = {{}}; {imports_init} if (module instanceof URL || typeof module === 'string' || module instanceof Request) {{ {init_memory2} @@ -598,7 +587,6 @@ impl<'a> Context<'a> { }} ", init_memory_arg = init_memory_arg, - module = module_name, init_memory1 = init_memory1, init_memory2 = init_memory2, start = if needs_manual_start { @@ -2163,7 +2151,7 @@ impl<'a> Context<'a> { } // errors if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; + return `${val.name}: ${val.message}\\n${val.stack}`; } // TODO we could test for more things here, like `Set`s and `Map`s. return className; diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 738fa763..813fe63a 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -934,8 +934,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> { }} catch (e) {{\n\ let error = (function () {{ try {{ - return e instanceof Error - ? `${{e.message}}\n\nStack:\n${{e.stack}}` + return e instanceof Error \ + ? `${{e.message}}\\n\\nStack:\\n${{e.stack}}` \ : e.toString(); }} catch(_) {{ return \"\"; From e8e84a3f9c9eeca2ced26f22ebf446c67befed54 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 08:50:19 -0700 Subject: [PATCH 03/26] Remove __exports map on the `web` target This is no longe rneeded now that we precisely track what needs to be exported for an imported item, so all the imports are hooked up correctly elsewhere without the need for the `__exports` map. --- crates/cli-support/src/js/mod.rs | 48 ++------------------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 0ecca33a..830fd025 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -142,7 +142,8 @@ impl<'a> Context<'a> { OutputMode::Bundler { .. } | OutputMode::Node { experimental_modules: true, - } => { + } + | OutputMode::Web => { if contents.starts_with("function") { let body = &contents[8..]; if export_name == definition_name { @@ -161,50 +162,6 @@ impl<'a> Context<'a> { format!("export const {} = {};\n", export_name, contents) } } - OutputMode::Web => { - // In web mode there's no need to export the internals of - // wasm-bindgen as we're not using the module itself as the - // import object but rather the `__exports` map we'll be - // initializing below. - let export = if export_name.starts_with("__wbindgen") - || export_name.starts_with("__wbg_") - || export_name.starts_with("__widl_") - { - "" - } else { - "export " - }; - if contents.starts_with("function") { - let body = &contents[8..]; - if export_name == definition_name { - format!( - "{}function {name}{}\n__exports.{name} = {name}", - export, - body, - name = export_name, - ) - } else { - format!( - "{}function {defname}{}\n__exports.{name} = {defname}", - export, - body, - name = export_name, - defname = definition_name, - ) - } - } else if contents.starts_with("class") { - assert_eq!(export_name, definition_name); - format!("{}{}\n", export, contents) - } else { - assert_eq!(export_name, definition_name); - format!( - "{}const {name} = {};\n__exports.{name} = {name};", - export, - contents, - name = export_name - ) - } - } }; self.global(&global); Ok(()) @@ -366,7 +323,6 @@ impl<'a> Context<'a> { // expose the same initialization function as `--target no-modules` // as the default export of the module. OutputMode::Web => { - self.imports_post.push_str("const __exports = {};\n"); self.imports_post.push_str("let wasm;\n"); init = self.gen_init(needs_manual_start); footer.push_str("export default init;\n"); From 22b26db9111a5abe969a8b20b52fa73501e25994 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 08:57:28 -0700 Subject: [PATCH 04/26] Use `delete_typed` to improve some ergonomics --- crates/cli-support/src/webidl.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/cli-support/src/webidl.rs b/crates/cli-support/src/webidl.rs index c8166a05..0409e39a 100644 --- a/crates/cli-support/src/webidl.rs +++ b/crates/cli-support/src/webidl.rs @@ -475,24 +475,22 @@ impl<'a> Context<'a> { } } - let mut descriptor_section = None; - for (id, section) in self.module.customs.iter_mut() { - let custom = match section - .as_any_mut() - .downcast_mut::() - { - Some(s) => s, - None => continue, - }; - descriptor_section = Some(id); - + if let Some(custom) = self + .module + .customs + .delete_typed::() + { + let WasmBindgenDescriptorsSection { + descriptors, + closure_imports, + } = *custom; // Store all the executed descriptors in our own field so we have // access to them while processing programs. - self.descriptors.extend(custom.descriptors.drain()); + self.descriptors.extend(descriptors); // Register all the injected closure imports as that they're expected // to manufacture a particular type of closure. - for (id, descriptor) in custom.closure_imports.drain() { + for (id, descriptor) in closure_imports { self.aux .import_map .insert(id, AuxImport::Closure(descriptor)); @@ -506,9 +504,6 @@ impl<'a> Context<'a> { .insert(id, ImportBinding::Function(binding)); } } - if let Some(id) = descriptor_section { - self.module.customs.delete(id); - } } fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { From 346868f78bdd7c51ef433dcf2bdaaac6ec80ca1f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 10:32:14 -0700 Subject: [PATCH 05/26] Fix a failing CLI test --- crates/cli-support/src/js/mod.rs | 3 ++- crates/cli/tests/wasm-bindgen/npm.rs | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 830fd025..0814a9fa 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1982,7 +1982,8 @@ impl<'a> Context<'a> { if !self.config.mode.nodejs() && !self.config.mode.bundler() { bail!( "NPM dependencies have been specified in `{}` but \ - this is only compatible with the `bundler` and `nodejs` targets" + this is only compatible with the `bundler` and `nodejs` targets", + path.display(), ); } diff --git a/crates/cli/tests/wasm-bindgen/npm.rs b/crates/cli/tests/wasm-bindgen/npm.rs index 1572a939..2ad9e66f 100644 --- a/crates/cli/tests/wasm-bindgen/npm.rs +++ b/crates/cli/tests/wasm-bindgen/npm.rs @@ -22,10 +22,15 @@ fn no_modules_rejects_npm() { .file("package.json", "") .wasm_bindgen("--no-modules"); cmd.assert() - .stderr("\ -error: failed to generate bindings for JS import `foo` - caused by: import from `foo` module not allowed with `--target no-modules`; use `nodejs`, `web`, or `bundler` target instead -") + .stderr( + str::is_match( + "\ +error: NPM dependencies have been specified in `.*` but this is only \ +compatible with the `bundler` and `nodejs` targets +", + ) + .unwrap(), + ) .failure(); } From ff0a50e31eff62f8b7bec1df109efd19663aaf8f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 10:34:34 -0700 Subject: [PATCH 06/26] Fix failing interpreter tests --- crates/wasm-interpreter/tests/smoke.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/wasm-interpreter/tests/smoke.rs b/crates/wasm-interpreter/tests/smoke.rs index 8bfbfa8c..37522960 100644 --- a/crates/wasm-interpreter/tests/smoke.rs +++ b/crates/wasm-interpreter/tests/smoke.rs @@ -17,7 +17,17 @@ fn interpret(wat: &str, name: &str, result: Option<&[u32]>) { assert!(status.success()); let module = walrus::Module::from_file(output.path()).unwrap(); let mut i = Interpreter::new(&module).unwrap(); - assert_eq!(i.interpret_descriptor(name, &module), result); + let id = module + .exports + .iter() + .filter(|e| e.name == name) + .filter_map(|e| match e.item { + walrus::ExportItem::Function(f) => Some(f), + _ => None, + }) + .next() + .unwrap(); + assert_eq!(i.interpret_descriptor(id, &module), result); } #[test] @@ -30,7 +40,6 @@ fn smoke() { ) "#; interpret(wat, "foo", Some(&[])); - interpret(wat, "bar", None); let wat = r#" (module From cba1e70077165f5bc11fd9fbb26a5291dc27b276 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 10:56:59 -0700 Subject: [PATCH 07/26] Fix TypeScript output for fields --- crates/cli-support/src/js/js2rust.rs | 2 +- crates/cli-support/src/js/mod.rs | 47 ++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index d76cfbd3..0bc199d7 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -51,7 +51,7 @@ pub struct Js2Rust<'a, 'b: 'a> { /// Typescript expression representing the type of the return value of this /// function. - ret_ty: String, + pub ret_ty: String, /// Expression used to generate the return value. The string "RET" in this /// expression is replaced with the actual wasm invocation eventually. diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 0814a9fa..c668dd34 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -58,6 +58,8 @@ pub struct ExportedClass { typescript: String, has_constructor: bool, wrap_needed: bool, + /// Map from field name to type as a string plus whether it has a setter + typescript_fields: HashMap, } const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; @@ -638,6 +640,19 @@ impl<'a> Context<'a> { ts_dst.push_str(" free(): void;"); dst.push_str(&class.contents); ts_dst.push_str(&class.typescript); + + let mut fields = class.typescript_fields.keys().collect::>(); + fields.sort(); // make sure we have deterministic output + for name in fields { + let (ty, readonly) = &class.typescript_fields[name]; + if *readonly { + ts_dst.push_str("readonly "); + } + ts_dst.push_str(name); + ts_dst.push_str(": "); + ts_dst.push_str(ty); + ts_dst.push_str(";\n"); + } dst.push_str("}\n"); ts_dst.push_str("}\n"); @@ -1913,14 +1928,15 @@ impl<'a> Context<'a> { &format!("wasm.{}", wasm_name), ExportedShim::Named(&wasm_name), ); + let ret_ty = j2r.ret_ty.clone(); let exported = require_class(&mut self.exported_classes, class); let docs = format_doc_comments(&export.comments, Some(raw_docs)); match export.kind { AuxExportKind::Getter { .. } => { - exported.push(&docs, name, "get ", &js, &ts); + exported.push_field(name, &js, Some(&ret_ty), true); } AuxExportKind::Setter { .. } => { - exported.push(&docs, name, "set ", &js, &ts); + exported.push_field(name, &js, None, false); } AuxExportKind::StaticFunction { .. } => { exported.push(&docs, name, "static ", &js, &ts); @@ -1983,7 +1999,7 @@ impl<'a> Context<'a> { bail!( "NPM dependencies have been specified in `{}` but \ this is only compatible with the `bundler` and `nodejs` targets", - path.display(), + path.display(), ); } @@ -2164,6 +2180,31 @@ impl ExportedClass { self.typescript.push_str(ts); self.typescript.push_str("\n"); } + + /// Used for adding a field to a class, mainly to ensure that TypeScript + /// generation is handled specially. + /// + /// Note that the `ts` is optional and it's expected to just be the field + /// type, not the full signature. It's currently only available on getters, + /// but there currently has to always be at least a getter. + fn push_field(&mut self, field: &str, js: &str, ts: Option<&str>, getter: bool) { + if getter { + self.contents.push_str("get "); + } else { + self.contents.push_str("set "); + } + self.contents.push_str(field); + self.contents.push_str(js); + self.contents.push_str("\n"); + let (ty, has_setter) = self + .typescript_fields + .entry(field.to_string()) + .or_insert_with(Default::default); + if let Some(ts) = ts { + *ty = ts.to_string(); + } + *has_setter = *has_setter || !getter; + } } #[test] From 164712c3054c496d5c709a4f1fe4463b967e3856 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 11:00:51 -0700 Subject: [PATCH 08/26] Temporarily ignore intentionally failing test --- tests/wasm/import_class.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 496f50cd..dcdbe745 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -139,6 +139,7 @@ fn rename_type() { } #[wasm_bindgen_test] +#[cfg(ignored)] // TODO: fix this before landing fn switch_methods() { assert!(!switch_methods_called()); SwitchMethods::a(); From edd1469d216d8bf68ba5878c028a73436d445dbc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 May 2019 14:07:55 -0700 Subject: [PATCH 09/26] Include docs in generated JS getters/setters --- crates/cli-support/src/js/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c668dd34..1110142e 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1933,10 +1933,10 @@ impl<'a> Context<'a> { let docs = format_doc_comments(&export.comments, Some(raw_docs)); match export.kind { AuxExportKind::Getter { .. } => { - exported.push_field(name, &js, Some(&ret_ty), true); + exported.push_field(&docs, name, &js, Some(&ret_ty), true); } AuxExportKind::Setter { .. } => { - exported.push_field(name, &js, None, false); + exported.push_field(&docs, name, &js, None, false); } AuxExportKind::StaticFunction { .. } => { exported.push(&docs, name, "static ", &js, &ts); @@ -2187,7 +2187,8 @@ impl ExportedClass { /// Note that the `ts` is optional and it's expected to just be the field /// type, not the full signature. It's currently only available on getters, /// but there currently has to always be at least a getter. - fn push_field(&mut self, field: &str, js: &str, ts: Option<&str>, getter: bool) { + fn push_field(&mut self, docs: &str, field: &str, js: &str, ts: Option<&str>, getter: bool) { + self.contents.push_str(docs); if getter { self.contents.push_str("get "); } else { From b4c395bd6ed4daca8379172c07ae74742e31cbb7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 May 2019 07:28:45 -0700 Subject: [PATCH 10/26] Fix an inverted condition for catch_and_throw --- crates/cli-support/src/js/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 1110142e..f0980f0f 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1958,7 +1958,7 @@ impl<'a> Context<'a> { catch: bool, ) -> Result<(), Error> { let signature = self.bindings.imports[&id].clone(); - let catch_and_rethrow = !self.config.debug; + let catch_and_rethrow = self.config.debug; let js = Rust2Js::new(self) .catch_and_rethrow(catch_and_rethrow) .catch(catch) From 81fbc642f47d819a2e6eff6a68cf6d7b4beb028a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 May 2019 07:28:55 -0700 Subject: [PATCH 11/26] Don't pass strings in raytrace-parallel example Make sure to explicitly parse strings to numbers --- examples/raytrace-parallel/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/raytrace-parallel/index.js b/examples/raytrace-parallel/index.js index 3201982b..7a8a39c6 100644 --- a/examples/raytrace-parallel/index.js +++ b/examples/raytrace-parallel/index.js @@ -116,6 +116,6 @@ function render(scene) { rendering.stop(); rendering = null; } - rendering = new State(scene.render(concurrency.value, pool, ctx)); + rendering = new State(scene.render(parseInt(concurrency.value), pool, ctx)); pool = null; // previous call took ownership of `pool`, zero it out here too } From cbd4b87d081384b046dc4b453e83e1e4d7062236 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 May 2019 07:29:13 -0700 Subject: [PATCH 12/26] Fix handling imported memories Need to make sure we update the import itself and configure the value on the import object! --- crates/cli-support/src/js/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f0980f0f..485683e9 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -453,7 +453,8 @@ impl<'a> Context<'a> { fn gen_init(&mut self, needs_manual_start: bool) -> (String, String) { let mem = self.module.memories.get(self.memory); - let (init_memory1, init_memory2) = if mem.import.is_some() { + let (init_memory1, init_memory2) = if let Some(id) = mem.import { + self.module.imports.get_mut(id).module = "wbg".to_string(); let mut memory = String::from("new WebAssembly.Memory({"); memory.push_str(&format!("initial:{}", mem.initial)); if let Some(max) = mem.maximum { @@ -465,8 +466,8 @@ impl<'a> Context<'a> { memory.push_str("})"); self.imports_post.push_str("let memory;\n"); ( - format!("memory = __exports.memory = maybe_memory;"), - format!("memory = __exports.memory = {};", memory), + format!("memory = imports.wbg.memory = maybe_memory;"), + format!("memory = imports.wbg.memory = {};", memory), ) } else { (String::new(), String::new()) From 55fc5367a500c5e5815a8bf0c30d45f0f0364830 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 May 2019 09:59:55 -0700 Subject: [PATCH 13/26] Fix a typo in the `JsvalEq` intrinsic This is supposed to be `===`, not `==`. --- crates/cli-support/src/js/rust2js.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 813fe63a..d9520632 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1033,7 +1033,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { let expr = match intrinsic { Intrinsic::JsvalEq => { assert_eq!(self.js_arguments.len(), 2); - format!("{} == {}", self.js_arguments[0], self.js_arguments[1]) + format!("{} === {}", self.js_arguments[0], self.js_arguments[1]) } Intrinsic::IsFunction => { From b51df39bc961c64c3ad20a8afe53d63f0be80e0b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 May 2019 10:54:03 -0700 Subject: [PATCH 14/26] Reimplement `anyref` processing and passes This commit reimplements the `anyref` transformation pass tasked with taking raw rustc output and enhancing the module to use `anyref`. This was disabled in the previous commits during refactoring, and now the pass is re-enabled in the manner originally intended. Instead of being tangled up in the `js/mod.rs` pass, the anyref transformation now happens locally within one module, `cli-support/src/anyref.rs`, which exclusively uses the output of the `webidl` module which produces a WebIDL bindings section as well as an auxiliary wasm-bindgen specific section. This makes the anyref transform much more straightforward and local, ensuring that it doesn't propagate elsewhere and can be a largely local concern during the transformation. The main addition needed to support this pass was detailed knowledge of the ABI of a `Descriptor`. This knowledge is already implicitly hardcoded in `js2rust.rs` and `rust2js.rs` through the ABI shims generated. This was previously used for the anyref transformation to piggy-back what was already there, but as a separate pass we are unable to reuse the knowledge in the binding generator. Instead `Descriptor` now has two dedicated methods describing the various ABI properties of a type. This is then asserted to be correct (all the time) when processing bindings, ensuring that the two are kept in sync. --- crates/anyref-xform/src/lib.rs | 104 ++++++++++----------------- crates/cli-support/src/anyref.rs | 87 ++++++++++++++++++++++ crates/cli-support/src/descriptor.rs | 77 ++++++++++++++++++++ crates/cli-support/src/js/js2rust.rs | 74 ++++++------------- crates/cli-support/src/js/mod.rs | 46 +++++------- crates/cli-support/src/js/rust2js.rs | 62 ++++++---------- crates/cli-support/src/lib.rs | 7 +- 7 files changed, 264 insertions(+), 193 deletions(-) create mode 100644 crates/cli-support/src/anyref.rs diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs index 94c2d1fd..1862eb99 100644 --- a/crates/anyref-xform/src/lib.rs +++ b/crates/anyref-xform/src/lib.rs @@ -20,6 +20,7 @@ use std::cmp; use std::collections::{BTreeMap, HashMap, HashSet}; use std::mem; use walrus::ir::*; +use walrus::{ExportId, ImportId}; use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType}; // must be kept in sync with src/lib.rs and ANYREF_HEAP_START @@ -32,8 +33,8 @@ pub struct Context { // Functions within the module that we're gonna be wrapping, organized by // type. The `Function` contains information about what arguments/return // values in the function signature should turn into anyref. - imports: HashMap>, - exports: HashMap, + imports: HashMap, + exports: HashMap, elements: BTreeMap, // When wrapping closures with new shims, this is the index of the next @@ -42,9 +43,6 @@ pub struct Context { // The anyref table we'll be using, injected after construction table: Option, - - // Whether or not the transformation will actually be run at the end - pub enabled: bool, } struct Transform<'a> { @@ -68,7 +66,6 @@ struct Transform<'a> { } struct Function { - name: String, // A map of argument index to whether it's an owned or borrowed anyref // (owned = true) args: HashMap, @@ -87,10 +84,6 @@ impl Context { /// large the function table is so we know what indexes to hand out when /// we're appending entries. pub fn prepare(&mut self, module: &mut Module) -> Result<(), Error> { - if !self.enabled { - return Ok(()); - } - // Figure out what the maximum index of functions pointers are. We'll // be adding new entries to the function table later (maybe) so // precalculate this ahead of time. @@ -118,19 +111,13 @@ impl Context { /// transformed. The actual transformation happens later during `run`. pub fn import_xform( &mut self, - module: &str, - name: &str, + id: ImportId, anyref: &[(usize, bool)], ret_anyref: bool, ) -> &mut Self { - if !self.enabled { - return self; + if let Some(f) = self.function(anyref, ret_anyref) { + self.imports.insert(id, f); } - let f = self.function(name, anyref, ret_anyref); - self.imports - .entry(module.to_string()) - .or_insert_with(Default::default) - .insert(name.to_string(), f); self } @@ -138,15 +125,13 @@ impl Context { /// transformed. The actual transformation happens later during `run`. pub fn export_xform( &mut self, - name: &str, + id: ExportId, anyref: &[(usize, bool)], ret_anyref: bool, ) -> &mut Self { - if !self.enabled { - return self; + if let Some(f) = self.function(anyref, ret_anyref) { + self.exports.insert(id, f); } - let f = self.function(name, anyref, ret_anyref); - self.exports.insert(name.to_string(), f); self } @@ -158,34 +143,26 @@ impl Context { idx: u32, anyref: &[(usize, bool)], ret_anyref: bool, - ) -> u32 { - if !self.enabled { - return idx; - } - let name = format!("closure{}", idx); - let f = self.function(&name, anyref, ret_anyref); - let ret = self.next_element; - self.next_element += 1; - self.elements.insert(ret, (idx, f)); - ret + ) -> Option { + self.function(anyref, ret_anyref).map(|f| { + let ret = self.next_element; + self.next_element += 1; + self.elements.insert(ret, (idx, f)); + ret + }) } - fn function(&self, name: &str, anyref: &[(usize, bool)], ret_anyref: bool) -> Function { - Function { - name: name.to_string(), + fn function(&self, anyref: &[(usize, bool)], ret_anyref: bool) -> Option { + if !ret_anyref && anyref.len() == 0 { + return None; + } + Some(Function { args: anyref.iter().cloned().collect(), ret_anyref, - } - } - - pub fn anyref_table_id(&self) -> TableId { - self.table.unwrap() + }) } pub fn run(&mut self, module: &mut Module) -> Result<(), Error> { - if !self.enabled { - return Ok(()); - } let table = self.table.unwrap(); // Inject a stack pointer global which will be used for managing the @@ -261,9 +238,7 @@ impl Transform<'_> { // Perform transformations of imports, exports, and function pointers. self.process_imports(module); - for m in self.cx.imports.values() { - assert!(m.is_empty()); - } + assert!(self.cx.imports.is_empty()); self.process_exports(module); assert!(self.cx.exports.is_empty()); self.process_elements(module)?; @@ -333,20 +308,15 @@ impl Transform<'_> { walrus::ImportKind::Function(f) => f, _ => continue, }; - let import = { - let entries = match self.cx.imports.get_mut(&import.module) { - Some(s) => s, - None => continue, - }; - match entries.remove(&import.name) { - Some(s) => s, - None => continue, - } + let func = match self.cx.imports.remove(&import.id()) { + Some(s) => s, + None => continue, }; let shim = self.append_shim( f, - import, + &import.name, + func, &mut module.types, &mut module.funcs, &mut module.locals, @@ -356,29 +326,25 @@ impl Transform<'_> { } fn process_exports(&mut self, module: &mut Module) { - let mut new_exports = Vec::new(); - for export in module.exports.iter() { + // let mut new_exports = Vec::new(); + for export in module.exports.iter_mut() { let f = match export.item { walrus::ExportItem::Function(f) => f, _ => continue, }; - let function = match self.cx.exports.remove(&export.name) { + let function = match self.cx.exports.remove(&export.id()) { Some(s) => s, None => continue, }; let shim = self.append_shim( f, + &export.name, function, &mut module.types, &mut module.funcs, &mut module.locals, ); - new_exports.push((export.name.to_string(), shim, export.id())); - } - - for (name, shim, old_id) in new_exports { - module.exports.add(&name, shim); - module.exports.delete(old_id); + export.item = shim.into(); } } @@ -402,6 +368,7 @@ impl Transform<'_> { let target = kind.elements[idx as usize].unwrap(); let shim = self.append_shim( target, + &format!("closure{}", idx), function, &mut module.types, &mut module.funcs, @@ -422,6 +389,7 @@ impl Transform<'_> { fn append_shim( &mut self, shim_target: FunctionId, + name: &str, mut func: Function, types: &mut walrus::ModuleTypes, funcs: &mut walrus::ModuleFunctions, @@ -625,7 +593,7 @@ impl Transform<'_> { // nice name for debugging and then we're good to go! let expr = builder.with_side_effects(before, result, after); let id = builder.finish_parts(shim_ty, params, vec![expr], types, funcs); - let name = format!("{}_anyref_shim", func.name); + let name = format!("{}_anyref_shim", name); funcs.get_mut(id).name = Some(name); self.shims.insert(id); return id; diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs new file mode 100644 index 00000000..44b17b3a --- /dev/null +++ b/crates/cli-support/src/anyref.rs @@ -0,0 +1,87 @@ +use crate::descriptor::Function; +use crate::webidl::{ImportBinding, WasmBindgenAux, WebidlCustomSection, AuxImport}; +use failure::Error; +use std::collections::HashSet; +use walrus::Module; + +pub fn process(module: &mut Module) -> Result<(), Error> { + let mut cfg = wasm_bindgen_anyref_xform::Context::default(); + cfg.prepare(module)?; + let bindings = module + .customs + .get_typed::() + .expect("webidl custom section should exist"); + + for (export, binding) in bindings.exports.iter() { + let (args, ret) = extract_anyrefs(binding); + cfg.export_xform(*export, &args, ret); + } + + for (import, kind) in bindings.imports.iter() { + let binding = match kind { + ImportBinding::Function(f) => f, + ImportBinding::Constructor(f) => f, + ImportBinding::Method(f) => f, + }; + let (args, ret) = extract_anyrefs(binding); + cfg.import_xform(*import, &args, ret); + } + + let aux = module + .customs + .get_typed_mut::() + .expect("webidl custom section should exist"); + for import in aux.import_map.values_mut() { + let closure = match import { + AuxImport::Closure(f) => f, + _ => continue, + }; + let (args, ret) = extract_anyrefs(&closure.function); + if let Some(new) = cfg.table_element_xform(closure.shim_idx, &args, ret) { + closure.shim_idx = new; + } + } + + cfg.run(module)?; + walrus::passes::gc::run(module); + + // The GC pass above may end up removing some imported intrinsics. For + // example `__wbindgen_object_clone_ref` is no longer needed after the + // anyref pass. Make sure to delete the associated metadata for those + // intrinsics so we don't try to access stale intrinsics later on. + let remaining_imports = module + .imports + .iter() + .map(|i| i.id()) + .collect::>(); + module + .customs + .get_typed_mut::() + .expect("webidl custom section should exist") + .imports + .retain(|id, _| remaining_imports.contains(id)); + module + .customs + .get_typed_mut::() + .expect("wasm-bindgen aux section should exist") + .import_map + .retain(|id, _| remaining_imports.contains(id)); + Ok(()) +} + +fn extract_anyrefs(f: &Function) -> (Vec<(usize, bool)>, bool) { + let mut args = Vec::new(); + let mut cur = 0; + if f.ret.abi_returned_through_pointer() { + cur += 1; + } + for arg in f.arguments.iter() { + if arg.is_anyref() { + args.push((cur, true)); + } else if arg.is_ref_anyref() { + args.push((cur, false)); + } + cur += arg.abi_arg_count(); + } + (args, f.ret.is_anyref()) +} diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 6efeb370..ebaf794f 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -292,6 +292,83 @@ impl Descriptor { _ => false, } } + + pub fn abi_returned_through_pointer(&self) -> bool { + if self.vector_kind().is_some() { + return true; + } + if self.get_64().is_some() { + return true; + } + match self { + Descriptor::Option(inner) => match &**inner { + Descriptor::Anyref + | Descriptor::RustStruct(_) + | Descriptor::Enum { .. } + | Descriptor::Char + | Descriptor::Boolean + | Descriptor::I8 + | Descriptor::U8 + | Descriptor::I16 + | Descriptor::U16 => false, + _ => true, + }, + _ => false, + } + } + + pub fn abi_arg_count(&self) -> usize { + if let Descriptor::Option(inner) = self { + if inner.get_64().is_some() { + return 4; + } + if let Descriptor::Ref(inner) = &**inner { + match &**inner { + Descriptor::Anyref => return 1, + _ => {} + } + } + } + if self.stack_closure().is_some() { + return 2; + } + if self.abi_returned_through_pointer() { + 2 + } else { + 1 + } + } + + pub fn assert_abi_return_correct(&self, before: usize, after: usize) { + if before != after { + assert_eq!( + before + 1, + after, + "abi_returned_through_pointer wrong for {:?}", + self, + ); + assert!( + self.abi_returned_through_pointer(), + "abi_returned_through_pointer wrong for {:?}", + self, + ); + } else { + assert!( + !self.abi_returned_through_pointer(), + "abi_returned_through_pointer wrong for {:?}", + self, + ); + } + } + + pub fn assert_abi_arg_correct(&self, before: usize, after: usize) { + assert_eq!( + before + self.abi_arg_count(), + after, + "abi_arg_count wrong for {:?}", + self, + ); + } } fn get(a: &mut &[u32]) -> u32 { diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 0bc199d7..4fe8c303 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -68,15 +68,6 @@ pub struct Js2Rust<'a, 'b: 'a> { /// The string value here is the class that this should be a constructor /// for. constructor: Option, - - /// metadata for anyref transformations - anyref_args: Vec<(usize, bool)>, - ret_anyref: bool, -} - -pub enum ExportedShim<'a> { - Named(&'a str), - TableElement(&'a mut u32), } impl<'a, 'b> Js2Rust<'a, 'b> { @@ -92,8 +83,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ret_ty: String::new(), ret_expr: String::new(), constructor: None, - anyref_args: Vec::new(), - ret_anyref: false, } } @@ -104,17 +93,26 @@ impl<'a, 'b> Js2Rust<'a, 'b> { function: &Function, opt_arg_names: &Option>, ) -> Result<&mut Self, Error> { - if let Some(arg_names) = opt_arg_names { - assert_eq!(arg_names.len(), function.arguments.len()); - for (arg, arg_name) in function.arguments.iter().zip(arg_names) { - self.argument(arg, arg_name.as_str())?; - } - } else { - for arg in function.arguments.iter() { - self.argument(arg, None)?; - } + let arg_names = match opt_arg_names { + Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(), + None => vec![None; function.arguments.len()], + }; + assert_eq!(arg_names.len(), function.arguments.len()); + for (arg, arg_name) in function.arguments.iter().zip(arg_names) { + // Process the function argument and assert that the metadata about + // the number of arguments on the Rust side required is correct. + let before = self.rust_arguments.len(); + self.argument(arg, arg_name)?; + arg.assert_abi_arg_correct(before, self.rust_arguments.len()); } + + // Process the return argument, and assert that the metadata returned + // about the descriptor is indeed correct. + let before = self.rust_arguments.len(); self.ret(&function.ret)?; + function + .ret + .assert_abi_return_correct(before, self.rust_arguments.len()); Ok(self) } @@ -181,12 +179,9 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ret } - pub fn argument<'c, I>(&mut self, arg: &Descriptor, opt_arg_name: I) -> Result<&mut Self, Error> - where - I: Into>, - { + fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> { let i = self.arg_idx; - let name = self.abi_arg(opt_arg_name.into()); + let name = self.abi_arg(arg_name); let (arg, optional) = match arg { Descriptor::Option(t) => (&**t, true), @@ -252,7 +247,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.rust_arguments .push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name)); } else { - self.anyref_args.push((self.rust_arguments.len(), true)); self.rust_arguments.push(name); } } else { @@ -449,7 +443,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.js_arguments .push(JsArgument::required(name.clone(), "any".to_string())); if self.cx.config.anyref { - self.anyref_args.push((self.rust_arguments.len(), false)); self.rust_arguments.push(name); } else { // the "stack-ful" nature means that we're always popping from the @@ -492,7 +485,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { Ok(self) } - pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> { + fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> { if let Some(name) = ty.rust_struct() { match &self.constructor { Some(class) if class == name => { @@ -566,7 +559,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { if ty.is_anyref() { self.ret_ty = "any".to_string(); self.ret_expr = format!("return {};", self.cx.take_object("RET")); - self.ret_anyref = true; return Ok(self); } @@ -784,12 +776,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Returns two strings, the first of which is the JS expression for the /// generated function shim and the second is a TypeScript signature of the /// JS expression. - pub fn finish( - &mut self, - prefix: &str, - invoc: &str, - exported_shim: ExportedShim, - ) -> (String, String, String) { + pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) { let js_args = self .js_arguments .iter() @@ -854,23 +841,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { } ts.push(';'); - if self.ret_anyref || self.anyref_args.len() > 0 { - match exported_shim { - ExportedShim::Named(name) => { - self.cx - .anyref - .export_xform(name, &self.anyref_args, self.ret_anyref); - } - ExportedShim::TableElement(idx) => { - *idx = self.cx.anyref.table_element_xform( - *idx, - &self.anyref_args, - self.ret_anyref, - ); - } - } - } - (js, ts, self.js_doc_comments()) } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 485683e9..08b09318 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2,7 +2,7 @@ mod js2rust; mod rust2js; use crate::descriptor::VectorKind; -use crate::js::js2rust::{ExportedShim, Js2Rust}; +use crate::js::js2rust::Js2Rust; use crate::js::rust2js::Rust2Js; use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; use crate::webidl::{JsImport, JsImportName, WasmBindgenAux, WebidlCustomSection}; @@ -47,8 +47,6 @@ pub struct Context<'a> { /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. pub npm_dependencies: HashMap, - - pub anyref: wasm_bindgen_anyref_xform::Context, } #[derive(Default)] @@ -99,7 +97,6 @@ impl<'a> Context<'a> { module, function_table_needed: false, memory, - anyref: Default::default(), npm_dependencies: Default::default(), }) } @@ -192,13 +189,6 @@ impl<'a> Context<'a> { // `__wrap` and such. self.write_classes()?; - // And now that we're almost ready, run the final "anyref" pass. This is - // where we transform a wasm module which doesn't actually use `anyref` - // anywhere to using the type internally. The transformation here is - // based off all the previous data we've collected so far for each - // import/export listed. - // self.anyref.run(self.module)?; - // We're almost done here, so we can delete any internal exports (like // `__wbindgen_malloc`) if none of our JS glue actually needed it. self.unexport_unused_internal_exports(); @@ -1794,9 +1784,17 @@ impl<'a> Context<'a> { if !self.should_write_global("anyref_table") { return; } - self.module - .exports - .add("__wbg_anyref_table", self.anyref.anyref_table_id()); + let table = self + .module + .tables + .iter() + .find(|t| match t.kind { + walrus::TableKind::Anyref(_) => true, + _ => false, + }) + .expect("failed to find anyref table in module") + .id(); + self.module.exports.add("__wbg_anyref_table", table); } fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> { @@ -1878,11 +1876,7 @@ impl<'a> Context<'a> { AuxExportKind::Function(name) => { let (js, ts, js_doc) = Js2Rust::new(&name, self) .process(&descriptor, &export.arg_names)? - .finish( - "function", - &format!("wasm.{}", wasm_name), - ExportedShim::Named(&wasm_name), - ); + .finish("function", &format!("wasm.{}", wasm_name)); self.export( &name, &js, @@ -1897,11 +1891,7 @@ impl<'a> Context<'a> { let (js, ts, raw_docs) = Js2Rust::new("constructor", self) .constructor(Some(&class)) .process(&descriptor, &export.arg_names)? - .finish( - "", - &format!("wasm.{}", wasm_name), - ExportedShim::Named(&wasm_name), - ); + .finish("", &format!("wasm.{}", wasm_name)); let exported = require_class(&mut self.exported_classes, class); if exported.has_constructor { bail!("found duplicate constructor for class `{}`", class); @@ -1924,11 +1914,9 @@ impl<'a> Context<'a> { j2r.method(false); } } - let (js, ts, raw_docs) = j2r.process(&descriptor, &export.arg_names)?.finish( - "", - &format!("wasm.{}", wasm_name), - ExportedShim::Named(&wasm_name), - ); + let (js, ts, raw_docs) = j2r + .process(&descriptor, &export.arg_names)? + .finish("", &format!("wasm.{}", wasm_name)); let ret_ty = j2r.ret_ty.clone(); let exported = require_class(&mut self.exported_classes, class); let docs = format_doc_comments(&export.comments, Some(raw_docs)); diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index d9520632..36acf1fd 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,6 +1,5 @@ use crate::descriptor::Descriptor; use crate::intrinsic::Intrinsic; -use crate::js::js2rust::ExportedShim; use crate::js::{Context, Js2Rust}; use crate::webidl::{AuxImport, AuxValue, ImportBinding}; use failure::{bail, Error}; @@ -119,9 +118,19 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } }; for arg in function.arguments.iter() { + // Process the function argument and assert that the metadata about + // the number of arguments on the Rust side required is correct. + let before = self.shim_arguments.len(); self.argument(arg)?; + arg.assert_abi_arg_correct(before, self.shim_arguments.len()); } + // Process the return argument, and assert that the metadata returned + // about the descriptor is indeed correct. + let before = self.shim_arguments.len(); self.ret(&function.ret)?; + function + .ret + .assert_abi_return_correct(before, self.shim_arguments.len()); Ok(self) } @@ -308,7 +317,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { if let Some((f, mutable)) = arg.stack_closure() { let arg2 = self.shim_argument(); - let mut shim = f.shim_idx; let (js, _ts, _js_doc) = { let mut builder = Js2Rust::new("", self.cx); if mutable { @@ -320,11 +328,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } else { builder.rust_argument("this.a"); } - builder.rust_argument("this.b").process(f, &None)?.finish( - "function", - "this.f", - ExportedShim::TableElement(&mut shim), - ) + builder + .rust_argument("this.b") + .process(f, &None)? + .finish("function", "this.f") }; self.cx.function_table_needed = true; self.global_idx(); @@ -338,7 +345,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { abi, arg2, js = js, - idx = shim, + idx = f.shim_idx, )); self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi)); self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi)); @@ -676,7 +683,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { format!("new {}({})", js, variadic_args(&self.js_arguments)?) } Style::Method => { - let descriptor = |anchor: &str, extra: &str, field: &str, which:&str| { + let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| { format!( "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", anchor, extra, field, which @@ -741,7 +748,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { assert!(self.style == Style::Function); assert!(!variadic); assert_eq!(self.js_arguments.len(), 3); - let mut shim = closure.shim_idx; let (js, _ts, _js_doc) = { let mut builder = Js2Rust::new("", self.cx); @@ -777,11 +783,9 @@ impl<'a, 'b> Rust2Js<'a, 'b> { .finally("this.a = 0;") .finally("}"); } - builder.process(&closure.function, &None)?.finish( - "function", - "f", - ExportedShim::TableElement(&mut shim), - ) + builder + .process(&closure.function, &None)? + .finish("function", "f") }; self.cx.function_table_needed = true; let body = format!( @@ -795,7 +799,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { let real = cb.bind(cb); real.original = cb; ", - shim, + closure.shim_idx, closure.dtor_idx, &self.js_arguments[1], js, @@ -978,32 +982,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret.push_str(&invoc); ret.push_str("\n}\n"); - // if self.ret_anyref || self.anyref_args.len() > 0 { - // // Some return values go at the the beginning of the argument list - // // (they force a return pointer). Handle that here by offsetting all - // // our arg indices by one, but throw in some sanity checks for if - // // this ever changes. - // if let Some(start) = self.shim_arguments.get(0) { - // if start == "ret" { - // assert!(!self.ret_anyref); - // if let Some(next) = self.shim_arguments.get(1) { - // assert_eq!(next, "arg0"); - // } - // for (idx, _) in self.anyref_args.iter_mut() { - // *idx += 1; - // } - // } else { - // assert_eq!(start, "arg0"); - // } - // } - // self.cx.anyref.import_xform( - // "__wbindgen_placeholder__", - // shim, - // &self.anyref_args, - // self.ret_anyref, - // ); - // } - Ok(ret) } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index b43a251c..1c65c484 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use std::str; use walrus::Module; +mod anyref; mod decode; mod intrinsic; mod descriptor; @@ -316,6 +317,10 @@ impl Bindgen { // supports that aren't covered by WebIDL bindings. webidl::process(&mut module)?; + if self.anyref { + anyref::process(&mut module)?; + } + // If we're in a testing mode then remove the start function since we // shouldn't execute it. if !self.emit_start { @@ -326,8 +331,6 @@ impl Bindgen { // shim generation which will actually generate JS for all this. let (js, ts) = { let mut cx = js::Context::new(&mut module, self)?; - cx.anyref.enabled = self.anyref; - cx.anyref.prepare(cx.module)?; let aux = cx.module.customs.delete_typed::() .expect("aux section should be present"); From 3b5e3edd184857075968a150c9dcd76c4cbdf173 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 3 Jun 2019 11:36:18 -0700 Subject: [PATCH 15/26] Fix anyref closure transformations * Catch all closures by walking all `Descriptor` values and looking for either `Function` or `Closure`. * Update the correct arguments for wasm by ensuring that the closure modifications skip the first two arguments. --- crates/cli-support/src/anyref.rs | 86 ++++++++++++++++++++++------ crates/cli-support/src/js/rust2js.rs | 1 + src/anyref.rs | 2 +- src/lib.rs | 2 +- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 44b17b3a..b56085b1 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,30 +1,33 @@ -use crate::descriptor::Function; -use crate::webidl::{ImportBinding, WasmBindgenAux, WebidlCustomSection, AuxImport}; +use crate::descriptor::{Closure, Descriptor, Function}; +use crate::webidl::{AuxImport, ImportBinding, WasmBindgenAux, WebidlCustomSection}; use failure::Error; use std::collections::HashSet; use walrus::Module; +use wasm_bindgen_anyref_xform::Context; pub fn process(module: &mut Module) -> Result<(), Error> { - let mut cfg = wasm_bindgen_anyref_xform::Context::default(); + let mut cfg = Context::default(); cfg.prepare(module)?; let bindings = module .customs - .get_typed::() + .get_typed_mut::() .expect("webidl custom section should exist"); - for (export, binding) in bindings.exports.iter() { - let (args, ret) = extract_anyrefs(binding); + for (export, binding) in bindings.exports.iter_mut() { + let (args, ret) = extract_anyrefs(binding, 0); cfg.export_xform(*export, &args, ret); + process_closure_arguments(&mut cfg, binding); } - for (import, kind) in bindings.imports.iter() { + for (import, kind) in bindings.imports.iter_mut() { let binding = match kind { ImportBinding::Function(f) => f, ImportBinding::Constructor(f) => f, ImportBinding::Method(f) => f, }; - let (args, ret) = extract_anyrefs(binding); + let (args, ret) = extract_anyrefs(binding, 0); cfg.import_xform(*import, &args, ret); + process_closure_arguments(&mut cfg, binding); } let aux = module @@ -32,13 +35,9 @@ pub fn process(module: &mut Module) -> Result<(), Error> { .get_typed_mut::() .expect("webidl custom section should exist"); for import in aux.import_map.values_mut() { - let closure = match import { - AuxImport::Closure(f) => f, - _ => continue, - }; - let (args, ret) = extract_anyrefs(&closure.function); - if let Some(new) = cfg.table_element_xform(closure.shim_idx, &args, ret) { - closure.shim_idx = new; + match import { + AuxImport::Closure(f) => process_closure(&mut cfg, f), + _ => {} } } @@ -69,9 +68,62 @@ pub fn process(module: &mut Module) -> Result<(), Error> { Ok(()) } -fn extract_anyrefs(f: &Function) -> (Vec<(usize, bool)>, bool) { +/// Process the `function` provided to ensure that all references to `Closure` +/// descriptors are processed below. +fn process_closure_arguments(cfg: &mut Context, function: &mut Function) { + for arg in function.arguments.iter_mut() { + process_descriptor(cfg, arg); + } + process_descriptor(cfg, &mut function.ret); + + fn process_descriptor(cfg: &mut Context, descriptor: &mut Descriptor) { + match descriptor { + Descriptor::Ref(d) + | Descriptor::RefMut(d) + | Descriptor::Option(d) + | Descriptor::Slice(d) + | Descriptor::Clamped(d) + | Descriptor::Vector(d) => process_descriptor(cfg, d), + Descriptor::Closure(c) => process_closure(cfg, c), + Descriptor::Function(c) => process_function(cfg, c), + _ => {} + } + } + + fn process_function(cfg: &mut Context, function: &mut Function) { + let (args, ret) = extract_anyrefs(&function, 2); + if let Some(new) = cfg.table_element_xform(function.shim_idx, &args, ret) { + function.shim_idx = new; + } + process_closure_arguments(cfg, function); + } +} + +/// Ensure that the `Closure` is processed in case any of its arguments +/// recursively contain `anyref` and such. +fn process_closure(cfg: &mut Context, closure: &mut Closure) { + let (args, ret) = extract_anyrefs(&closure.function, 2); + if let Some(new) = cfg.table_element_xform(closure.shim_idx, &args, ret) { + closure.shim_idx = new; + } + process_closure_arguments(cfg, &mut closure.function); +} + +/// Extract a description of the anyref arguments from the function signature +/// described by `f`. +/// +/// The returned values are expected to be passed to the anyref transformation +/// pass, and indicate which arguments (by index) in the wasm signature should +/// be transformed from `i32` to `anyref` as well as whether the returned value +/// is an `anyref` or not. +/// +/// The `offset` argument here is typically 0 and indicates the offset at which +/// the wasm abi arguments described by `f` start at. For closures this is 2 +/// because two synthetic arguments are injected into the wasm signature which +/// aren't present in the `Function` signature. +fn extract_anyrefs(f: &Function, offset: usize) -> (Vec<(usize, bool)>, bool) { let mut args = Vec::new(); - let mut cur = 0; + let mut cur = offset; if f.ret.abi_returned_through_pointer() { cur += 1; } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 36acf1fd..6053fa3d 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1178,6 +1178,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { // // But for now, we just bounce wasm -> js -> wasm because it is // easy. + self.cx.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?; "wasm.__wbindgen_anyref_heap_live_count_impl()".into() } else { self.cx.expose_global_heap(); diff --git a/src/anyref.rs b/src/anyref.rs index 84c2f6a1..47d8502a 100644 --- a/src/anyref.rs +++ b/src/anyref.rs @@ -109,7 +109,7 @@ impl Slab { None => internal_error("slot out of bounds"), }; } - self.data.len() as u32 - free_count - super::JSIDX_RESERVED + self.data.len() as u32 - free_count } } diff --git a/src/lib.rs b/src/lib.rs index 19cfadba..c5ded655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -553,7 +553,7 @@ impl Drop for JsValue { fn drop(&mut self) { unsafe { // We definitely should never drop anything in the stack area - debug_assert!(self.idx >= JSIDX_OFFSET); + debug_assert!(self.idx >= JSIDX_OFFSET, "free of stack slot {}", self.idx); // Otherwise if we're not dropping one of our reserved values, // actually call the intrinsic. See #1054 for eventually removing From c7021ba307aab211d48feba825d24a69176c6539 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:18:00 -0500 Subject: [PATCH 16/26] Update crates/cli-support/src/js/mod.rs Co-Authored-By: Nick Fitzgerald --- crates/cli-support/src/js/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 08b09318..d7f56c6a 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -81,7 +81,7 @@ impl<'a> Context<'a> { Ok(Context { globals: String::new(), imports_post: String::new(), - typescript: format!("/* tslint:disable */\n"), + typescript: "/* tslint:disable */\n".to_string(), exposed_globals: Some(Default::default()), required_internal_exports: Default::default(), imported_names: Default::default(), From 71209686e9fc200ce40759895c1f163cad7ad38c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 07:20:39 -0700 Subject: [PATCH 17/26] Use `unwrap_call` instead of an explicit `match` --- crates/cli-support/src/descriptors.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/cli-support/src/descriptors.rs b/crates/cli-support/src/descriptors.rs index 4752aab9..aef97a14 100644 --- a/crates/cli-support/src/descriptors.rs +++ b/crates/cli-support/src/descriptors.rs @@ -148,13 +148,9 @@ impl WasmBindgenDescriptorsSection { walrus::FunctionKind::Local(l) => l, _ => unreachable!(), }; - match local.get_mut(call_instr) { - Expr::Call(e) => { - assert_eq!(e.func, wbindgen_describe_closure); - e.func = id; - } - _ => unreachable!(), - } + let call = local.get_mut(call_instr).unwrap_call_mut(); + assert_eq!(call.func, wbindgen_describe_closure); + call.func = id; self.closure_imports .insert(import_id, descriptor.unwrap_closure()); } From 4eafaeae2ddf1df5e96072433c56269aa3541f42 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 08:52:54 -0700 Subject: [PATCH 18/26] Handle the function table export on-demand Don't delay processing until `finalize`, but instead process it as soon as it's requested to avoid doing too much logic in `finalize`. --- crates/cli-support/src/js/mod.rs | 26 ++++++++++++-------------- crates/cli-support/src/js/rust2js.rs | 6 +++--- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index d7f56c6a..3f72f775 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -41,7 +41,6 @@ pub struct Context<'a> { defined_identifiers: HashMap, exported_classes: Option>, - function_table_needed: bool, memory: MemoryId, /// A map of the name of npm dependencies we've loaded so far to the path @@ -95,7 +94,6 @@ impl<'a> Context<'a> { .delete_typed::() .unwrap(), module, - function_table_needed: false, memory, npm_dependencies: Default::default(), }) @@ -207,18 +205,6 @@ impl<'a> Context<'a> { needs_manual_start = self.unstart_start_function(); } - // If our JS glue needs to access the function table, then do so here. - // JS closure shim generation may access the function table as an - // example, but if there's no closures in the module there's no need to - // export it! - if self.function_table_needed { - let id = match self.module.tables.main_function_table()? { - Some(id) => id, - None => bail!("no function table found in module"), - }; - self.module.exports.add("__wbg_function_table", id); - } - // After all we've done, especially // `unexport_unused_internal_exports()`, we probably have a bunch of // garbage in the module that's no longer necessary, so delete @@ -2121,6 +2107,18 @@ impl<'a> Context<'a> { ", ); } + + fn export_function_table(&mut self) -> Result<(), Error> { + if !self.should_write_global("wbg-function-table") { + return Ok(()) + } + let id = match self.module.tables.main_function_table()? { + Some(id) => id, + None => bail!("no function table found in module"), + }; + self.module.exports.add("__wbg_function_table", id); + Ok(()) + } } fn generate_identifier(name: &str, used_names: &mut HashMap) -> String { diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 6053fa3d..8d33c550 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -333,7 +333,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { .process(f, &None)? .finish("function", "this.f") }; - self.cx.function_table_needed = true; + self.cx.export_function_table()?; self.global_idx(); self.prelude(&format!( "\ @@ -787,7 +787,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { .process(&closure.function, &None)? .finish("function", "f") }; - self.cx.function_table_needed = true; + self.cx.export_function_table()?; let body = format!( " const f = wasm.__wbg_function_table.get({}); @@ -1147,7 +1147,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Intrinsic::FunctionTable => { assert_eq!(self.js_arguments.len(), 0); - self.cx.function_table_needed = true; + self.cx.export_function_table()?; format!("wasm.__wbg_function_table") } From 6f727d7c13441beeb0fa650b8daa613270c1defb Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 08:56:34 -0700 Subject: [PATCH 19/26] Refactor the module name slightly in `gen_init` --- crates/cli-support/src/js/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 3f72f775..bfd1050b 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -428,9 +428,10 @@ impl<'a> Context<'a> { } fn gen_init(&mut self, needs_manual_start: bool) -> (String, String) { + let module_name = "wbg"; let mem = self.module.memories.get(self.memory); let (init_memory1, init_memory2) = if let Some(id) = mem.import { - self.module.imports.get_mut(id).module = "wbg".to_string(); + self.module.imports.get_mut(id).module = module_name.to_string(); let mut memory = String::from("new WebAssembly.Memory({"); memory.push_str(&format!("initial:{}", mem.initial)); if let Some(max) = mem.maximum { @@ -442,8 +443,8 @@ impl<'a> Context<'a> { memory.push_str("})"); self.imports_post.push_str("let memory;\n"); ( - format!("memory = imports.wbg.memory = maybe_memory;"), - format!("memory = imports.wbg.memory = {};", memory), + format!("memory = imports.{}.memory = maybe_memory;", module_name), + format!("memory = imports.{}.memory = {};", module_name, memory), ) } else { (String::new(), String::new()) @@ -458,7 +459,6 @@ impl<'a> Context<'a> { // Initialize the `imports` object for all import definitions that we're // directed to wire up. let mut imports_init = String::new(); - let module_name = "wbg"; if self.wasm_import_definitions.len() > 0 { imports_init.push_str("imports."); imports_init.push_str(module_name); From cfd3e0406f94efc2d76953192ab8eade47c68216 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:06:24 -0700 Subject: [PATCH 20/26] Split symbol intrinsics into two This allows using WebIDL bindings types to describe both of them instead of having a custom ABI, allowing for more direct and rich bindings eventually! --- crates/cli-support/src/intrinsic.rs | 9 ++++++--- crates/cli-support/src/js/rust2js.rs | 17 ++++++++--------- src/lib.rs | 14 +++++++++----- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index c502689a..6c20bc31 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -103,9 +103,12 @@ intrinsics! { #[symbol = "__wbindgen_string_new"] #[signature = fn(ref_string()) -> Anyref] StringNew, - #[symbol = "__wbindgen_symbol_new"] - #[signature = fn(I32, I32) -> Anyref] - SymbolNew, + #[symbol = "__wbindgen_symbol_anonymous_new"] + #[signature = fn() -> Anyref] + SymbolAnonymousNew, + #[symbol = "__wbindgen_symbol_named_new"] + #[signature = fn(ref_string()) -> Anyref] + SymbolNamedNew, #[symbol = "__wbindgen_number_get"] #[signature = fn(ref_anyref(), F64) -> F64] NumberGet, diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 8d33c550..ed655078 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1080,15 +1080,14 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.js_arguments[0].clone() } - Intrinsic::SymbolNew => { - // FIXME: should probably have two intrinsics, one for a - // new anonymous symbol and one for a symbol with a string - assert_eq!(self.js_arguments.len(), 2); - self.cx.expose_get_string_from_wasm()?; - format!( - "{} === 0 ? Symbol() : Symbol(getStringFromWasm({0}, {}))", - self.js_arguments[0], self.js_arguments[1] - ) + Intrinsic::SymbolNamedNew => { + assert_eq!(self.js_arguments.len(), 1); + format!("Symbol({})", self.js_arguments[0]) + } + + Intrinsic::SymbolAnonymousNew => { + assert_eq!(self.js_arguments.len(), 0); + "Symbol()".to_string() } Intrinsic::NumberGet => { diff --git a/src/lib.rs b/src/lib.rs index c5ded655..f6097f99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ use core::fmt; use core::marker; use core::mem; use core::ops::{Deref, DerefMut}; -use core::ptr; use crate::convert::FromWasmAbi; @@ -171,9 +170,13 @@ impl JsValue { /// JS object corresponding to the symbol created. pub fn symbol(description: Option<&str>) -> JsValue { unsafe { - let ptr = description.map(|s| s.as_ptr()).unwrap_or(ptr::null()); - let len = description.map(|s| s.len()).unwrap_or(0); - JsValue::_new(__wbindgen_symbol_new(ptr, len)) + match description { + Some(description) => JsValue::_new(__wbindgen_symbol_named_new( + description.as_ptr(), + description.len(), + )), + None => JsValue::_new(__wbindgen_symbol_anonymous_new()), + } } } @@ -488,7 +491,8 @@ externs! { fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; fn __wbindgen_number_new(f: f64) -> u32; - fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_symbol_named_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_symbol_anonymous_new() -> u32; fn __wbindgen_anyref_heap_live_count() -> u32; From ee426c03a97413fe2fd4f67e7e1956d9a6d4028d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:15:42 -0700 Subject: [PATCH 21/26] Ensure that generated JS is deterministic Iteration order of hash maps is nondeterministic, so add a `sorted_iter` function and then use that throughout whenever iteration order of a hash map would affect the generated JS. --- crates/cli-support/src/js/mod.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index bfd1050b..1aaf0c2f 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -258,7 +258,7 @@ impl<'a> Context<'a> { } => { js.push_str("let wasm;\n"); - for (id, js) in self.wasm_import_definitions.iter() { + for (id, js) in sorted_iter(&self.wasm_import_definitions) { let import = self.module.imports.get_mut(*id); import.module = format!("./{}.js", module_name); footer.push_str("\nmodule.exports."); @@ -282,7 +282,7 @@ impl<'a> Context<'a> { experimental_modules: true, } => { imports.push_str(&format!("import * as wasm from './{}_bg';\n", module_name)); - for (id, js) in self.wasm_import_definitions.iter() { + for (id, js) in sorted_iter(&self.wasm_import_definitions) { let import = self.module.imports.get_mut(*id); import.module = format!("./{}.js", module_name); footer.push_str("\nexport const "); @@ -356,7 +356,7 @@ impl<'a> Context<'a> { OutputMode::Node { experimental_modules: false, } => { - for (module, items) in self.js_imports.iter() { + for (module, items) in sorted_iter(&self.js_imports) { imports.push_str("const { "); for (i, (item, rename)) in items.iter().enumerate() { if i > 0 { @@ -379,7 +379,7 @@ impl<'a> Context<'a> { experimental_modules: true, } | OutputMode::Web => { - for (module, items) in self.js_imports.iter() { + for (module, items) in sorted_iter(&self.js_imports) { imports.push_str("import { "); for (i, (item, rename)) in items.iter().enumerate() { if i > 0 { @@ -464,7 +464,7 @@ impl<'a> Context<'a> { imports_init.push_str(module_name); imports_init.push_str(" = {};\n"); } - for (id, js) in self.wasm_import_definitions.iter() { + for (id, js) in sorted_iter(&self.wasm_import_definitions) { let import = self.module.imports.get_mut(*id); import.module = module_name.to_string(); imports_init.push_str("imports."); @@ -1822,7 +1822,7 @@ impl<'a> Context<'a> { } pub fn generate(&mut self, aux: &WasmBindgenAux) -> Result<(), Error> { - for (id, export) in aux.export_map.iter() { + for (id, export) in sorted_iter(&aux.export_map) { self.generate_export(*id, export).with_context(|_| { format!( "failed to generate bindings for Rust export `{}`", @@ -1830,7 +1830,7 @@ impl<'a> Context<'a> { ) })?; } - for (id, import) in aux.import_map.iter() { + for (id, import) in sorted_iter(&aux.import_map) { let variadic = aux.imports_with_variadic.contains(&id); let catch = aux.imports_with_catch.contains(&id); self.generate_import(*id, import, variadic, catch) @@ -2195,6 +2195,20 @@ impl ExportedClass { } } +/// Returns a sorted iterator over a hash mpa, sorted based on key. +/// +/// The intention of this API is to be used whenever the iteration order of a +/// `HashMap` might affect the generated JS bindings. We want to ensure that the +/// generated output is deterministic and we do so by ensuring that iteration of +/// hash maps is consistently sorted. +fn sorted_iter(map: &HashMap) -> impl Iterator + where K: Ord, +{ + let mut pairs = map.iter().collect::>(); + pairs.sort_by_key(|(k, _)| *k); + pairs.into_iter() +} + #[test] fn test_generate_identifier() { let mut used_names: HashMap = HashMap::new(); From c22b907e7f7b3eca2c22e4ff07fbf64287151cbb Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:18:48 -0700 Subject: [PATCH 22/26] Touch up some comments --- crates/cli-support/src/js/mod.rs | 14 +++++--------- crates/cli-support/src/lib.rs | 5 +++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 1aaf0c2f..e5602ed9 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -191,15 +191,11 @@ impl<'a> Context<'a> { // `__wbindgen_malloc`) if none of our JS glue actually needed it. self.unexport_unused_internal_exports(); - // Handle the `start` function, if one was specified. If we're in a - // --test mode (such as wasm-bindgen-test-runner) then we skip this - // entirely. Otherwise we want to first add a start function to the - // `start` section if one is specified. - // - // Note that once a start function is added, if any, we immediately - // un-start it. This is done because we require that the JS glue - // initializes first, so we execute wasm startup manually once the JS - // glue is all in place. + // Initialization is just flat out tricky and not something we + // understand super well. To try to handle various issues that have come + // up we always remove the `start` function if one is present. The JS + // bindings glue then manually calls the start function (if it was + // previously present). let mut needs_manual_start = false; if self.config.emit_start { needs_manual_start = self.unstart_start_function(); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 1c65c484..16ddca9c 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -317,6 +317,11 @@ impl Bindgen { // supports that aren't covered by WebIDL bindings. webidl::process(&mut module)?; + // Now that we've got type information from the webidl processing pass, + // touch up the output of rustc to insert anyref shims where necessary. + // This is only done if the anyref pass is enabled, which it's + // currently off-by-default since `anyref` is still in development in + // engines. if self.anyref { anyref::process(&mut module)?; } From 6e8c3e88f86790cdb526e1fcad2765ba8b451eb9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:26:27 -0700 Subject: [PATCH 23/26] Directly import `__wrap` functions if possible These can have similar optimizations as importing a value directly. --- crates/cli-support/src/js/rust2js.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index ed655078..b2d39ada 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -907,6 +907,9 @@ impl<'a, 'b> Rust2Js<'a, 'b> { assert!(!variadic); assert_eq!(self.js_arguments.len(), 1); self.cx.require_class_wrap(class); + if self.is_noop() { + return Ok(format!("{}.__wrap", class)); + } format!("{}.__wrap({})", class, self.js_arguments[0]) } From bf1a31e139f954eb7cc450e00e304659fcef8ec9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 4 Jun 2019 09:29:17 -0700 Subject: [PATCH 24/26] Don't generate a free function shim for classes This was once required due to flavorful management of the `WeakRef` proposal but nowadays it's simple enough that we don't need to refactor it out here. --- crates/cli-support/src/js/mod.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index e5602ed9..2c4f80f4 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -571,26 +571,17 @@ impl<'a> Context<'a> { )); } - self.global(&format!( - " - function free{}(ptr) {{ - wasm.{}(ptr); - }} - ", - name, - wasm_bindgen_shared::free_function(&name) - )); - if self.config.weak_refs { self.global(&format!( " const {}FinalizationGroup = new FinalizationGroup((items) => {{ for (const ptr of items) {{ - free{}(ptr); + wasm.{}(ptr); }} }}); ", - name, name, + name, + wasm_bindgen_shared::free_function(&name), )); } @@ -600,7 +591,7 @@ impl<'a> Context<'a> { const ptr = this.ptr; this.ptr = 0; {} - free{}(ptr); + wasm.{}(ptr); }} ", if self.config.weak_refs { @@ -608,7 +599,7 @@ impl<'a> Context<'a> { } else { String::new() }, - name, + wasm_bindgen_shared::free_function(&name), )); ts_dst.push_str(" free(): void;"); dst.push_str(&class.contents); From 59e773f5ec2b8ed95e05d80ec0c682c5848f587a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jun 2019 11:07:57 -0700 Subject: [PATCH 25/26] Update walrus --- Cargo.toml | 1 - crates/anyref-xform/Cargo.toml | 2 +- crates/cli-support/Cargo.toml | 2 +- crates/cli/Cargo.toml | 2 +- crates/threads-xform/Cargo.toml | 2 +- crates/wasm-interpreter/Cargo.toml | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd2714a2..b7c9b81e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,4 +90,3 @@ wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } js-sys = { path = 'crates/js-sys' } web-sys = { path = 'crates/web-sys' } -walrus = { git = 'https://github.com/rustwasm/walrus' } diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index 761c72dd..941a8c9f 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -13,4 +13,4 @@ edition = '2018' [dependencies] failure = "0.1" -walrus = "0.7.0" +walrus = "0.8.0" diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index a07a734c..f71a77f7 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -18,7 +18,7 @@ log = "0.4" rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" -walrus = "0.7.0" +walrus = "0.8.0" wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.45' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.45' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.45' } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 2e057dc0..4d1bce28 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -24,7 +24,7 @@ rouille = { version = "3.0.0", default-features = false } serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" -walrus = "0.7.0" +walrus = "0.8.0" wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.45" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.45" } diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index ba3ecdaa..fb6cae31 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -13,4 +13,4 @@ edition = "2018" [dependencies] failure = "0.1" -walrus = "0.7.0" +walrus = "0.8.0" diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index 3701fe6d..b667ac85 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -14,7 +14,7 @@ edition = '2018' [dependencies] failure = "0.1" log = "0.4" -walrus = "0.7.0" +walrus = "0.8.0" [dev-dependencies] tempfile = "3" From e24c03182b540e1c99c5382f0532db096a7c5c37 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Jun 2019 09:47:18 -0700 Subject: [PATCH 26/26] Attempt to fix CI --- azure-pipelines.yml | 6 +++--- ci/azure-install-rust.yml | 15 +++++++++------ ci/azure-install-wasm-pack.yml | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d57bc4d3..70a05351 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -101,7 +101,7 @@ jobs: steps: - template: ci/azure-install-rust.yml - template: ci/azure-install-node.yml - - template: ci/azure-install-sccache.yml + #- template: ci/azure-install-sccache.yml - script: cargo test -p wasm-bindgen-webidl - script: cargo test -p webidl-tests --target wasm32-unknown-unknown env: @@ -128,7 +128,7 @@ jobs: cd wabt/build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=off -DCMAKE_CXX_COMPILER_LAUNCHER=$RUSTC_WRAPPER cmake --build . -- -j$(nproc) - echo "##vso[task.setvariable variable=PATH;]$PATH:$PWD" + echo "##vso[task.prependpath]$PWD" - script: cargo test -p wasm-bindgen-wasm-interpreter - job: test_typescript_output @@ -171,7 +171,7 @@ jobs: - script: | set -e curl -L https://github.com/japaric/xargo/releases/download/v0.3.13/xargo-v0.3.13-x86_64-unknown-linux-musl.tar.gz | tar xzf - - echo "##vso[task.setvariable variable=PATH;]$PATH:$PWD" + echo "##vso[task.prependpath]$PWD" displayName: "install xargo" - script: | set -e diff --git a/ci/azure-install-rust.yml b/ci/azure-install-rust.yml index dd4ac1c0..6eeac289 100644 --- a/ci/azure-install-rust.yml +++ b/ci/azure-install-rust.yml @@ -3,17 +3,20 @@ parameters: steps: - bash: | - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $TOOLCHAIN - echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + set -e + if command -v rustup; then + rustup update $TOOLCHAIN + rustup default $TOOLCHAIN + else + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $TOOLCHAIN + echo "##vso[task.prependpath]$HOME/.cargo/bin" + fi displayName: Install rust - Unix condition: ne( variables['Agent.OS'], 'Windows_NT' ) env: TOOLCHAIN: ${{ parameters.toolchain }} - - script: | - curl -sSf -o rustup-init.exe https://win.rustup.rs - rustup-init.exe -y --default-toolchain %TOOLCHAIN% - echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin" + - bash: rustup update --no-self-update $TOOLCHAIN && rustup default $TOOLCHAIN displayName: Install rust - Windows condition: eq( variables['Agent.OS'], 'Windows_NT' ) env: diff --git a/ci/azure-install-wasm-pack.yml b/ci/azure-install-wasm-pack.yml index 4759d82e..8afcbee2 100644 --- a/ci/azure-install-wasm-pack.yml +++ b/ci/azure-install-wasm-pack.yml @@ -4,5 +4,5 @@ steps: - script: | set -ex cargo build -p wasm-bindgen-cli - ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen + ln -snf `pwd`/target/debug/wasm-bindgen $(dirname `which cargo`)/wasm-bindgen displayName: "install wasm-bindgen for `wasm-pack` to use"