From 8855ecfde43ce6416fd1b94d995a2615cd0cdd47 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 18 Oct 2018 08:43:36 -0700 Subject: [PATCH] Add experimental support for the `anyref` type This commit adds experimental support to `wasm-bindgen` to emit and leverage the `anyref` native wasm type. This native type is still in a proposal status (the reference-types proposal). The intention of `anyref` is to be able to directly hold JS values in wasm and pass the to imported functions, namely to empower eventual host bindings (now renamed WebIDL bindings) integration where we can skip JS shims altogether for many imports. This commit doesn't actually affect wasm-bindgen's behavior at all as-is, but rather this support requires an opt-in env var to be configured. Once the support is stable in browsers it's intended that this will add a CLI switch for turning on this support, eventually defaulting it to `true` in the far future. The basic strategy here is to take the `stack` and `slab` globals in the generated JS glue and move them into wasm using a table. This new table in wasm is managed at the fringes via injected shims. At `wasm-bindgen`-time the CLI will rewrite exports and imports with shims that actually use `anyref` if needed, performing loads/stores inside the wasm module instead of externally in the wasm module. This should provide a boost over what we have today, but it's not a fantastic strategy long term. We have a more grand vision for `anyref` being a first-class type in the language, but that's on a much longer horizon and this is currently thought to be the best we can do in terms of integration in the near future. The stack/heap JS tables are combined into one wasm table. The stack starts at the end of the table and grows down with a stack pointer (also injected). The heap starts at the end and grows up (state managed in linear memory). The anyref transformation here will hook up various intrinsics in wasm-bindgen to the runtime functionality if the anyref supoprt is enabled. The main tricky treatment here was applied to closures, where we need JS to use a different function pointer than the one Rust gives it to use a JS function pointer empowered with anyref. This works by switching up a bit how descriptors work, embedding the shims to call inside descriptors rather than communicated at runtime. This means that we're accessing constant values in the generated JS and we can just update the constant value accessed. --- Cargo.toml | 1 + crates/anyref-xform/Cargo.toml | 15 + crates/anyref-xform/src/lib.rs | 922 ++++++++++++++++++ crates/cli-support/Cargo.toml | 2 + crates/cli-support/src/js/closures.rs | 18 +- crates/cli-support/src/js/js2rust.rs | 81 +- crates/cli-support/src/js/mod.rs | 697 ++++++++----- crates/cli-support/src/js/rust2js.rs | 148 ++- crates/cli-support/src/lib.rs | 13 +- .../bin/wasm-bindgen-test-runner/index.html | 24 +- crates/gc/src/lib.rs | 30 +- crates/wasm-utils/Cargo.toml | 14 + .../wasm_utils.rs => wasm-utils/src/lib.rs} | 2 + publish.rs | 2 + src/anyref.rs | 203 ++++ src/lib.rs | 119 +-- 16 files changed, 1877 insertions(+), 414 deletions(-) create mode 100644 crates/anyref-xform/Cargo.toml create mode 100644 crates/anyref-xform/src/lib.rs create mode 100644 crates/wasm-utils/Cargo.toml rename crates/{cli-support/src/wasm_utils.rs => wasm-utils/src/lib.rs} (99%) create mode 100644 src/anyref.rs diff --git a/Cargo.toml b/Cargo.toml index bd6fee202b61..3fe226788857 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' } +parity-wasm = { git = 'https://github.com/alexcrichton/parity-wasm', branch = 'reference-types' } diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml new file mode 100644 index 000000000000..f03b48eb71ea --- /dev/null +++ b/crates/anyref-xform/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wasm-bindgen-anyref-xform" +version = "0.2.28" +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 +""" + +[dependencies] +parity-wasm = "0.35" +wasm-bindgen-wasm-utils = { path = '../wasm-utils', version = '=0.2.28' } diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs new file mode 100644 index 000000000000..299df2abe669 --- /dev/null +++ b/crates/anyref-xform/src/lib.rs @@ -0,0 +1,922 @@ +//! 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. + +extern crate parity_wasm; +extern crate wasm_bindgen_wasm_utils as wasm_utils; + +use std::collections::{BTreeMap, HashMap}; + +use parity_wasm::elements::*; +use wasm_utils::Remap; + +// 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, + + // A map of function index to an instruction, used to replace calls to one + // function with another (or another native instruction). This is + // intermediate state during the pass. + call_map: HashMap>, + + // Indices of items that we have injected or found. This state is maintained + // during the pass execution. + table: Option, + heap_alloc: Option, + heap_dealloc: Option, + stack_pointer: Option, + + // When wrapping closures with new shims, this is the index of the next + // table entry that we'll be handing out. + next_element: u32, + + // Whether or not the transformation will actually be run at the end + pub enabled: bool, +} + +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, +} + +#[derive(Default)] +struct Sections<'a> { + codes: Option<&'a mut CodeSection>, + types: Option<&'a mut TypeSection>, + exports: Option<&'a mut ExportSection>, + functions: Option<&'a mut FunctionSection>, + imports: Option<&'a mut ImportSection>, + function_names: Option<&'a mut FunctionNameSection>, + tables: Option<&'a mut TableSection>, + globals: Option<&'a mut GlobalSection>, + elements: Option<&'a mut ElementSection>, + start: Option<&'a mut u32>, + imported_functions: u32, +} + +struct Shim { + new_func_idx: u32, + new_type_idx: u32, +} + +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) { + if !self.enabled { + return + } + let mut sections = Sections::from(module); + if let Some(s) = &mut sections.elements { + for entry in s.entries() { + let expr = match entry.offset() { + Some(i) => i, + None => continue, + }; + let start = match expr.code().get(0) { + Some(Instruction::I32Const(i)) => *i as u32, + _ => continue, + }; + let end = start + entry.members().len() as u32; + if end > self.next_element { + self.next_element = end; + } + } + } + + // Add in anyref tables/globals so we can use them in other various + // passes + self.inject_tables(&mut sections); + self.inject_globals(&mut sections); + } + + /// 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(idx, (ret, 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_idx(&self) -> u32 { + self.table.unwrap() + } + + pub fn run(&mut self, module: &mut Module) { + if !self.enabled { + return + } + self.xform(module).remap_module(module) + } + + fn xform(&mut self, module: &mut Module) -> Remap u32> { + // Sort of a hack for now, but ensure that these two sections are + // present unconditionally as we're going to be injecting items into + // them later. + self.ensure_table_section(module); + self.ensure_global_section(module); + self.ensure_start_section(module); + + // Probe for all the sections and store them all in a struct with fields + // so we can take disjoint borrows on each struct field. + let mut sections = Sections::from(module); + + // Detect all the various intrinsics and such. This will also along the + // way inject an intrinsic for cloning an anyref. + self.find_intrinsics(&mut sections); + + // And with all that data prepared now actually perform transformations + // of imports, exports, and function pointers. + let nbodies = sections.codes.as_ref().map(|s| s.bodies().len()).unwrap_or(0); + self.process_imports(&mut sections); + for m in self.imports.values() { + assert!(m.is_empty()); + } + self.process_exports(&mut sections); + assert!(self.exports.is_empty()); + self.process_elements(&mut sections); + assert!(self.elements.is_empty()); + + // Perform all instruction transformations to rewrite calls between + // functions and make sure everything is still hooked up right. + self.rewrite_calls(&mut sections, nbodies); + + // Inject initialization routine to set up default slots in the table + // (things like null/undefined/true/false) + self.inject_initialization(&mut sections) + } + + fn ensure_table_section(&mut self, module: &mut Module) { + let mut pos = None; + // See this URL for section orderings: + // + // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure + for i in 0..module.sections().len() { + match &mut module.sections_mut()[i] { + Section::Type(_) | + Section::Import(_) | + Section::Function(_) => continue, + Section::Table(_) => return, + _ => {} + } + pos = Some(i); + break + } + let empty = TableSection::with_entries(Vec::new()); + let section = Section::Table(empty); + let len = module.sections().len(); + let pos = pos.unwrap_or_else(|| len - 1); + module.sections_mut().insert(pos, section); + } + + fn ensure_global_section(&mut self, module: &mut Module) { + let mut pos = None; + // See this URL for section orderings: + // + // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure + for i in 0..module.sections().len() { + match &mut module.sections_mut()[i] { + Section::Type(_) | + Section::Import(_) | + Section::Function(_) | + Section::Table(_) | + Section::Memory(_) => continue, + Section::Global(_) => return, + _ => {} + } + pos = Some(i); + break + } + let empty = GlobalSection::with_entries(Vec::new()); + let section = Section::Global(empty); + let len = module.sections().len(); + let pos = pos.unwrap_or_else(|| len - 1); + module.sections_mut().insert(pos, section); + } + + fn ensure_start_section(&mut self, module: &mut Module) { + let mut pos = None; + // See this URL for section orderings: + // + // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure + for i in 0..module.sections().len() { + match &mut module.sections_mut()[i] { + Section::Type(_) | + Section::Import(_) | + Section::Function(_) | + Section::Table(_) | + Section::Memory(_) | + Section::Global(_) | + Section::Export(_) => continue, + Section::Start(_) => return, + _ => {} + } + pos = Some(i); + break + } + let section = Section::Start(u32::max_value()); + let len = module.sections().len(); + let pos = pos.unwrap_or_else(|| len - 1); + module.sections_mut().insert(pos, section); + } + + /// Inject an `anyref` table to use for the stack and the heap. + fn inject_tables(&mut self, sections: &mut Sections) { + let mut offset = 0; + if let Some(imports) = &mut sections.imports { + for i in imports.entries() { + match i.external() { + External::Table(_) => offset += 1, + _ => {}, + } + } + } + let tables = sections.tables.as_mut().unwrap(); + + self.table = Some(offset + tables.entries().len() as u32); + let tablety = TableType::new(TableElementType::AnyRef, DEFAULT_MIN, None); + tables.entries_mut().push(tablety); + } + + /// Inject a stack pointer for the anyref table. This is used to know where + /// to store borrowed `anyref` values (aka `&JsValue`). + fn inject_globals(&mut self, sections: &mut Sections) { + let mut offset = 0; + if let Some(imports) = &mut sections.imports { + offset = imports.globals() as u32; + } + let globals = sections.globals.as_mut().unwrap(); + + self.stack_pointer = Some(offset + globals.entries().len() as u32); + let global = GlobalEntry::new( + GlobalType::new(ValueType::I32, true), + InitExpr::new(vec![ + Instruction::I32Const(DEFAULT_MIN as i32), + Instruction::End, + ]), + ); + globals.entries_mut().push(global); + } + + fn find_intrinsics(&mut self, sections: &mut Sections) { + // Find exports of some intrinsics which we only need for a runtime + // implementation. + for entry in sections.exports.as_ref().unwrap().entries() { + let f = match entry.internal() { + Internal::Function(f) => *f, + _ => continue, + }; + match entry.field() { + "__wbindgen_anyref_table_alloc" => { + self.heap_alloc = Some(f); + } + "__wbindgen_anyref_table_dealloc" => { + self.heap_dealloc = Some(f); + } + _ => {}, + } + } + + // inject a shim for clone ref in case it's needed below + let clone_shim = self.inject_clone_ref_shim(sections); + + // Build up a map of various imported intrinsics to wire them up to + // different implementations or different functions. + let imports = sections.imports.as_ref().unwrap(); + let iter = imports.entries() + .iter() + .filter(|f| { + match f.external() { + External::Function(_) => true, + _ => false, + } + }) + .enumerate(); + for (i, entry) in iter { + let i = i as u32; + if entry.module() == "__wbindgen_anyref_xform__" { + match entry.field() { + "__wbindgen_anyref_table_grow" => { + let null = Instruction::RefNull; + let grow = Instruction::TableGrow(self.table.unwrap()); + self.call_map.insert(i, vec![null, grow]); + } + n => panic!("unkonwn intrinsic: {}", n), + } + } else if entry.module() == "__wbindgen_placeholder__" { + match entry.field() { + "__wbindgen_object_drop_ref" => { + let drop = self.heap_dealloc.unwrap(); + self.call_map.insert(i, vec![Instruction::Call(drop)]); + } + "__wbindgen_object_clone_ref" => { + self.call_map.insert(i, vec![Instruction::Call(clone_shim)]); + } + _ => {} + } + } + } + } + + fn inject_clone_ref_shim(&mut self, sections: &mut Sections) -> u32 { + let types = sections.types.as_mut().unwrap(); + let codes = sections.codes.as_mut().unwrap(); + let functions = sections.functions.as_mut().unwrap(); + let function_names = sections.function_names.as_mut().unwrap(); + + // (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 code = Vec::new(); + code.push(Instruction::Call(self.heap_alloc.unwrap())); + code.push(Instruction::TeeLocal(1)); + code.push(Instruction::GetLocal(0)); + code.push(Instruction::TableGet(self.table.unwrap())); + code.push(Instruction::TableSet(self.table.unwrap())); + code.push(Instruction::GetLocal(1)); + code.push(Instruction::End); + + let ty = FunctionType::new(vec![ValueType::I32], Some(ValueType::I32)); + let type_idx = types.types().len() as u32; + types.types_mut().push(Type::Function(ty)); + functions.entries_mut().push(Func::new(type_idx)); + + let instructions = Instructions::new(code); + let locals = vec![Local::new(1, ValueType::I32)]; + codes.bodies_mut().push(FuncBody::new(locals, instructions)); + + let idx = sections.imported_functions + (codes.bodies().len() as u32 - 1); + function_names + .names_mut() + .insert(idx, "__wbindgen_object_clone_ref".to_string()); + return idx + } + + fn process_imports(&mut self, sections: &mut Sections) { + let imports = match &mut sections.imports { + Some(s) => s, + None => return, + }; + let types = sections.types.as_mut().unwrap(); + let functions = sections.functions.as_mut().unwrap(); + let codes = sections.codes.as_mut().unwrap(); + let function_names = sections.function_names.as_mut().unwrap(); + + let iter = imports.entries_mut() + .iter_mut() + .filter(|i| { + match i.external() { + External::Function(_) => true, + _ => false, + } + }) + .enumerate(); + for (i, entry) in iter { + let i = i as u32; + let import = { + let entries = match self.imports.get_mut(entry.module()) { + Some(s) => s, + None => continue, + }; + match entries.remove(entry.field()) { + Some(s) => s, + None => continue, + } + }; + let type_idx = match entry.external_mut() { + External::Function(f) => f, + _ => continue, + }; + + let shim = self.append_shim( + i, + *type_idx, + import, + types, + functions, + codes, + function_names, + sections.imported_functions, + ); + *type_idx = shim.new_type_idx; + } + } + + fn process_exports(&mut self, sections: &mut Sections) { + let exports = match &mut sections.exports { + Some(s) => s, + None => return, + }; + let types = sections.types.as_mut().unwrap(); + let codes = sections.codes.as_mut().unwrap(); + let functions = sections.functions.as_mut().unwrap(); + let function_names = sections.function_names.as_mut().unwrap(); + + for entry in exports.entries_mut() { + let mut export = match self.exports.remove(entry.field()) { + Some(s) => s, + None => continue, + }; + let i = match entry.internal_mut() { + Internal::Function(f) => f, + _ => unreachable!(), + }; + let code_idx = *i - sections.imported_functions; + let type_idx = functions.entries_mut()[code_idx as usize].type_ref(); + let shim = self.append_shim( + *i, + type_idx, + export, + types, + functions, + codes, + function_names, + sections.imported_functions, + ); + *i = shim.new_func_idx; + } + } + + fn process_elements(&mut self, sections: &mut Sections) { + let elements = match &mut sections.elements { + Some(s) => s, + None => return, + }; + let types = sections.types.as_mut().unwrap(); + let codes = sections.codes.as_mut().unwrap(); + let functions = sections.functions.as_mut().unwrap(); + let function_names = sections.function_names.as_mut().unwrap(); + + let mut processed = Vec::new(); + + let mut max = 0; + let mut maxi = 0; + for (i, entry) in elements.entries().iter().enumerate() { + let start = { + let expr = match entry.offset() { + Some(i) => i, + None => continue, + }; + match expr.code().get(0) { + Some(Instruction::I32Const(i)) => *i as u32, + _ => continue, + } + }; + let end = entry.members().len() as u32 + start; + for (idx, _function) in self.elements.range(start..end) { + processed.push((*idx, entry.members()[(idx - start) as usize])); + } + + // Keep track of whatever segment maps to the end of the table, as + // we'll be appending our generated shims to it. + if end > max { + max = end; + maxi = i; + } + } + + // Make sure that we add shims in the same order that we were originally + // called with `table_element_xform` as we handed out table element + // indices in consecutive increasing order. + processed.sort_by_key(|(i, _)| self.elements[i].0); + + // Create shims for all our functions and append them all to the segment + // which places elements at the end. + let max_segment = &mut elements.entries_mut()[maxi]; + let new_shims = processed.len() as u32; + for (i, target) in processed { + let (_, function) = self.elements.remove(&i).unwrap(); + assert!(target >= sections.imported_functions); + let code_idx = target - sections.imported_functions; + let type_idx = functions.entries_mut()[code_idx as usize].type_ref(); + let shim = self.append_shim( + target, + type_idx, + function, + types, + functions, + codes, + function_names, + sections.imported_functions, + ); + max_segment.members_mut().push(shim.new_func_idx); + } + + // and finally update the table limits to ensure it can accomodate all + // of the new shims we just put in it. + // + // Assume that table 0 is the function pointer table. + let update_limits = |table: &mut TableType| { + assert_eq!(table.elem_type(), TableElementType::AnyFunc); + *table = TableType::new( + table.elem_type(), + table.limits().initial() + new_shims, + table.limits().maximum().map(|i| i + new_shims), + ); + }; + if let Some(imports) = &mut sections.imports { + for i in imports.entries_mut() { + if let External::Table(table) = i.external_mut() { + update_limits(table); + return + } + } + } + let tables = sections.tables.as_mut().unwrap(); + update_limits(&mut tables.entries_mut()[0]); + } + + fn append_shim( + &mut self, + shim_target: u32, + type_idx: u32, + mut func: Function, + types: &mut TypeSection, + functions: &mut FunctionSection, + codes: &mut CodeSection, + function_names: &mut FunctionNameSection, + imported_functions: u32, + ) -> Shim { + let is_export = shim_target >= imported_functions; + let new_func_idx = imported_functions + (codes.bodies().len() as u32); + + // If this is an imported function then the module can't call this + // function directly (as it'll have `anyref` in its signature now). + // Instead change all invocations of it to invoke the shim we're about + // to generate. + if !is_export { + self.call_map.insert(shim_target, vec![Instruction::Call(new_func_idx)]); + } + + let old_ty = { + let current_ty = &types.types()[type_idx as usize]; + let fty = match current_ty { + Type::Function(f) => f, + }; + fty.clone() + }; + + // 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 params = Vec::new(); + let mut param_convert = Vec::new(); + let mut anyref_stack = 0; + + for (i, old_ty) in old_ty.params().iter().enumerate() { + let is_owned = func.args.remove(&i); + let new_ty = is_owned.map(|_which| ValueType::AnyRef) + .unwrap_or(old_ty.clone()); + params.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 mut code = Vec::new(); + let table = self.table.unwrap(); + let stack_pointer = self.stack_pointer.unwrap(); + let heap_alloc = self.heap_alloc.unwrap(); + let heap_dealloc = self.heap_dealloc.unwrap(); + + // Unconditionally allocate some locals which get cleaned up in later + // gc passes if we don't actually end up using them. + let stack_idx = params.len() as u32; + let scratch_idx = params.len() as u32 + 1; + let scratch_anyref_idx = params.len() as u32 + 2; + + // Update our stack pointer if there's any borrowed anyref objects. + if anyref_stack > 0 { + code.push(Instruction::GetGlobal(stack_pointer)); + code.push(Instruction::I32Const(anyref_stack)); + code.push(Instruction::I32Sub); + code.push(Instruction::TeeLocal(stack_idx)); + code.push(Instruction::SetGlobal(stack_pointer)); + } + let mut next_stack_offset = 0; + + for (i, convert) in param_convert.iter().enumerate() { + let i = i as u32; + match *convert { + Convert::None => { + code.push(Instruction::GetLocal(i)); + } + Convert::Load { owned: true } => { + // load the anyref onto the stack, then afterwards + // deallocate our index, leaving the anyref on the stack. + code.push(Instruction::GetLocal(i)); + code.push(Instruction::TableGet(table)); + code.push(Instruction::GetLocal(i)); + code.push(Instruction::Call(heap_dealloc)); + } + Convert::Load { owned: false } => { + code.push(Instruction::GetLocal(i)); + code.push(Instruction::TableGet(table)); + } + Convert::Store { owned: true } => { + // Allocate space for the anyref, store it, and then leave + // the index of the allocated anyref on the stack. + code.push(Instruction::Call(heap_alloc)); + code.push(Instruction::TeeLocal(scratch_idx)); + code.push(Instruction::GetLocal(i)); + code.push(Instruction::TableSet(table)); + code.push(Instruction::GetLocal(scratch_idx)); + } + Convert::Store { owned: false } => { + // Store an anyref at an offset from our function's stack + // pointer frame. + code.push(Instruction::GetLocal(stack_idx)); + if next_stack_offset == 0 { + code.push(Instruction::GetLocal(i)); + code.push(Instruction::TableSet(table)); + code.push(Instruction::GetLocal(stack_idx)); + } else { + code.push(Instruction::I32Const(next_stack_offset)); + code.push(Instruction::I32Add); + code.push(Instruction::TeeLocal(scratch_idx)); + code.push(Instruction::GetLocal(i)); + code.push(Instruction::TableSet(table)); + code.push(Instruction::GetLocal(scratch_idx)); + } + next_stack_offset += 1; + } + } + } + + // 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. + code.push(Instruction::Call(shim_target)); + + // 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. + let new_ret_ty = if func.ret_anyref { + assert_eq!(old_ty.return_type(), Some(ValueType::I32)); + 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. + code.push(Instruction::TeeLocal(scratch_idx)); + code.push(Instruction::TableGet(table)); + code.push(Instruction::GetLocal(scratch_idx)); + code.push(Instruction::Call(heap_dealloc)); + } 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. + code.push(Instruction::SetLocal(scratch_anyref_idx)); + code.push(Instruction::Call(heap_alloc)); + code.push(Instruction::TeeLocal(scratch_idx)); + code.push(Instruction::GetLocal(scratch_anyref_idx)); + code.push(Instruction::TableSet(table)); + code.push(Instruction::GetLocal(scratch_idx)); + } + Some(ValueType::AnyRef) + } else { + old_ty.return_type() + }; + + // On function exit restore our anyref stack pointer if we decremented + // it to start off. + if anyref_stack > 0 { + code.push(Instruction::GetLocal(stack_idx)); + code.push(Instruction::I32Const(anyref_stack)); + code.push(Instruction::I32Add); + code.push(Instruction::SetGlobal(stack_pointer)); + } + + code.push(Instruction::End); + + // Handle the type signatures of these functions. We manufacture a + // new functino type with our parameters we just built above, and + // then this is pushed into the type section. + // + // For exports this is the type of the function we just generated, but + // for imports we generated a function of the old type and are + // rewriting the imported function's type. + let new_ty = FunctionType::new(params, new_ret_ty); + let new_type_idx = types.types().len() as u32; + types.types_mut().push(Type::Function(new_ty)); + let type_idx = if is_export { new_type_idx } else { type_idx }; + functions.entries_mut().push(Func::new(type_idx)); + + // Push our new shim function onto the section of all functions. + // Afterwards be sure to give it a reasonable-ish name + let instructions = Instructions::new(code); + let locals = vec![ + Local::new(2, ValueType::I32), + Local::new(1, ValueType::AnyRef), + ]; + codes.bodies_mut().push(FuncBody::new(locals, instructions)); + function_names + .names_mut() + .insert(new_func_idx, format!("{}_anyref_shim", func.name)); + + Shim { + new_type_idx, + new_func_idx, + } + } + + fn rewrite_calls(&mut self, sections: &mut Sections, max: usize) { + let code = match &mut sections.codes { + Some(s) => s, + None => return, + }; + for body in code.bodies_mut()[..max].iter_mut() { + let instrs = body.code_mut().elements_mut(); + for i in (0..instrs.len()).rev() { + let target = match instrs[i] { + Instruction::Call(i) => i, + _ => continue, + }; + let replacement = match self.call_map.get(&target) { + Some(list) => list, + None => continue, + }; + instrs.remove(i); + for instr in replacement.iter().rev().cloned() { + instrs.insert(i, instr); + } + } + } + } + + fn inject_initialization(&mut self, sections: &mut Sections) + -> Remap u32> + { + let start = sections.start.as_mut().unwrap(); + let types = sections.types.as_mut().unwrap(); + let codes = sections.codes.as_mut().unwrap(); + let imports = sections.imports.as_mut().unwrap(); + + // If there was a preexisting start function then we update its first + // instruction to initialization of the anyref table. + if **start != u32::max_value() { + assert!(**start >= sections.imported_functions); + let code_idx = **start - sections.imported_functions; + let code = &mut codes.bodies_mut()[code_idx as usize]; + code + .code_mut() + .elements_mut() + .insert(0, Instruction::Call(u32::max_value())); + } + + let type_idx = types.types().len() as u32; + let ty = Type::Function(FunctionType::new(Vec::new(), None)); + types.types_mut().push(ty); + let entry = ImportEntry::new( + "__wbindgen_placeholder__".to_string(), + "__wbindgen_init_anyref_table".to_string(), + External::Function(type_idx), + ); + imports.entries_mut().push(entry); + + let prev_function_imports = sections.imported_functions; + Remap(move |idx| { + if idx < prev_function_imports { + idx + } else if idx == u32::max_value() { + prev_function_imports + } else { + idx + 1 + } + }) + } +} + +impl<'a> Sections<'a> { + fn from(module: &'a mut Module) -> Sections<'a> { + let mut sections = Sections::default(); + for section in module.sections_mut() { + match section { + Section::Code(s) => sections.codes = Some(s), + Section::Type(s) => sections.types = Some(s), + Section::Export(s) => sections.exports = Some(s), + Section::Function(s) => sections.functions = Some(s), + Section::Table(s) => sections.tables = Some(s), + Section::Global(s) => sections.globals = Some(s), + Section::Element(s) => sections.elements = Some(s), + Section::Start(s) => sections.start = Some(s), + Section::Import(s) => { + sections.imported_functions = s.functions() as u32; + sections.imports = Some(s); + } + Section::Name(NameSection::Function(s)) => { + sections.function_names = Some(s); + } + _ => {} + } + } + return sections; + } +} diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index bed9e08fb2ab..c73c4026e424 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -15,7 +15,9 @@ base64 = "0.9" failure = "0.1.2" parity-wasm = "0.35" tempfile = "3.0" +wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.28' } wasm-bindgen-gc = { path = '../gc', version = '=0.2.28' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.28' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.28' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.28' } +wasm-bindgen-wasm-utils = { path = "../wasm-utils", version = '=0.2.28' } diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs index 83515049b910..bb41dc1fe5ee 100644 --- a/crates/cli-support/src/js/closures.rs +++ b/crates/cli-support/src/js/closures.rs @@ -14,11 +14,11 @@ use std::mem; use failure::Error; use parity_wasm::elements::*; +use wasm_utils::Remap; use descriptor::Descriptor; -use js::js2rust::Js2Rust; +use js::js2rust::{Js2Rust, ExportedShim}; use js::Context; -use wasm_utils::Remap; pub fn rewrite(input: &mut Context) -> Result<(), Error> { let info = ClosureDescriptors::new(input); @@ -120,8 +120,7 @@ impl ClosureDescriptors { Some(i) => i, None => continue, }; - let descriptor = input - .interpreter + let descriptor = input.interpreter .interpret_closure_descriptor(i, input.module, &mut ret.element_removal_list) .unwrap(); // `new_idx` is the function-space index of the function that we'll @@ -234,6 +233,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++;"); @@ -248,9 +248,10 @@ 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) {{ @@ -261,11 +262,12 @@ impl ClosureDescriptors { cb.cnt = 1; let real = cb.bind(cb); real.original = cb; - return addHeapObject(real); + return {}; }}", - closure.shim_idx, + shim, closure.dtor_idx, js, + input.add_heap_object("real"), ); input.export(&import_name, &body, None); diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 1edf41a17aaa..c4120ed3d341 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -45,6 +45,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> { @@ -60,6 +69,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ret_ty: String::new(), ret_expr: String::new(), constructor: None, + anyref_args: Vec::new(), + ret_anyref: false, } } @@ -194,13 +205,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); } @@ -391,10 +414,15 @@ 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.finally("stack.pop();"); - 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 { + self.cx.expose_borrowed_objects(); + self.finally("stack.pop();"); + self.rust_arguments + .push(format!("addBorrowedObject({})", name)); + } return Ok(self); } @@ -466,7 +494,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")?; @@ -498,8 +526,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); } @@ -690,7 +718,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() @@ -732,6 +765,26 @@ impl<'a, 'b> Js2Rust<'a, 'b> { ts.push_str(&self.ret_ty); } ts.push_str(";\n"); + + 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 99698f402039..96f2eacc9976 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::mem; +use anyref_xform; use decode; use failure::{Error, ResultExt}; use gc; @@ -14,7 +15,7 @@ use wasm_interpreter::Interpreter; use wasm_utils::Remap; mod js2rust; -use self::js2rust::Js2Rust; +use self::js2rust::{Js2Rust, ExportedShim}; mod rust2js; use self::rust2js::Rust2Js; mod closures; @@ -59,6 +60,9 @@ pub struct Context<'a> { pub function_table_needed: bool, pub interpreter: &'a mut Interpreter, pub memory_init: Option, + + pub anyref: anyref_xform::Context, + pub anyref_table_needed: bool, } #[derive(Default)] @@ -167,274 +171,256 @@ impl<'a> Context<'a> { pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { self.write_classes()?; - self.bind("__wbindgen_object_clone_ref", &|me| { - me.expose_add_heap_object(); - me.expose_get_object(); - let bump_cnt = if me.config.debug { - String::from( - " - if (typeof(val) === 'number') throw new Error('corrupt slab'); - val.cnt += 1; - ", - ) - } else { - String::from("val.cnt += 1;") - }; - Ok(format!( - " - function(idx) {{ - // If this object is on the stack promote it to the heap. - if ((idx & 1) === 1) return addHeapObject(getObject(idx)); - - // Otherwise if the object is on the heap just bump the - // refcount and move on - const val = slab[idx >> 1]; - {} - return idx; - }} - ", - bump_cnt - )) - })?; - - self.bind("__wbindgen_object_drop_ref", &|me| { - me.expose_drop_ref(); - Ok(String::from( - " - function(i) { - dropRef(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( - " - function(i) { - const val = getObject(i); + 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.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_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; - dropRef(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("dropRef".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(); + "dropRef".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| { @@ -444,19 +430,75 @@ impl<'a> Context<'a> { --no-modules" ); } + Ok(format!("function() {{ return {}; }}", + me.add_heap_object("init.__wbindgen_wasm_module"))) + })?; + + self.bind("__wbindgen_rethrow", &|me| { + Ok(format!("function(idx) {{ throw {}; }}", me.take_object("idx"))) + })?; + + closures::rewrite(self)?; + if self.config.emit_start { + self.add_start_function()?; + } + self.anyref.run(self.module); + self.unexport_unused_internal_exports(); + self.export_table(); + + // 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); + }")) + })?; + + self.gc(); + + // 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_add_heap_object(); + me.expose_get_object(); + let bump_cnt = if me.config.debug { + String::from( + " + if (typeof(val) === 'number') throw new Error('corrupt slab'); + val.cnt += 1; + ", + ) + } else { + String::from("val.cnt += 1;") + }; Ok(format!( " - function() {{ - return addHeapObject(init.__wbindgen_wasm_module); + function(idx) {{ + // If this object is on the stack promote it to the heap. + if ((idx & 1) === 1) return addHeapObject(getObject(idx)); + + // Otherwise if the object is on the heap just bump the + // refcount and move on + const val = slab[idx >> 1]; + {} + return idx; }} ", + bump_cnt )) })?; - self.bind("__wbindgen_rethrow", &|me| { - me.expose_take_object(); - Ok(String::from("function(idx) { throw takeObject(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) { dropRef(i); }")) })?; closures::rewrite(self).with_context(|_| { @@ -485,17 +527,11 @@ impl<'a> Context<'a> { // it manually. To handle the ESM use case we switch the start function // to calling an imported function which defers the start function via // `Promise.resolve().then(...)` to execute on the next microtask tick. - let mut has_start_function = false; - 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(); - } + let has_start_function = self.unstart_start_function(); + if has_start_function && !self.config.no_modules { + self.inject_start_shim(); } - self.gc(); - // Note that it's important `throw` comes last *after* we gc. The // `__wbindgen_malloc` function may call this but we only want to // generate code for this if it's actually live (and __wbindgen_malloc @@ -513,6 +549,9 @@ impl<'a> Context<'a> { self.rewrite_imports(module_name); self.update_producers_section(); + if self.config.anyref { + self.append_gc_section(); + } let mut js = if self.config.threads.is_some() { // TODO: It's not clear right now how to best use threads with @@ -539,6 +578,7 @@ impl<'a> Context<'a> { } memory.push_str("})"); + assert!(has_start_function); format!( "\ (function() {{ @@ -665,9 +705,6 @@ impl<'a> Context<'a> { ) }; - self.export_table(); - self.gc(); - while js.contains("\n\n\n") { js = js.replace("\n\n\n", "\n\n"); } @@ -743,21 +780,17 @@ impl<'a> Context<'a> { let mut wrap_needed = class.wrap_needed; let new_name = shared::new_function(&name); if self.wasm_import_needed(&new_name) { - self.expose_add_heap_object(); wrap_needed = true; - - self.export( + self.anyref.import_xform( + "__wbindgen_placeholder__", &new_name, - &format!( - " - function(ptr) {{ - return addHeapObject({}.__wrap(ptr)); - }} - ", - name - ), - None, + &[], + 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 { @@ -963,6 +996,7 @@ impl<'a> Context<'a> { } fn expose_global_stack(&mut self) { + assert!(!self.config.anyref); if !self.exposed_globals.insert("stack") { return; } @@ -986,6 +1020,7 @@ impl<'a> Context<'a> { } fn expose_global_slab(&mut self) { + assert!(!self.config.anyref); if !self.exposed_globals.insert("slab") { return; } @@ -1163,21 +1198,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(()) } @@ -1280,25 +1334,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.exposed_globals.insert("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) { @@ -1627,8 +1701,10 @@ 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" @@ -1678,10 +1754,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> { @@ -2298,6 +2374,84 @@ impl<'a> Context<'a> { .push(entry); self.set_start_section(imports); } + + fn expose_anyref_table(&mut self) { + assert!(self.config.anyref); + if !self.exposed_globals.insert("anyref_table") { + return; + } + self.anyref_table_needed = true; + for section in self.module.sections_mut() { + let exports = match *section { + Section::Export(ref mut s) => s, + _ => continue, + }; + let entry = ExportEntry::new( + "__wbg_anyref_table".to_string(), + Internal::Table(self.anyref.anyref_table_idx()), + ); + exports.entries_mut().push(entry); + break; + } + } + + fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> { + assert!(self.config.anyref); + if !self.exposed_globals.insert("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) + } + } + + /// Adds a firefox-specific section to the wasm file. This magical section + /// is currently required to exercise `anyref` on nightly, and if it's + /// omitted then the page won't be considered to have anyref enabled. + fn append_gc_section(&mut self) { + let section = Section::Unparsed { + id: 0x2a, + payload: vec![1, 1], + }; + self.module.sections_mut().insert(0, section); + } } impl<'a, 'b> SubContext<'a, 'b> { @@ -2354,7 +2508,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, @@ -2399,9 +2557,8 @@ impl<'a, 'b> SubContext<'a, 'b> { Some(class_name) } else { None - }) - .process(descriptor.unwrap_function())? - .finish("", &format!("wasm.{}", wasm_name)); + }).process(descriptor.unwrap_function())? + .finish("", &format!("wasm.{}", wasm_name), ExportedShim::Named(&wasm_name)); let class = self .cx @@ -2468,19 +2625,14 @@ 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( + self.cx.anyref.import_xform( + "__wbindgen_placeholder__", &import.shim, - &format!( - " - function() {{ - return addHeapObject({}); - }} - ", - obj - ), - None, + &[], + true, ); + let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj)); + self.cx.export(&import.shim, &body, None); Ok(()) } @@ -2531,7 +2683,16 @@ impl<'a, 'b> SubContext<'a, 'b> { } = name { shim.cx.direct_imports.insert(import.shim, (module, name)); - return Ok(()); + + 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(()) } } @@ -2539,7 +2700,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(()) } @@ -2553,15 +2714,15 @@ impl<'a, 'b> SubContext<'a, 'b> { return Ok(()); } let name = self.import_name(info, &import.name)?; - self.cx.expose_get_object(); - let body = format!( - " - function(idx) {{ - return getObject(idx) instanceof {} ? 1 : 0; - }} - ", - 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(()) } @@ -2601,6 +2762,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)? @@ -2611,12 +2773,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 b88375d9b493..d257aec3b253 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,6 +1,7 @@ use failure::Error; -use super::{Context, ImportTarget, Js2Rust}; +use super::{Context, Js2Rust, ImportTarget}; +use super::js2rust::ExportedShim; use descriptor::{Descriptor, Function}; /// Helper struct for manufacturing a shim in JS used to translate Rust types to @@ -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> { @@ -54,23 +60,17 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret_expr: String::new(), catch: false, variadic: false, + anyref_args: Vec::new(), + ret_anyref: false, } } pub fn catch(&mut self, catch: bool) -> &mut Self { - if catch { - self.cx.expose_uint32_memory(); - self.cx.expose_add_heap_object(); - } self.catch = catch; self } pub fn variadic(&mut self, variadic: bool) -> &mut Self { - if variadic { - self.cx.expose_uint32_memory(); - self.cx.expose_add_heap_object(); - } self.variadic = variadic; self } @@ -103,7 +103,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, @@ -143,12 +143,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(()); } @@ -253,6 +255,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 { @@ -267,7 +270,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { builder .rust_argument("this.b") .process(f)? - .finish("function", "this.f") + .finish("function", "this.f", ExportedShim::TableElement(&mut shim)) }; self.cx.function_table_needed = true; self.global_idx(); @@ -281,7 +284,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)); @@ -339,16 +342,29 @@ 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(()); } @@ -525,6 +541,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> { arg_idx: _, cx: _, global_idx: _, + anyref_args: _, + ret_anyref: _, } = self; !catch && @@ -540,7 +558,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { js_arguments == shim_arguments } - pub fn finish(&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(", ")); @@ -553,38 +571,47 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ret.push_str(") {\n"); ret.push_str(&self.prelude); + let variadic = self.variadic; + let ret_expr = &self.ret_expr; let handle_variadic = |invoc: &str, js_arguments: &[String]| { - let ret = if self.variadic { + let ret = if variadic { 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 { - self.ret_expr.replace( + ret_expr.replace( "JS", &format!("{}({}, ...{})", invoc, args.join(", "), last_arg), ) } else { - self.ret_expr - .replace("JS", &format!("{}(...{})", invoc, last_arg)) + ret_expr.replace( + "JS", + &format!("{}(...{})", invoc, last_arg), + ) } } else { - self.ret_expr - .replace("JS", &format!("{}({})", invoc, js_arguments.join(", "))) + 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 self.variadic { + if variadic { bail!("{} cannot be variadic", desc); } - match (class, self.js_arguments.len()) { + match (class, js_arguments.len()) { (None, n) if n == amt + 1 => { - Ok((self.js_arguments[0].clone(), &self.js_arguments[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(), &self.js_arguments[..])), + (Some(class), n) if n == amt => { + Ok((class.clone(), &js_arguments[..])) + } (Some(_), _) => bail!("static setters must have {} arguments", amt), } }; @@ -630,11 +657,23 @@ impl<'a, 'b> Rust2Js<'a, 'b> { }; if self.catch { - let catch = "\ - const view = getUint32Memory();\n\ - view[exnptr / 4] = 1;\n\ - view[exnptr / 4 + 1] = addHeapObject(e);\n\ - "; + self.cx.expose_uint32_memory(); + let catch = if self.cx.config.anyref { + self.cx.expose_add_to_anyref_table()?; + "\ + const idx = addToAnyrefTable(e); + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = idx; + " + } else { + self.cx.expose_add_heap_object(); + "\ + const view = getUint32Memory(); + view[exnptr / 4] = 1; + view[exnptr / 4 + 1] = addHeapObject(e); + " + }; invoc = format!( "\ @@ -663,6 +702,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 f5e46dc038ee..006be61063c1 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -2,12 +2,14 @@ extern crate parity_wasm; #[macro_use] -extern crate wasm_bindgen_shared as shared; +extern crate failure; +extern crate wasm_bindgen_anyref_xform as anyref_xform; extern crate wasm_bindgen_gc as gc; #[macro_use] -extern crate failure; +extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_threads_xform as threads_xform; extern crate wasm_bindgen_wasm_interpreter as wasm_interpreter; +extern crate wasm_bindgen_wasm_utils as wasm_utils; use std::collections::BTreeSet; use std::env; @@ -22,7 +24,6 @@ use parity_wasm::elements::*; mod decode; mod descriptor; mod js; -mod wasm_utils; pub mod wasm2es6js; pub struct Bindgen { @@ -44,6 +45,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 { @@ -69,6 +71,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(), } } @@ -204,8 +207,12 @@ impl Bindgen { imported_statics: Default::default(), direct_imports: Default::default(), start: None, + anyref: Default::default(), + anyref_table_needed: false, }; cx.parse_wasm_names(); + cx.anyref.enabled = self.anyref; + cx.anyref.prepare(cx.module); for program in programs.iter() { js::SubContext { program, diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/index.html b/crates/cli/src/bin/wasm-bindgen-test-runner/index.html index 57a3dac73063..f1ba94289798 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/index.html +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/index.html @@ -7,18 +7,18 @@ diff --git a/crates/gc/src/lib.rs b/crates/gc/src/lib.rs index ec4a459cc5aa..64a2bdba8604 100644 --- a/crates/gc/src/lib.rs +++ b/crates/gc/src/lib.rs @@ -363,6 +363,8 @@ impl<'a> LiveContext<'a> { ValueType::F32 => {} ValueType::F64 => {} ValueType::V128 => {} + ValueType::AnyRef => {} + ValueType::AnyFunc => {} } } @@ -385,9 +387,9 @@ impl<'a> LiveContext<'a> { self.add_block_type(b) } Instruction::Call(f) => self.add_function(f), - Instruction::CallIndirect(t, _) => { + Instruction::CallIndirect(t, table) => { self.add_type(t); - self.add_table(0); + self.add_table(table); } Instruction::GetGlobal(i) | Instruction::SetGlobal(i) => self.add_global(i), Instruction::MemoryInit(i) | Instruction::MemoryDrop(i) => { @@ -398,6 +400,9 @@ impl<'a> LiveContext<'a> { self.add_table(0); self.add_element_segment(i); } + Instruction::TableGet(i) | + Instruction::TableSet(i) | + Instruction::TableGrow(i) => self.add_table(i), _ => {} } } @@ -655,6 +660,8 @@ impl<'a> RemapContext<'a> { ValueType::F32 => {} ValueType::F64 => {} ValueType::V128 => {} + ValueType::AnyRef => {} + ValueType::AnyFunc => {} } } @@ -816,15 +823,15 @@ impl<'a> RemapContext<'a> { | Instruction::If(ref mut b) => self.remap_block_type(b), Instruction::Call(ref mut f) => self.remap_function_idx(f), Instruction::CallIndirect(ref mut t, _) => self.remap_type_idx(t), - Instruction::GetGlobal(ref mut i) | Instruction::SetGlobal(ref mut i) => { - self.remap_global_idx(i) - } - Instruction::TableInit(ref mut i) | Instruction::TableDrop(ref mut i) => { - self.remap_element_idx(i) - } - Instruction::MemoryInit(ref mut i) | Instruction::MemoryDrop(ref mut i) => { - self.remap_data_idx(i) - } + Instruction::GetGlobal(ref mut i) | + Instruction::SetGlobal(ref mut i) => self.remap_global_idx(i), + Instruction::TableInit(ref mut i) | + Instruction::TableDrop(ref mut i) => self.remap_element_idx(i), + Instruction::MemoryInit(ref mut i) | + Instruction::MemoryDrop(ref mut i) => self.remap_data_idx(i), + Instruction::TableGet(ref mut i) | + Instruction::TableSet(ref mut i) | + Instruction::TableGrow(ref mut i) => self.remap_table_idx(i), _ => {} } } @@ -870,6 +877,7 @@ impl<'a> RemapContext<'a> { } fn remap_table_idx(&self, i: &mut u32) { + trace!("table {} => {}", *i, self.tables[*i as usize]); *i = self.tables[*i as usize]; assert!(*i != u32::max_value()); } diff --git a/crates/wasm-utils/Cargo.toml b/crates/wasm-utils/Cargo.toml new file mode 100644 index 000000000000..cb88ce32a71f --- /dev/null +++ b/crates/wasm-utils/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wasm-bindgen-wasm-utils" +version = "0.2.28" +authors = ["The wasm-bindgen Developers"] +license = "MIT/Apache-2.0" +repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-utils" +homepage = "https://rustwasm.github.io/wasm-bindgen/" +documentation = "https://docs.rs/wasm-bindgen-wasm-utils" +description = """ +Wasm utilities used by the wasm-bindgen CLI +""" + +[dependencies] +parity-wasm = "0.35" diff --git a/crates/cli-support/src/wasm_utils.rs b/crates/wasm-utils/src/lib.rs similarity index 99% rename from crates/cli-support/src/wasm_utils.rs rename to crates/wasm-utils/src/lib.rs index 3d81857c2d77..7458d34ebf1e 100644 --- a/crates/cli-support/src/wasm_utils.rs +++ b/crates/wasm-utils/src/lib.rs @@ -1,3 +1,5 @@ +extern crate parity_wasm; + use std::mem; use parity_wasm::elements::*; diff --git a/publish.rs b/publish.rs index 32ea5a453823..b59d88b31a2c 100644 --- a/publish.rs +++ b/publish.rs @@ -26,8 +26,10 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wasm-bindgen-test-macro", "wasm-bindgen-test", "wasm-bindgen-wasm-interpreter", + "wasm-bindgen-wasm-utils", "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 000000000000..b77bec1199b2 --- /dev/null +++ b/src/anyref.rs @@ -0,0 +1,203 @@ +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; + } +} + +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"), + } + } +} + +#[cold] +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 + } + 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 72e89142d0b0..cdc7bec93602 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: /// /// ``` @@ -54,6 +72,7 @@ if_std! { extern crate std; use std::prelude::v1::*; pub mod closure; + mod anyref; } /// Representation of an object owned by JS. @@ -67,11 +86,12 @@ pub struct JsValue { _marker: marker::PhantomData<*mut u8>, // not at all threadsafe } -const JSIDX_UNDEFINED: u32 = 0; -const JSIDX_NULL: u32 = 2; -const JSIDX_TRUE: u32 = 4; -const JSIDX_FALSE: u32 = 6; -const JSIDX_RESERVED: u32 = 8; +const ANYREF_HEAP_START: u32 = 32; // must be kept in sync with anyref-xform +const JSIDX_UNDEFINED: u32 = ANYREF_HEAP_START + 0; +const JSIDX_NULL: u32 = ANYREF_HEAP_START + 1; +const JSIDX_TRUE: u32 = ANYREF_HEAP_START + 2; +const JSIDX_FALSE: u32 = ANYREF_HEAP_START + 3; +const JSIDX_RESERVED: u32 = ANYREF_HEAP_START + 4; impl JsValue { /// The `null` JS value constant. @@ -443,57 +463,43 @@ 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_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_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); @@ -533,11 +539,6 @@ impl Drop for JsValue { #[inline] fn drop(&mut self) { unsafe { - // The first bit indicates whether this is a stack value or not. - // Stack values should never be dropped (they're always in - // `ManuallyDrop`) - debug_assert!(self.idx & 1 == 0); - // We don't want to drop the first few elements as they're all // reserved, but everything else is safe to drop. if self.idx >= JSIDX_RESERVED { @@ -898,7 +899,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`