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()); +}