diff --git a/.travis.yml b/.travis.yml index a1bf5e38875..10281e56533 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,8 @@ matrix: - cargo test # Run the main body of the test suite - cargo test --target wasm32-unknown-unknown + # Make sure the anyref pass at least compiles even if it doesn't run + - NODE_ARGS=/dev/null WASM_BINDGEN_ANYREF=1 cargo test --target wasm32-unknown-unknown --test wasm # Rerun the test suite but disable `--debug` in generated JS - WASM_BINDGEN_NO_DEBUG=1 cargo test --target wasm32-unknown-unknown # Make sure our serde tests work diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml new file mode 100644 index 00000000000..bc6a4314d6d --- /dev/null +++ b/crates/anyref-xform/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wasm-bindgen-anyref-xform" +version = "0.2.37" +authors = ["The wasm-bindgen Developers"] +license = "MIT/Apache-2.0" +repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/anyref-xform" +homepage = "https://rustwasm.github.io/wasm-bindgen/" +documentation = "https://docs.rs/wasm-bindgen-anyref-xform" +description = """ +Internal anyref transformations for wasm-bindgen +""" +edition = '2018' + +[dependencies] +failure = "0.1" +walrus = "0.4" diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs new file mode 100644 index 00000000000..14747507e7b --- /dev/null +++ b/crates/anyref-xform/src/lib.rs @@ -0,0 +1,730 @@ +//! Transformation for wasm-bindgen to enable usage of `anyref` in a wasm +//! module. +//! +//! This crate is in charge of enabling code using `wasm-bindgen` to use the +//! `anyref` type inside of the wasm module. This transformation pass primarily +//! wraps exports and imports in shims which use `anyref`, but quickly turn them +//! into `i32` value types. This is all largely a stopgap until Rust has +//! first-class support for the `anyref` type, but that's thought to be in the +//! far future and will take quite some time to implement. In the meantime, we +//! have this! +//! +//! The pass here works by collecting information during binding generation +//! about imports and exports. Afterwards this pass runs in one go against a +//! wasm module, updating exports, imports, calls to these functions, etc. The +//! goal at least is to have valid wasm modules coming in that don't use +//! `anyref` and valid wasm modules going out which use `anyref` at the fringes. + +use failure::{bail, format_err, Error}; +use std::cmp; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem; +use walrus::ir::*; +use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType}; + +// must be kept in sync with src/lib.rs and ANYREF_HEAP_START +const DEFAULT_MIN: u32 = 32; + +/// State of the anyref pass, used to collect information while bindings are +/// generated and used eventually to actually execute the entire pass. +#[derive(Default)] +pub struct Context { + // Functions within the module that we're gonna be wrapping, organized by + // type. The `Function` contains information about what arguments/return + // values in the function signature should turn into anyref. + imports: HashMap>, + exports: HashMap, + elements: BTreeMap, + + // When wrapping closures with new shims, this is the index of the next + // table entry that we'll be handing out. + next_element: u32, + + // The anyref table we'll be using, injected after construction + table: Option, + + // Whether or not the transformation will actually be run at the end + pub enabled: bool, +} + +struct Transform<'a> { + cx: &'a mut Context, + + // A map of functions to intrinsics that they represent + intrinsic_map: HashMap, + // A map of old import functions to the new internally-defined shims which + // call the correct new import functions + import_map: HashMap, + // A set of all shims we've created + shims: HashSet, + + // Indices of items that we have injected or found. This state is maintained + // during the pass execution. + table: TableId, + clone_ref: FunctionId, + heap_alloc: FunctionId, + heap_dealloc: FunctionId, + stack_pointer: GlobalId, +} + +struct Function { + name: String, + // A map of argument index to whether it's an owned or borrowed anyref + // (owned = true) + args: HashMap, + ret_anyref: bool, +} + +enum Intrinsic { + TableGrow, + TableSetNull, + DropRef, + CloneRef, +} + +impl Context { + /// Executed first very early over a wasm module, used to learn about how + /// large the function table is so we know what indexes to hand out when + /// we're appending entries. + pub fn prepare(&mut self, module: &mut Module) -> Result<(), Error> { + if !self.enabled { + return Ok(()); + } + + // Figure out what the maximum index of functions pointers are. We'll + // be adding new entries to the function table later (maybe) so + // precalculate this ahead of time. + let mut tables = module.tables.iter().filter_map(|t| match &t.kind { + walrus::TableKind::Function(f) => Some(f), + _ => None, + }); + if let Some(t) = tables.next() { + if tables.next().is_some() { + bail!("more than one function table present") + } + self.next_element = t.elements.len() as u32; + } + drop(tables); + + // Add in an anyref table to the module, which we'll be using for + // our transform below. + let kind = walrus::TableKind::Anyref(Default::default()); + self.table = Some(module.tables.add_local(DEFAULT_MIN, None, kind)); + + Ok(()) + } + + /// Store information about an imported function that needs to be + /// transformed. The actual transformation happens later during `run`. + pub fn import_xform( + &mut self, + module: &str, + name: &str, + anyref: &[(usize, bool)], + ret_anyref: bool, + ) -> &mut Self { + if !self.enabled { + return self; + } + let f = self.function(name, anyref, ret_anyref); + self.imports + .entry(module.to_string()) + .or_insert_with(Default::default) + .insert(name.to_string(), f); + self + } + + /// Store information about an exported function that needs to be + /// transformed. The actual transformation happens later during `run`. + pub fn export_xform( + &mut self, + name: &str, + anyref: &[(usize, bool)], + ret_anyref: bool, + ) -> &mut Self { + if !self.enabled { + return self; + } + let f = self.function(name, anyref, ret_anyref); + self.exports.insert(name.to_string(), f); + self + } + + /// Store information about a function pointer that needs to be transformed. + /// The actual transformation happens later during `run`. Returns an index + /// that the new wrapped function pointer will be injected at. + pub fn table_element_xform( + &mut self, + idx: u32, + anyref: &[(usize, bool)], + ret_anyref: bool, + ) -> u32 { + if !self.enabled { + return idx; + } + let name = format!("closure{}", idx); + let f = self.function(&name, anyref, ret_anyref); + let ret = self.next_element; + self.next_element += 1; + self.elements.insert(ret, (idx, f)); + ret + } + + fn function(&self, name: &str, anyref: &[(usize, bool)], ret_anyref: bool) -> Function { + Function { + name: name.to_string(), + args: anyref.iter().cloned().collect(), + ret_anyref, + } + } + + pub fn anyref_table_id(&self) -> TableId { + self.table.unwrap() + } + + pub fn run(&mut self, module: &mut Module) -> Result<(), Error> { + if !self.enabled { + return Ok(()); + } + let table = self.table.unwrap(); + + // Inject a stack pointer global which will be used for managing the + // stack on the anyref table. + let init = InitExpr::Value(Value::I32(DEFAULT_MIN as i32)); + let stack_pointer = module.globals.add_local(ValType::I32, true, init); + + let mut heap_alloc = None; + let mut heap_dealloc = None; + + // Find exports of some intrinsics which we only need for a runtime + // implementation. + for export in module.exports.iter() { + let f = match export.item { + walrus::ExportItem::Function(f) => f, + _ => continue, + }; + match export.name.as_str() { + "__wbindgen_anyref_table_alloc" => heap_alloc = Some(f), + "__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f), + _ => {} + } + } + let heap_alloc = heap_alloc.ok_or_else(|| format_err!("failed to find heap alloc"))?; + let heap_dealloc = + heap_dealloc.ok_or_else(|| format_err!("failed to find heap dealloc"))?; + + // Create a shim function that looks like: + // + // (func __wbindgen_object_clone_ref (param i32) (result i32) + // (local i32) + // (table.set + // (tee_local 1 (call $heap_alloc)) + // (table.get (local.get 0))) + // (local.get 1)) + let mut builder = walrus::FunctionBuilder::new(); + let arg = module.locals.add(ValType::I32); + let local = module.locals.add(ValType::I32); + + let alloc = builder.call(heap_alloc, Box::new([])); + let tee = builder.local_tee(local, alloc); + let get_arg = builder.local_get(arg); + let get_table = builder.table_get(table, get_arg); + let set_table = builder.table_set(table, tee, get_table); + let get_local = builder.local_get(local); + + let ty = module.types.add(&[ValType::I32], &[ValType::I32]); + let clone_ref = builder.finish(ty, vec![arg], vec![set_table, get_local], module); + let name = "__wbindgen_object_clone_ref".to_string(); + module.funcs.get_mut(clone_ref).name = Some(name); + + // And run the transformation! + Transform { + cx: self, + intrinsic_map: HashMap::new(), + import_map: HashMap::new(), + shims: HashSet::new(), + table, + clone_ref, + heap_alloc, + heap_dealloc, + stack_pointer, + } + .run(module) + } +} + +impl Transform<'_> { + fn run(&mut self, module: &mut Module) -> Result<(), Error> { + // Detect all the various intrinsics and such. This will also along the + // way inject an intrinsic for cloning an anyref. + self.find_intrinsics(module)?; + + // Perform transformations of imports, exports, and function pointers. + self.process_imports(module); + for m in self.cx.imports.values() { + assert!(m.is_empty()); + } + self.process_exports(module); + assert!(self.cx.exports.is_empty()); + self.process_elements(module)?; + assert!(self.cx.elements.is_empty()); + + // If we didn't actually transform anything, no need to inject or + // rewrite anything from below. + if self.shims.is_empty() { + return Ok(()); + } + + // Perform all instruction transformations to rewrite calls between + // functions and make sure everything is still hooked up right. + self.rewrite_calls(module); + + // Inject initialization routine to set up default slots in the table + // (things like null/undefined/true/false) + self.inject_initialization(module); + + Ok(()) + } + + fn find_intrinsics(&mut self, module: &mut Module) -> Result<(), Error> { + // Build up a map of various imported intrinsics to wire them up to + // different implementations or different functions. + for import in module.imports.iter_mut() { + let f = match import.kind { + walrus::ImportKind::Function(f) => f, + _ => continue, + }; + if import.module == "__wbindgen_anyref_xform__" { + match import.name.as_str() { + "__wbindgen_anyref_table_grow" => { + self.intrinsic_map.insert(f, Intrinsic::TableGrow); + } + "__wbindgen_anyref_table_set_null" => { + self.intrinsic_map.insert(f, Intrinsic::TableSetNull); + } + n => bail!("unknown intrinsic: {}", n), + } + } else if import.module == "__wbindgen_placeholder__" { + match import.name.as_str() { + "__wbindgen_object_drop_ref" => { + self.intrinsic_map.insert(f, Intrinsic::DropRef); + } + "__wbindgen_object_clone_ref" => { + self.intrinsic_map.insert(f, Intrinsic::CloneRef); + } + _ => continue, + } + } else { + continue; + } + + // Make sure we don't actually end up using the original import + // because any invocation of them should be remapped to something + // else. + import.name = format!("{}_unused", import.name); + } + + Ok(()) + } + + fn process_imports(&mut self, module: &mut Module) { + for import in module.imports.iter_mut() { + let f = match import.kind { + walrus::ImportKind::Function(f) => f, + _ => continue, + }; + let import = { + let entries = match self.cx.imports.get_mut(&import.module) { + Some(s) => s, + None => continue, + }; + match entries.remove(&import.name) { + Some(s) => s, + None => continue, + } + }; + + let shim = self.append_shim( + f, + import, + &mut module.types, + &mut module.funcs, + &mut module.locals, + ); + self.import_map.insert(f, shim); + } + } + + fn process_exports(&mut self, module: &mut Module) { + let mut new_exports = Vec::new(); + for export in module.exports.iter() { + let f = match export.item { + walrus::ExportItem::Function(f) => f, + _ => continue, + }; + let function = match self.cx.exports.remove(&export.name) { + Some(s) => s, + None => continue, + }; + let shim = self.append_shim( + f, + function, + &mut module.types, + &mut module.funcs, + &mut module.locals, + ); + new_exports.push((export.name.to_string(), shim, export.id())); + } + + for (name, shim, old_id) in new_exports { + module.exports.add(&name, shim); + module.exports.delete(old_id); + } + } + + fn process_elements(&mut self, module: &mut Module) -> Result<(), Error> { + let table = match module.tables.main_function_table()? { + Some(t) => t, + None => return Ok(()), + }; + let table = module.tables.get_mut(table); + let kind = match &mut table.kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + if kind.relative_elements.len() > 0 { + bail!("not compatible with relative element initializers yet"); + } + + // Create shims for all our functions and append them all to the segment + // which places elements at the end. + while let Some((idx, function)) = self.cx.elements.remove(&(kind.elements.len() as u32)) { + let target = kind.elements[idx as usize].unwrap(); + let shim = self.append_shim( + target, + function, + &mut module.types, + &mut module.funcs, + &mut module.locals, + ); + kind.elements.push(Some(shim)); + } + + // ... and next update the limits of the table in case any are listed. + table.initial = cmp::max(table.initial, kind.elements.len() as u32); + if let Some(max) = table.maximum { + table.maximum = Some(cmp::max(max, kind.elements.len() as u32)); + } + + Ok(()) + } + + fn append_shim( + &mut self, + shim_target: FunctionId, + mut func: Function, + types: &mut walrus::ModuleTypes, + funcs: &mut walrus::ModuleFunctions, + locals: &mut walrus::ModuleLocals, + ) -> FunctionId { + let target = funcs.get_mut(shim_target); + let (is_export, ty) = match &mut target.kind { + walrus::FunctionKind::Import(f) => (false, &mut f.ty), + walrus::FunctionKind::Local(f) => (true, &mut f.ty), + _ => unreachable!() + }; + + let target_ty = types.get(*ty); + + // Learn about the various operations we're doing up front. Afterwards + // we'll have a better idea bout what sort of code we're gonna be + // generating. + enum Convert { + None, + Store { owned: bool }, + Load { owned: bool }, + } + let mut param_tys = Vec::new(); + let mut param_convert = Vec::new(); + let mut anyref_stack = 0; + + for (i, old_ty) in target_ty.params().iter().enumerate() { + let is_owned = func.args.remove(&i); + let new_ty = is_owned + .map(|_which| ValType::Anyref) + .unwrap_or(old_ty.clone()); + param_tys.push(new_ty.clone()); + if new_ty == *old_ty { + param_convert.push(Convert::None); + } else if is_export { + // We're calling an export, so we need to push this anyref into + // a table somehow. + param_convert.push(Convert::Store { + owned: is_owned.unwrap(), + }); + if is_owned == Some(false) { + anyref_stack += 1; + } + } else { + // We're calling an import, so we just need to fetch our table + // value. + param_convert.push(Convert::Load { + owned: is_owned.unwrap(), + }); + } + } + + let new_ret = if func.ret_anyref { + assert_eq!(target_ty.results(), &[ValType::I32]); + vec![ValType::Anyref] + } else { + target_ty.results().to_vec() + }; + let anyref_ty = types.add(¶m_tys, &new_ret); + + // If we're an export then our shim is what's actually going to get + // exported, and it's going to have the anyref signature. + // + // If we're an import, then our shim is what the Rust code calls, which + // means it'll have the original signature. The existing import's + // signature, however, is transformed to be an anyref signature. + let shim_ty = if is_export { + anyref_ty + } else { + mem::replace(ty, anyref_ty) + }; + + let mut builder = walrus::FunctionBuilder::new(); + let mut before = Vec::new(); + let params = types.get(shim_ty) + .params() + .iter() + .cloned() + .map(|ty| locals.add(ty)) + .collect::>(); + + // Unconditionally allocate some locals which get cleaned up in later + // gc passes if we don't actually end up using them. + let fp = locals.add(ValType::I32); + let scratch_i32 = locals.add(ValType::I32); + let scratch_anyref = locals.add(ValType::Anyref); + + // Update our stack pointer if there's any borrowed anyref objects. + if anyref_stack > 0 { + let sp = builder.global_get(self.stack_pointer); + let size = builder.const_(Value::I32(anyref_stack)); + let new_sp = builder.binop(BinaryOp::I32Sub, sp, size); + let tee = builder.local_tee(fp, new_sp); + before.push(builder.global_set(self.stack_pointer, tee)); + } + let mut next_stack_offset = 0; + + let mut args = Vec::new(); + for (i, convert) in param_convert.iter().enumerate() { + let local = builder.local_get(params[i]); + args.push(match *convert { + Convert::None => local, + Convert::Load { owned: true } => { + // load the anyref onto the stack, then afterwards + // deallocate our index, leaving the anyref on the stack. + let get = builder.table_get(self.table, local); + let free = builder.call(self.heap_dealloc, Box::new([local])); + builder.with_side_effects(Vec::new(), get, vec![free]) + } + Convert::Load { owned: false } => builder.table_get(self.table, local), + Convert::Store { owned: true } => { + // Allocate space for the anyref, store it, and then leave + // the index of the allocated anyref on the stack. + let alloc = builder.call(self.heap_alloc, Box::new([])); + let tee = builder.local_tee(scratch_i32, alloc); + let store = builder.table_set(self.table, tee, local); + let get = builder.local_get(scratch_i32); + builder.with_side_effects(vec![store], get, Vec::new()) + } + Convert::Store { owned: false } => { + // Store an anyref at an offset from our function's stack + // pointer frame. + let get_fp = builder.local_get(fp); + next_stack_offset += 1; + let (index, idx_local) = if next_stack_offset == 1 { + (get_fp, fp) + } else { + let rhs = builder.i32_const(next_stack_offset); + let add = builder.binop(BinaryOp::I32Add, get_fp, rhs); + (builder.local_tee(scratch_i32, add), scratch_i32) + }; + let store = builder.table_set(self.table, index, local); + let get = builder.local_get(idx_local); + builder.with_side_effects(vec![store], get, Vec::new()) + } + }); + } + + // Now that we've converted all the arguments, call the original + // function. This may be either an import or an export which we're + // wrapping. + let mut result = builder.call(shim_target, args.into_boxed_slice()); + let mut after = Vec::new(); + + // If an anyref value is returned, then we need to be sure to apply + // special treatment to convert it to an i32 as well. Note that only + // owned anyref values can be returned, so that's all that's handled + // here. + if func.ret_anyref { + if is_export { + // We're an export so we have an i32 on the stack and need to + // convert it to an anyref, basically by doing the same as an + // owned load above: get the value then deallocate our slot. + let tee = builder.local_tee(scratch_i32, result); + result = builder.table_get(self.table, tee); + let get_local = builder.local_get(scratch_i32); + after.push(builder.call(self.heap_dealloc, Box::new([get_local]))); + } else { + // Imports are the opposite, we have any anyref on the stack + // and convert it to an i32 by allocating space for it and + // storing it there. + before.push(builder.local_set(scratch_anyref, result)); + let alloc = builder.call(self.heap_alloc, Box::new([])); + let tee = builder.local_tee(scratch_i32, alloc); + let get = builder.local_get(scratch_anyref); + before.push(builder.table_set(self.table, tee, get)); + result = builder.local_get(scratch_i32); + } + } + + // On function exit restore our anyref stack pointer if we decremented + // it to start off. + // + // Note that we pave over all our stack slots with `ref.null` to ensure + // that the table doesn't accidentally hold a strong reference to items + // no longer in use by our wasm instance. + // + // TODO: use `table.fill` once that's spec'd + if anyref_stack > 0 { + for i in 0..anyref_stack { + let get_fp = builder.local_get(fp); + let index = if i > 0 { + let offset = builder.i32_const(i); + builder.binop(BinaryOp::I32Add, get_fp, offset) + } else { + get_fp + }; + let null = builder.ref_null(); + after.push(builder.table_set(self.table, index, null)); + } + + let get_fp = builder.local_get(fp); + let size = builder.i32_const(anyref_stack); + let new_sp = builder.binop(BinaryOp::I32Add, get_fp, size); + after.push(builder.global_set(self.stack_pointer, new_sp)); + } + + // Create the final expression node and then finish the function builder + // with a fresh type we've been calculating so far. Give the function a + // nice name for debugging and then we're good to go! + let expr = builder.with_side_effects(before, result, after); + let id = builder.finish_parts(shim_ty, params, vec![expr], types, funcs); + let name = format!("{}_anyref_shim", func.name); + funcs.get_mut(id).name = Some(name); + self.shims.insert(id); + return id; + } + + fn rewrite_calls(&mut self, module: &mut Module) { + for (id, func) in module.funcs.iter_local_mut() { + if self.shims.contains(&id) { + continue; + } + let mut entry = func.entry_block(); + Rewrite { + func, + xform: self, + replace: None, + } + .visit_block_id_mut(&mut entry); + } + + struct Rewrite<'a, 'b> { + func: &'a mut walrus::LocalFunction, + xform: &'a Transform<'b>, + replace: Option, + } + + impl VisitorMut for Rewrite<'_, '_> { + fn local_function_mut(&mut self) -> &mut walrus::LocalFunction { + self.func + } + + fn visit_expr_id_mut(&mut self, expr: &mut ExprId) { + expr.visit_mut(self); + if let Some(id) = self.replace.take() { + *expr = id; + } + } + + fn visit_call_mut(&mut self, e: &mut Call) { + e.visit_mut(self); + let intrinsic = match self.xform.intrinsic_map.get(&e.func) { + Some(f) => f, + None => { + // If this wasn't a call of an intrinsic, but it was a + // call of one of our old import functions then we + // switch the functions we're calling here. + if let Some(f) = self.xform.import_map.get(&e.func) { + e.func = *f; + } + return; + } + }; + + let builder = self.func.builder_mut(); + + match intrinsic { + Intrinsic::TableGrow => { + assert_eq!(e.args.len(), 1); + let delta = e.args[0]; + let null = builder.ref_null(); + let grow = builder.table_grow(self.xform.table, delta, null); + self.replace = Some(grow); + } + Intrinsic::TableSetNull => { + assert_eq!(e.args.len(), 1); + let index = e.args[0]; + let null = builder.ref_null(); + let set = builder.table_set(self.xform.table, index, null); + self.replace = Some(set); + } + Intrinsic::DropRef => e.func = self.xform.heap_dealloc, + Intrinsic::CloneRef => e.func = self.xform.clone_ref, + } + } + } + } + + // Ensure that the `start` function for this module calls the + // `__wbindgen_init_anyref_table` function. This'll ensure that all + // instances of this module have the initial slots of the anyref table + // initialized correctly. + fn inject_initialization(&mut self, module: &mut Module) { + let ty = module.types.add(&[], &[]); + let import = module.add_import_func( + "__wbindgen_placeholder__", + "__wbindgen_init_anyref_table", + ty, + ); + + let prev_start = match module.start { + Some(f) => f, + None => { + module.start = Some(import); + return; + } + }; + + let mut builder = walrus::FunctionBuilder::new(); + let call_init = builder.call(import, Box::new([])); + let call_prev = builder.call(prev_start, Box::new([])); + let new_start = builder.finish(ty, Vec::new(), vec![call_init, call_prev], module); + module.start = Some(new_start); + } +} diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 4e190ce707d..9c7eb06a245 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -3,9 +3,9 @@ use std::env; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter::FromIterator; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::SeqCst; -use std::sync::atomic::{AtomicBool}; -use std::sync::atomic::{AtomicUsize}; use ast; use proc_macro2::{self, Ident}; diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 7c84aa87fa9..0a444d69fe1 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4" rustc-demangle = "0.1.13" tempfile = "3.0" walrus = "0.4.0" +wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.37' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.37' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.37' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.37' } diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs index 24d5e9329e1..a4dcba4a3b2 100644 --- a/crates/cli-support/src/js/closures.rs +++ b/crates/cli-support/src/js/closures.rs @@ -10,7 +10,7 @@ //! this works can be found in the code below. use crate::descriptor::Descriptor; -use crate::js::js2rust::Js2Rust; +use crate::js::js2rust::{ExportedShim, Js2Rust}; use crate::js::Context; use failure::Error; use std::collections::{BTreeMap, HashSet}; @@ -142,7 +142,7 @@ impl ClosureDescriptors { let table = input.module.tables.get_mut(table_id); let table = match &mut table.kind { walrus::TableKind::Function(f) => f, - walrus::TableKind::Anyref(_) => unreachable!(), + _ => unreachable!(), }; for idx in self.element_removal_list.iter().cloned() { log::trace!("delete element {}", idx); @@ -178,6 +178,7 @@ impl ClosureDescriptors { let closure = instr.descriptor.closure().unwrap(); + let mut shim = closure.shim_idx; let (js, _ts, _js_doc) = { let mut builder = Js2Rust::new("", input); builder.prelude("this.cnt++;"); @@ -192,9 +193,12 @@ impl ClosureDescriptors { builder.rust_argument("this.a").rust_argument("b"); } builder.finally("if (this.cnt-- == 1) d(this.a, b);"); - builder.process(&closure.function)?.finish("function", "f") + builder.process(&closure.function)?.finish( + "function", + "f", + ExportedShim::TableElement(&mut shim), + ) }; - input.expose_add_heap_object(); input.function_table_needed = true; let body = format!( "function(a, b, _ignored) {{ @@ -205,15 +209,19 @@ impl ClosureDescriptors { cb.cnt = 1; let real = cb.bind(cb); real.original = cb; - return addHeapObject(real); + return {}; }}", - closure.shim_idx, closure.dtor_idx, js, + shim, + closure.dtor_idx, + js, + input.add_heap_object("real"), ); input.export(&import_name, &body, None); - let id = input - .module - .add_import_func("__wbindgen_placeholder__", &import_name, ty); + 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, diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 4dc4e8b24c0..22a62376e5c 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -44,6 +44,15 @@ pub struct Js2Rust<'a, 'b: 'a> { /// The string value here is the class that this should be a constructor /// for. constructor: Option, + + /// metadata for anyref transformations + anyref_args: Vec<(usize, bool)>, + ret_anyref: bool, +} + +pub enum ExportedShim<'a> { + Named(&'a str), + TableElement(&'a mut u32), } impl<'a, 'b> Js2Rust<'a, 'b> { @@ -59,6 +68,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ret_ty: String::new(), ret_expr: String::new(), constructor: None, + anyref_args: Vec::new(), + ret_anyref: false, } } @@ -193,13 +204,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> { if arg.is_anyref() { self.js_arguments.push((name.clone(), "any".to_string())); - self.cx.expose_add_heap_object(); - if optional { - self.cx.expose_is_like_none(); - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name,)); + if self.cx.config.anyref { + if optional { + self.cx.expose_add_to_anyref_table()?; + self.cx.expose_is_like_none(); + self.rust_arguments + .push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name)); + } else { + self.anyref_args.push((self.rust_arguments.len(), true)); + self.rust_arguments.push(name); + } } else { - self.rust_arguments.push(format!("addHeapObject({})", name)); + self.cx.expose_add_heap_object(); + if optional { + self.cx.expose_is_like_none(); + self.rust_arguments + .push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name)); + } else { + self.rust_arguments.push(format!("addHeapObject({})", name)); + } } return Ok(self); } @@ -383,14 +406,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> { if arg.is_ref_anyref() { self.js_arguments.push((name.clone(), "any".to_string())); - self.cx.expose_borrowed_objects(); - self.cx.expose_global_stack_pointer(); - // the "stack-ful" nature means that we're always popping from the - // stack, and make sure that we actually clear our reference to - // allow stale values to get GC'd - self.finally("heap[stack_pointer++] = undefined;"); - self.rust_arguments - .push(format!("addBorrowedObject({})", name)); + if self.cx.config.anyref { + self.anyref_args.push((self.rust_arguments.len(), false)); + self.rust_arguments.push(name); + } else { + // the "stack-ful" nature means that we're always popping from the + // stack, and make sure that we actually clear our reference to + // allow stale values to get GC'd + self.cx.expose_borrowed_objects(); + self.cx.expose_global_stack_pointer(); + self.finally("heap[stack_pointer++] = undefined;"); + self.rust_arguments + .push(format!("addBorrowedObject({})", name)); + } return Ok(self); } @@ -462,7 +490,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { if let Some(ty) = ty.vector_kind() { self.ret_ty = ty.js_ty().to_string(); - let f = self.cx.expose_get_vector_from_wasm(ty); + let f = self.cx.expose_get_vector_from_wasm(ty)?; self.cx.expose_global_argument_ptr()?; self.cx.expose_uint32_memory(); self.cx.require_internal_export("__wbindgen_free")?; @@ -494,8 +522,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> { // that `takeObject` will naturally pluck out `undefined`. if ty.is_anyref() { self.ret_ty = "any".to_string(); - self.cx.expose_take_object(); - self.ret_expr = format!("return takeObject(RET);"); + self.ret_expr = format!("return {};", self.cx.take_object("RET")); + self.ret_anyref = true; return Ok(self); } @@ -708,7 +736,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> { /// Returns two strings, the first of which is the JS expression for the /// generated function shim and the second is a TypeScript signature of the /// JS expression. - pub fn finish(&self, prefix: &str, invoc: &str) -> (String, String, String) { + pub fn finish( + &mut self, + prefix: &str, + invoc: &str, + exported_shim: ExportedShim, + ) -> (String, String, String) { let js_args = self .js_arguments .iter() @@ -754,6 +787,24 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ts.push_str(&self.ret_ty); } ts.push(';'); + + if self.ret_anyref || self.anyref_args.len() > 0 { + match exported_shim { + ExportedShim::Named(name) => { + self.cx + .anyref + .export_xform(name, &self.anyref_args, self.ret_anyref); + } + ExportedShim::TableElement(idx) => { + *idx = self.cx.anyref.table_element_xform( + *idx, + &self.anyref_args, + self.ret_anyref, + ); + } + } + } + (js, ts, self.js_doc_comments()) } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f77e4100c12..0a7fd60489b 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -7,7 +7,7 @@ use walrus::{MemoryId, Module}; use wasm_bindgen_wasm_interpreter::Interpreter; mod js2rust; -use self::js2rust::Js2Rust; +use self::js2rust::{ExportedShim, Js2Rust}; mod rust2js; use self::rust2js::Rust2Js; mod closures; @@ -52,6 +52,8 @@ pub struct Context<'a> { pub function_table_needed: bool, pub interpreter: &'a mut Interpreter, pub memory: MemoryId, + + pub anyref: wasm_bindgen_anyref_xform::Context, } #[derive(Default)] @@ -163,319 +165,366 @@ impl<'a> Context<'a> { } pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { - 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)); - } - ", - )) - })?; - - self.bind("__wbindgen_object_drop_ref", &|me| { - me.expose_drop_ref(); - Ok(String::from("function(i) { dropObject(i); }")) - })?; - self.bind("__wbindgen_string_new", &|me| { - me.expose_add_heap_object(); + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_string_new", + &[], + true, + ); me.expose_get_string_from_wasm(); - Ok(String::from( - " - function(p, l) { - return addHeapObject(getStringFromWasm(p, l)); - } - ", + Ok(format!( + "function(p, l) {{ return {}; }}", + me.add_heap_object("getStringFromWasm(p, l)") )) })?; self.bind("__wbindgen_number_new", &|me| { - me.expose_add_heap_object(); - Ok(String::from("function(i) { return addHeapObject(i); }")) + 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.expose_get_object(); + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_number_get", + &[(0, false)], + false, + ); me.expose_uint8_memory(); - Ok(String::from( + Ok(format!( " - function(n, invalid) { - let obj = getObject(n); + 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.expose_get_object(); - Ok(String::from( - " - function(idx) { - return getObject(idx) === null ? 1 : 0; - } - ", + 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.expose_get_object(); - Ok(String::from( - " - function(idx) { - return getObject(idx) === undefined ? 1 : 0; - } - ", + 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.expose_get_object(); - Ok(String::from( + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_boolean_get", + &[(0, false)], + false, + ); + Ok(format!( " - function(i) { - let v = getObject(i); - if (typeof(v) === 'boolean') { - return v ? 1 : 0; - } else { - return 2; - } - } + 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(); - me.expose_add_heap_object(); - Ok(String::from( - " - function(ptr, len) { - let a; - if (ptr === 0) { - a = Symbol(); - } else { - a = Symbol(getStringFromWasm(ptr, len)); - } - return addHeapObject(a); - } - ", + 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.expose_get_object(); - Ok(String::from( - " - function(i) { - return typeof(getObject(i)) === 'symbol' ? 1 : 0; - } - ", + 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.expose_get_object(); - Ok(String::from( + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_is_object", + &[(0, false)], + false, + ); + Ok(format!( " - function(i) { - const val = getObject(i); + function(i) {{ + const val = {}; return typeof(val) === 'object' && val !== null ? 1 : 0; - } - ", + }}", + me.get_object("i"), )) })?; self.bind("__wbindgen_is_function", &|me| { - me.expose_get_object(); - Ok(String::from( - " - function(i) { - return typeof(getObject(i)) === 'function' ? 1 : 0; - } - ", + 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.expose_get_object(); - Ok(String::from( - " - function(i) { - return typeof(getObject(i)) === 'string' ? 1 : 0; - } - ", + 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_get_object(); me.expose_uint32_memory(); - Ok(String::from( + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_string_get", + &[(0, false)], + false, + ); + Ok(format!( " - function(i, len_ptr) { - let obj = getObject(i); + 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_debug_string", &|me| { me.expose_pass_string_to_wasm()?; - me.expose_get_object(); me.expose_uint32_memory(); - Ok(String::from( - " - function(i, len_ptr) { - const toString = Object.prototype.toString; - const 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})`; - } - } - 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; + + 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})`; } - // Test for built-in - const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val)); - let className; - if (builtInMatches.length > 1) { - className = builtInMatches[1]; + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); + return 'Function'; } - 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'; - } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debug_str(val[0]); } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; + for(let i = 1; i < length; i++) { + debug += ', ' + debug_str(val[i]); } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; - }; - const val = getObject(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; + } + "; + 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.expose_drop_ref(); - Ok(String::from( + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_cb_drop", + &[(0, true)], + false, + ); + Ok(format!( " - function(i) { - const obj = getObject(i).original; - dropObject(i); - if (obj.cnt-- == 1) { + 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| { - me.expose_drop_ref(); - Ok("dropObject".to_string()) + 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_add_heap_object(); me.expose_get_string_from_wasm(); - Ok(String::from( - " - function(ptr, len) { - return addHeapObject(JSON.parse(getStringFromWasm(ptr, len))); - } - ", - )) + 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.expose_get_object(); + me.anyref.import_xform( + "__wbindgen_placeholder__", + "__wbindgen_json_serialize", + &[(0, false)], + false, + ); me.expose_pass_string_to_wasm()?; me.expose_uint32_memory(); - Ok(String::from( + Ok(format!( " - function(idx, ptrptr) { - const ptr = passStringToWasm(JSON.stringify(getObject(idx))); + 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| { - me.expose_get_object(); - Ok(String::from( - " - function(a, b) { - return getObject(a) === getObject(b) ? 1 : 0; - } - ", + Ok(format!( + "function(a, b) {{ return {} === {} ? 1 : 0; }}", + me.get_object("a"), + me.get_object("b") )) })?; self.bind("__wbindgen_memory", &|me| { - me.expose_add_heap_object(); let mem = me.memory(); - Ok(format!("function() {{ return addHeapObject({}); }}", mem)) + Ok(format!( + "function() {{ return {}; }}", + me.add_heap_object(mem) + )) })?; self.bind("__wbindgen_module", &|me| { @@ -485,23 +534,60 @@ impl<'a> Context<'a> { --no-modules" ); } - me.expose_add_heap_object(); Ok(format!( - " - function() {{ - return addHeapObject(init.__wbindgen_wasm_module); - }} - ", + "function() {{ return {}; }}", + me.add_heap_object("init.__wbindgen_wasm_module") )) })?; self.bind("__wbindgen_rethrow", &|me| { - me.expose_take_object(); - Ok(String::from("function(idx) { throw takeObject(idx); }")) + Ok(format!( + "function(idx) {{ throw {}; }}", + me.take_object("idx") + )) })?; closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?; self.write_classes()?; + self.anyref.run(self.module)?; + + // 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); }")) + })?; + self.unexport_unused_internal_exports(); // Handle the `start` function, if one was specified. If we're in a @@ -529,9 +615,6 @@ impl<'a> Context<'a> { if self.config.emit_start { self.add_start_function()?; has_start_function = self.unstart_start_function(); - if has_start_function && !self.config.no_modules { - self.inject_start_shim(); - } } self.export_table()?; @@ -703,6 +786,14 @@ impl<'a> Context<'a> { }, ) } else { + // In the "we're pretending to be an ES module use case if we've got + // a start function then we use an injected shim to actually execute + // the real start function on the next tick of the microtask queue + // (explained above) + if has_start_function { + self.inject_start_shim(); + } + let import_wasm = if self.globals.len() == 0 { String::new() } else if self.use_node_require() { @@ -804,21 +895,13 @@ 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) { - self.expose_add_heap_object(); wrap_needed = true; - - self.export( - &new_name, - &format!( - " - function(ptr) {{ - return addHeapObject({}.__wrap(ptr)); - }} - ", - name - ), - None, - ); + 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 { @@ -924,7 +1007,10 @@ impl<'a> Context<'a> { math_imports.push((renamed_import.clone(), format!("function{}", expr))); }; - // FIXME(#32): try to not use function shims + // 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); }"), @@ -1007,6 +1093,7 @@ impl<'a> Context<'a> { if !self.should_write_global("heap") { return; } + assert!(!self.config.anyref); self.global(&format!("const heap = new Array({});", INITIAL_HEAP_OFFSET)); self.global("heap.fill(undefined);"); self.global(&format!("heap.push({});", INITIAL_HEAP_VALUES.join(", "))); @@ -1133,21 +1220,40 @@ impl<'a> Context<'a> { } self.require_internal_export("__wbindgen_malloc")?; self.expose_uint32_memory(); - self.expose_add_heap_object(); - self.global( - " - function passArrayJsValueToWasm(array) { - const ptr = wasm.__wbindgen_malloc(array.length * 4); - const mem = getUint32Memory(); - for (let i = 0; i < array.length; i++) { - mem[ptr / 4 + i] = addHeapObject(array[i]); + if self.config.anyref { + // TODO: using `addToAnyrefTable` goes back and forth between wasm + // and JS a lot, we should have a bulk operation for this. + self.expose_add_to_anyref_table()?; + self.global( + " + function passArrayJsValueToWasm(array) { + const ptr = wasm.__wbindgen_malloc(array.length * 4); + const mem = getUint32Memory(); + for (let i = 0; i < array.length; i++) { + mem[ptr / 4 + i] = addToAnyrefTable(array[i]); + } + WASM_VECTOR_LEN = array.length; + return ptr; + } + ", + ); + } else { + self.expose_add_heap_object(); + self.global( + " + function passArrayJsValueToWasm(array) { + const ptr = wasm.__wbindgen_malloc(array.length * 4); + const mem = getUint32Memory(); + for (let i = 0; i < array.length; i++) { + mem[ptr / 4 + i] = addHeapObject(array[i]); + } + WASM_VECTOR_LEN = array.length; + return ptr; } - WASM_VECTOR_LEN = array.length; - return ptr; - } - ", - ); + ", + ); + } Ok(()) } @@ -1242,25 +1348,45 @@ impl<'a> Context<'a> { )); } - fn expose_get_array_js_value_from_wasm(&mut self) { + fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> { if !self.should_write_global("get_array_js_value_from_wasm") { - return; + return Ok(()); } self.expose_uint32_memory(); - self.expose_take_object(); - self.global( - " - function getArrayJsValueFromWasm(ptr, len) { - const mem = getUint32Memory(); - const slice = mem.subarray(ptr / 4, ptr / 4 + len); - const result = []; - for (let i = 0; i < slice.length; i++) { - result.push(takeObject(slice[i])); + if self.config.anyref { + self.expose_anyref_table(); + self.global( + " + function getArrayJsValueFromWasm(ptr, len) { + const mem = getUint32Memory(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) { + result.push(wasm.__wbg_anyref_table.get(slice[i])); + } + wasm.__wbindgen_drop_anyref_slice(ptr, len); + return result; } - return result; - } - ", - ); + ", + ); + self.require_internal_export("__wbindgen_drop_anyref_slice")?; + } else { + self.expose_take_object(); + self.global( + " + function getArrayJsValueFromWasm(ptr, len) { + const mem = getUint32Memory(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) { + result.push(takeObject(slice[i])); + } + return result; + } + ", + ); + } + Ok(()) } fn expose_get_array_i8_from_wasm(&mut self) { @@ -1553,21 +1679,36 @@ impl<'a> Context<'a> { )); } - fn expose_handle_error(&mut self) { + fn expose_handle_error(&mut self) -> Result<(), Error> { if !self.should_write_global("handle_error") { - return; + return Ok(()); } self.expose_uint32_memory(); - self.expose_add_heap_object(); - self.global( - " - function handleError(exnptr, e) { - const view = getUint32Memory(); - view[exnptr / 4] = 1; - view[exnptr / 4 + 1] = addHeapObject(e); - } - ", - ); + if self.config.anyref { + self.expose_add_to_anyref_table()?; + self.global( + " + function handleError(exnptr, e) { + const idx = addToAnyrefTable(e); + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = idx; + } + ", + ); + } else { + self.expose_add_heap_object(); + self.global( + " + function handleError(exnptr, e) { + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = addHeapObject(e); + } + ", + ); + } + Ok(()) } fn wasm_import_needed(&self, name: &str) -> bool { @@ -1615,8 +1756,8 @@ impl<'a> Context<'a> { Ok(s) } - fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> &'static str { - match ty { + 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(); "getStringFromWasm" @@ -1666,10 +1807,10 @@ impl<'a> Context<'a> { "getArrayF64FromWasm" } VectorKind::Anyref => { - self.expose_get_array_js_value_from_wasm(); + self.expose_get_array_js_value_from_wasm()?; "getArrayJsValueFromWasm" } - } + }) } fn expose_global_argument_ptr(&mut self) -> Result<(), Error> { @@ -2051,24 +2192,24 @@ impl<'a> Context<'a> { _ => bail!("export `{}` wasn't a function", start), }; - if let Some(prev) = self.module.start { - let prev = self.module.funcs.get(prev); - if let Some(prev) = &prev.name { - bail!( - "cannot flag `{}` as start function as `{}` is \ - already the start function", - start, - prev - ); + let prev_start = match self.module.start { + Some(f) => f, + None => { + self.module.start = Some(id); + return Ok(()); } - bail!( - "cannot flag `{}` as start function as another \ - function is already the start function", - start - ); - } + }; - self.module.start = Some(id); + // 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(()) } @@ -2098,8 +2239,66 @@ impl<'a> Context<'a> { let id = self.module .add_import_func("__wbindgen_placeholder__", "__wbindgen_defer_start", ty); + assert!(self.module.start.is_none()); self.module.start = Some(id); } + + fn expose_anyref_table(&mut self) { + assert!(self.config.anyref); + if !self.should_write_global("anyref_table") { + return; + } + self.module + .exports + .add("__wbg_anyref_table", self.anyref.anyref_table_id()); + } + + fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> { + assert!(self.config.anyref); + if !self.should_write_global("add_to_anyref_table") { + return Ok(()); + } + self.expose_anyref_table(); + self.require_internal_export("__wbindgen_anyref_table_alloc")?; + self.global( + " + function addToAnyrefTable(obj) { + const idx = wasm.__wbindgen_anyref_table_alloc(); + wasm.__wbg_anyref_table.set(idx, obj); + return idx; + } + ", + ); + + 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() + } else { + self.expose_take_object(); + format!("takeObject({})", expr) + } + } + + fn get_object(&mut self, expr: &str) -> String { + if self.config.anyref { + expr.to_string() + } else { + self.expose_get_object(); + format!("getObject({})", expr) + } + } } impl<'a, 'b> SubContext<'a, 'b> { @@ -2153,7 +2352,11 @@ impl<'a, 'b> SubContext<'a, 'b> { let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx) .process(descriptor.unwrap_function())? - .finish("function", &format!("wasm.{}", export.function.name)); + .finish( + "function", + &format!("wasm.{}", export.function.name), + ExportedShim::Named(&export.function.name), + ); self.cx.export( &export.function.name, &js, @@ -2205,7 +2408,11 @@ impl<'a, 'b> SubContext<'a, 'b> { None }) .process(descriptor.unwrap_function())? - .finish("", &format!("wasm.{}", wasm_name)); + .finish( + "", + &format!("wasm.{}", wasm_name), + ExportedShim::Named(&wasm_name), + ); let class = self .cx @@ -2276,19 +2483,11 @@ impl<'a, 'b> SubContext<'a, 'b> { // TODO: should support more types to import here let obj = self.import_name(info, &import.name)?; - self.cx.expose_add_heap_object(); - self.cx.export( - &import.shim, - &format!( - " - function() {{ - return addHeapObject({}); - }} - ", - obj - ), - None, - ); + 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); Ok(()) } @@ -2342,6 +2541,15 @@ impl<'a, 'b> SubContext<'a, 'b> { } = 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(()); } } @@ -2350,7 +2558,7 @@ impl<'a, 'b> SubContext<'a, 'b> { // 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)?; + let js = shim.finish(&target, &import.shim)?; shim.cx.export(&import.shim, &js, None); Ok(()) } @@ -2364,14 +2572,16 @@ impl<'a, 'b> SubContext<'a, 'b> { return Ok(()); } let name = self.import_name(info, &import.name)?; - self.cx.expose_get_object(); + self.cx.anyref.import_xform( + "__wbindgen_placeholder__", + &import.instanceof_shim, + &[(0, false)], + false, + ); let body = format!( - " - function(idx) {{ - return getObject(idx) instanceof {} ? 1 : 0; - }} - ", - name, + "function(idx) {{ return {} instanceof {} ? 1 : 0; }}", + self.cx.get_object("idx"), + name ); self.cx.export(&import.instanceof_shim, &body, None); Ok(()) @@ -2412,6 +2622,7 @@ impl<'a, 'b> SubContext<'a, 'b> { }; let set = { + let setter = ExportedShim::Named(&wasm_setter); let mut cx = Js2Rust::new(&field.name, self.cx); cx.method(true, false) .argument(&descriptor)? @@ -2422,12 +2633,13 @@ impl<'a, 'b> SubContext<'a, 'b> { field.name, &cx.js_arguments[0].1 )); - cx.finish("", &format!("wasm.{}", wasm_setter)).0 + 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(true, false) .ret(&descriptor)? - .finish("", &format!("wasm.{}", wasm_getter)); + .finish("", &format!("wasm.{}", wasm_getter), getter); if !dst.ends_with("\n") { dst.push_str("\n"); } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index a9a84d02714..b0824580f85 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,4 +1,5 @@ use crate::descriptor::{Descriptor, Function}; +use crate::js::js2rust::ExportedShim; use crate::js::{Context, ImportTarget, Js2Rust}; use failure::{bail, Error}; @@ -39,6 +40,11 @@ pub struct Rust2Js<'a, 'b: 'a> { /// Whether or not the last argument is a slice representing variadic arguments. variadic: bool, + + /// 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, } impl<'a, 'b> Rust2Js<'a, 'b> { @@ -55,6 +61,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> { catch: false, catch_and_rethrow: false, variadic: false, + anyref_args: Vec::new(), + ret_anyref: false, } } @@ -101,7 +109,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { if let Some(ty) = arg.vector_kind() { let abi2 = self.shim_argument(); - let f = self.cx.expose_get_vector_from_wasm(ty); + let f = self.cx.expose_get_vector_from_wasm(ty)?; self.prelude(&format!( "let v{0} = {prefix}{func}({0}, {1});", abi, @@ -141,12 +149,14 @@ impl<'a, 'b> Rust2Js<'a, 'b> { // No need to special case `optional` here because `takeObject` will // naturally work. if arg.is_anyref() { - self.cx.expose_take_object(); - self.js_arguments.push(format!("takeObject({})", abi)); + let arg = self.cx.take_object(&abi); + self.js_arguments.push(arg); + self.anyref_args.push((self.arg_idx - 1, true)); return Ok(()); } else if arg.is_ref_anyref() { - self.cx.expose_get_object(); - self.js_arguments.push(format!("getObject({})", abi)); + let arg = self.cx.get_object(&abi); + self.js_arguments.push(arg); + self.anyref_args.push((self.arg_idx - 1, false)); return Ok(()); } @@ -263,6 +273,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { if let Some((f, mutable)) = arg.stack_closure() { let arg2 = self.shim_argument(); + let mut shim = f.shim_idx; let (js, _ts, _js_doc) = { let mut builder = Js2Rust::new("", self.cx); if mutable { @@ -274,10 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } else { builder.rust_argument("this.a"); } - builder - .rust_argument("this.b") - .process(f)? - .finish("function", "this.f") + builder.rust_argument("this.b").process(f)?.finish( + "function", + "this.f", + ExportedShim::TableElement(&mut shim), + ) }; self.cx.function_table_needed = true; self.global_idx(); @@ -291,7 +303,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { abi, arg2, js = js, - idx = f.shim_idx, + idx = shim, )); self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi)); self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi)); @@ -349,16 +361,31 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return Ok(()); } if ty.is_anyref() { - self.cx.expose_add_heap_object(); - if optional { - self.cx.expose_is_like_none(); - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0 : addHeapObject(val); - " - .to_string(); + if self.cx.config.anyref { + if optional { + self.cx.expose_add_to_anyref_table()?; + self.cx.expose_is_like_none(); + self.ret_expr = " + const val = JS; + return isLikeNone(val) ? 0 : addToAnyrefTable(val); + " + .to_string(); + } else { + self.ret_anyref = true; + self.ret_expr = "return JS;".to_string() + } } else { - self.ret_expr = "return addHeapObject(JS);".to_string() + self.cx.expose_add_heap_object(); + if optional { + self.cx.expose_is_like_none(); + self.ret_expr = " + const val = JS; + return isLikeNone(val) ? 0 : addHeapObject(val); + " + .to_string(); + } else { + self.ret_expr = "return addHeapObject(JS);".to_string() + } } return Ok(()); } @@ -565,6 +592,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> { arg_idx: _, cx: _, global_idx: _, + anyref_args: _, + ret_anyref: _, } = self; !catch && @@ -581,7 +610,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { js_arguments == shim_arguments } - pub fn finish(&mut self, invoc: &ImportTarget) -> Result { + 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(", ")); @@ -596,7 +625,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { let variadic = self.variadic; let ret_expr = &self.ret_expr; - let js_arguments = &self.js_arguments; let handle_variadic = |invoc: &str, js_arguments: &[String]| { let ret = if variadic { let (last_arg, args) = match js_arguments.split_last() { @@ -617,6 +645,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Ok(ret) }; + let js_arguments = &self.js_arguments; let fixed = |desc: &str, class: &Option, amt: usize| { if variadic { bail!("{} cannot be variadic", desc); @@ -670,7 +699,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { }; if self.catch { - self.cx.expose_handle_error(); + self.cx.expose_handle_error()?; invoc = format!( "\ try {{\n\ @@ -712,6 +741,33 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret.push_str(&invoc); ret.push_str("\n}\n"); + + if self.ret_anyref || self.anyref_args.len() > 0 { + // Some return values go at the the beginning of the argument list + // (they force a return pointer). Handle that here by offsetting all + // our arg indices by one, but throw in some sanity checks for if + // this ever changes. + if let Some(start) = self.shim_arguments.get(0) { + if start == "ret" { + assert!(!self.ret_anyref); + if let Some(next) = self.shim_arguments.get(1) { + assert_eq!(next, "arg0"); + } + for (idx, _) in self.anyref_args.iter_mut() { + *idx += 1; + } + } else { + assert_eq!(start, "arg0"); + } + } + self.cx.anyref.import_xform( + "__wbindgen_placeholder__", + shim, + &self.anyref_args, + self.ret_anyref, + ); + } + Ok(ret) } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 06c44c1d17c..cc8061487af 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -35,6 +35,7 @@ pub struct Bindgen { // Experimental support for the wasm threads proposal, transforms the wasm // module to be "ready to be instantiated on any thread" threads: Option, + anyref: bool, } enum Input { @@ -62,6 +63,7 @@ impl Bindgen { emit_start: true, weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(), threads: threads_config(), + anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(), } } @@ -176,6 +178,22 @@ impl Bindgen { (module, stem) } }; + + // This isn't the hardest thing in the world too support but we + // basically don't know how to rationalize #[wasm_bindgen(start)] and + // the actual `start` function if present. Figure this out later if it + // comes up, but otherwise we should continue to be compatible with + // LLVM's output today. + // + // Note that start function handling in `js/mod.rs` will need to be + // updated as well, because `#[wasm_bindgen(start)]` is inserted *after* + // a module's start function, if any, because we assume start functions + // only show up when injected on behalf of wasm-bindgen's passes. + if module.start.is_some() { + bail!("wasm-bindgen is currently incompatible with modules that \ + already have a start function"); + } + let mut program_storage = Vec::new(); let programs = extract_programs(&mut module, &mut program_storage) .with_context(|_| "failed to extract wasm-bindgen custom sections")?; @@ -233,7 +251,10 @@ impl Bindgen { imported_statics: Default::default(), direct_imports: Default::default(), start: None, + anyref: Default::default(), }; + cx.anyref.enabled = self.anyref; + cx.anyref.prepare(cx.module)?; for program in programs.iter() { js::SubContext { program, diff --git a/crates/js-sys/tests/wasm/Reflect.rs b/crates/js-sys/tests/wasm/Reflect.rs index 23be554ac2e..c9243c3d17b 100644 --- a/crates/js-sys/tests/wasm/Reflect.rs +++ b/crates/js-sys/tests/wasm/Reflect.rs @@ -191,7 +191,10 @@ fn set_f64() { Reflect::set_f64(&a, 0.0, &JsValue::from_str("Bye!")).unwrap(); - assert_eq!(Reflect::get_f64(&a, 0.0).unwrap(), JsValue::from_str("Bye!")); + assert_eq!( + Reflect::get_f64(&a, 0.0).unwrap(), + JsValue::from_str("Bye!") + ); } #[wasm_bindgen_test] diff --git a/publish.rs b/publish.rs index 2c45b0745db..c983077b364 100644 --- a/publish.rs +++ b/publish.rs @@ -27,6 +27,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wasm-bindgen-wasm-interpreter", "wasm-bindgen-webidl", "wasm-bindgen-threads-xform", + "wasm-bindgen-anyref-xform", "wasm-bindgen-cli-support", "wasm-bindgen-cli", "wasm-bindgen", diff --git a/src/anyref.rs b/src/anyref.rs new file mode 100644 index 00000000000..5434a024352 --- /dev/null +++ b/src/anyref.rs @@ -0,0 +1,208 @@ +use std::slice; +use std::vec::Vec; +use std::ptr; +use std::alloc::{self, Layout}; +use std::mem; + +use JsValue; + +externs! { + #[link(wasm_import_module = "__wbindgen_anyref_xform__")] + extern "C" { + fn __wbindgen_anyref_table_grow(delta: usize) -> i32; + fn __wbindgen_anyref_table_set_null(idx: usize) -> (); + } +} + +pub struct Slab { + data: Vec, + head: usize, + base: usize, +} + +impl Slab { + fn new() -> Slab { + Slab { + data: Vec::new(), + head: 0, + base: 0, + } + } + + fn alloc(&mut self) -> usize { + let ret = self.head; + if ret == self.data.len() { + if self.data.len() == self.data.capacity() { + let extra = 128; + let r = unsafe { + __wbindgen_anyref_table_grow(extra) + }; + if r == -1 { + internal_error("table grow failure") + } + if self.base == 0 { + self.base = r as usize + (super::JSIDX_RESERVED as usize); + } else if self.base + self.data.len() != r as usize { + internal_error("someone else allocated table entires?") + } + + // poor man's `try_reserve_exact` until that's stable + unsafe { + let new_cap = self.data.capacity() + extra; + let size = mem::size_of::() * new_cap; + let align = mem::align_of::(); + let layout = match Layout::from_size_align(size, align) { + Ok(l) => l, + Err(_) => internal_error("size/align layout failure"), + }; + let ptr = alloc::alloc(layout) as *mut usize; + if ptr.is_null() { + internal_error("allocation failure"); + } + ptr::copy_nonoverlapping( + self.data.as_ptr(), + ptr, + self.data.len(), + ); + let new_vec = Vec::from_raw_parts( + ptr, + self.data.len(), + new_cap, + ); + let mut old = mem::replace(&mut self.data, new_vec); + old.set_len(0); + } + } + + // custom condition to ensure `push` below doesn't call `reserve` in + // optimized builds which pulls in lots of panic infrastructure + if self.data.len() >= self.data.capacity() { + internal_error("push should be infallible now") + } + self.data.push(ret + 1); + } + + // usage of `get_mut` thwarts panicking infrastructure in optimized + // builds + match self.data.get_mut(ret) { + Some(slot) => self.head = *slot, + None => internal_error("ret out of bounds"), + } + ret + self.base + } + + fn dealloc(&mut self, slot: usize) { + if slot < self.base { + internal_error("free reserved slot"); + } + let slot = slot - self.base; + + // usage of `get_mut` thwarts panicking infrastructure in optimized + // builds + match self.data.get_mut(slot) { + Some(ptr) => { + *ptr = self.head; + self.head = slot; + } + None => internal_error("slot out of bounds"), + } + } +} + +fn internal_error(msg: &str) -> ! { + let msg = if cfg!(debug_assertions) { msg } else { "" }; + super::throw_str(msg) +} + +// Whoa, there's two `tl` modules here! That's currently intention, but for sort +// of a weird reason. The table here is fundamentally thread local, so we want +// to use the `thread_local!` macro. The implementation of thread locals (as of +// the time of this writing) generates a lot of code as it pulls in panic paths +// in libstd (even when using `try_with`). There's a patch to fix that +// (rust-lang/rust#55518), but in the meantime the stable/beta channels produce +// a lot of code. +// +// Matters are made worse here because this code is almost never used (it's only +// here for an unstable feature). If we were to have panics here, though, then +// we couldn't effectively gc away the panic infrastructure, meaning this unused +// infrastructure would show up in binaries! That's a no-no for wasm-bindgen. +// +// In the meantime, if the atomics feature is turned on (which it never is by +// default) then we use `thread_local!`, otherwise we use a home-grown +// implementation that will be replaced once #55518 lands on stable. +#[cfg(target_feature = "atomics")] +mod tl { + use std::*; // hack to get `thread_local!` to work + use super::Slab; + use std::cell::Cell; + + thread_local!(pub static HEAP_SLAB: Cell = Cell::new(Slab::new())); +} + +#[cfg(not(target_feature = "atomics"))] +mod tl { + use std::alloc::{self, Layout}; + use std::cell::Cell; + use std::ptr; + use super::Slab; + + pub struct HeapSlab; + pub static HEAP_SLAB: HeapSlab = HeapSlab; + static mut SLOT: *mut Cell = 0 as *mut Cell; + + impl HeapSlab { + pub fn try_with(&self, f: impl FnOnce(&Cell) -> R) -> Result { + unsafe { + if SLOT.is_null() { + let ptr = alloc::alloc(Layout::new::>()); + if ptr.is_null() { + super::internal_error("allocation failure"); + } + let ptr = ptr as *mut Cell; + ptr::write(ptr, Cell::new(Slab::new())); + SLOT = ptr; + } + Ok(f(&*SLOT)) + } + } + } +} + +#[no_mangle] +pub extern fn __wbindgen_anyref_table_alloc() -> usize { + tl::HEAP_SLAB.try_with(|slot| { + let mut slab = slot.replace(Slab::new()); + let ret = slab.alloc(); + slot.replace(slab); + ret + }).unwrap_or_else(|_| internal_error("tls access failure")) +} + +#[no_mangle] +pub extern fn __wbindgen_anyref_table_dealloc(idx: usize) { + if idx < super::JSIDX_RESERVED as usize { + return + } + // clear this value from the table so while the table slot is un-allocated + // we don't keep around a strong reference to a potentially large object + unsafe { + __wbindgen_anyref_table_set_null(idx); + } + tl::HEAP_SLAB.try_with(|slot| { + let mut slab = slot.replace(Slab::new()); + slab.dealloc(idx); + slot.replace(slab); + }).unwrap_or_else(|_| internal_error("tls access failure")) +} + +#[no_mangle] +pub unsafe extern fn __wbindgen_drop_anyref_slice(ptr: *mut JsValue, len: usize) { + for slot in slice::from_raw_parts_mut(ptr, len) { + ptr::drop_in_place(slot); + } +} + +// see comment in module above this in `link_mem_intrinsics` +#[inline(never)] +pub fn link_intrinsics() { +} diff --git a/src/lib.rs b/src/lib.rs index 8335449521a..14a22b8d999 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,24 @@ macro_rules! if_std { )*) } +macro_rules! externs { + ($(#[$attr:meta])* extern "C" { $(fn $name:ident($($args:tt)*) -> $ret:ty;)* }) => ( + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + $(#[$attr])* + extern "C" { + $(fn $name($($args)*) -> $ret;)* + } + + $( + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + #[allow(unused_variables)] + unsafe extern fn $name($($args)*) -> $ret { + panic!("function not implemented on non-wasm32 targets") + } + )* + ) +} + /// A module which is typically glob imported from: /// /// ``` @@ -57,6 +75,7 @@ if_std! { extern crate std; use std::prelude::v1::*; pub mod closure; + mod anyref; } /// Representation of an object owned by JS. @@ -462,58 +481,44 @@ macro_rules! numbers { numbers! { i8 u8 i16 u16 i32 u32 f32 f64 } -macro_rules! externs { - ($(fn $name:ident($($args:tt)*) -> $ret:ty;)*) => ( - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - #[link(wasm_import_module = "__wbindgen_placeholder__")] - extern "C" { - $(fn $name($($args)*) -> $ret;)* - } - - $( - #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] - #[allow(unused_variables)] - unsafe extern "C" fn $name($($args)*) -> $ret { - panic!("function not implemented on non-wasm32 targets") - } - )* - ) -} - externs! { - fn __wbindgen_object_clone_ref(idx: u32) -> u32; - fn __wbindgen_object_drop_ref(idx: u32) -> (); - fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; - fn __wbindgen_number_new(f: f64) -> u32; - fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; - fn __wbindgen_is_null(idx: u32) -> u32; - fn __wbindgen_is_undefined(idx: u32) -> u32; - fn __wbindgen_boolean_get(idx: u32) -> u32; - fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32; - fn __wbindgen_is_symbol(idx: u32) -> u32; - fn __wbindgen_is_object(idx: u32) -> u32; - fn __wbindgen_is_function(idx: u32) -> u32; - fn __wbindgen_is_string(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_throw(a: *const u8, b: usize) -> !; - fn __wbindgen_rethrow(a: u32) -> !; - - fn __wbindgen_cb_drop(idx: u32) -> u32; - fn __wbindgen_cb_forget(idx: u32) -> (); - - fn __wbindgen_describe(v: u32) -> (); - 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_jsval_eq(a: u32, b: u32) -> u32; - - fn __wbindgen_memory() -> u32; - fn __wbindgen_module() -> u32; + #[link(wasm_import_module = "__wbindgen_placeholder__")] + extern "C" { + fn __wbindgen_object_clone_ref(idx: u32) -> u32; + fn __wbindgen_object_drop_ref(idx: u32) -> (); + fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_number_new(f: f64) -> u32; + fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; + fn __wbindgen_is_null(idx: u32) -> u32; + fn __wbindgen_is_undefined(idx: u32) -> u32; + fn __wbindgen_boolean_get(idx: u32) -> u32; + fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32; + fn __wbindgen_is_symbol(idx: u32) -> u32; + fn __wbindgen_is_object(idx: u32) -> u32; + fn __wbindgen_is_function(idx: u32) -> u32; + fn __wbindgen_is_string(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_throw(a: *const u8, b: usize) -> !; + fn __wbindgen_rethrow(a: u32) -> !; + + fn __wbindgen_cb_drop(idx: u32) -> u32; + fn __wbindgen_cb_forget(idx: u32) -> (); + + fn __wbindgen_describe(v: u32) -> (); + 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_jsval_eq(a: u32, b: u32) -> u32; + + fn __wbindgen_memory() -> u32; + fn __wbindgen_module() -> u32; + } } impl Clone for JsValue { + #[inline] fn clone(&self) -> JsValue { unsafe { let idx = __wbindgen_object_clone_ref(self.idx); @@ -973,7 +978,9 @@ pub mod __rt { /// in the object file and link the intrinsics. /// /// Ideas for how to improve this are most welcome! - pub fn link_mem_intrinsics() {} + pub fn link_mem_intrinsics() { + ::anyref::link_intrinsics(); + } } /// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`