diff --git a/build.rs b/build.rs index 0d19b8268..d4038035f 100644 --- a/build.rs +++ b/build.rs @@ -1,2 +1,4 @@ // Empty `build.rs` so that `[package] links = ...` works in `Cargo.toml`. -fn main() {} +fn main() { + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 593e75a23..2a091cde6 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -189,13 +189,6 @@ impl Descriptor { } } - pub fn ref_closure(&self) -> Option<&Closure> { - match *self { - Descriptor::Ref(ref s) => s.closure(), - _ => None, - } - } - pub fn closure(&self) -> Option<&Closure> { match *self { Descriptor::Closure(ref s) => Some(s), diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs new file mode 100644 index 000000000..eaa7a145a --- /dev/null +++ b/crates/cli-support/src/js/closures.rs @@ -0,0 +1,386 @@ +//! Support for closures in wasm-bindgen +//! +//! This module contains the bulk of the support necessary to support closures +//! in `wasm-bindgen`. The main "support" here is that `Closure::wrap` creates +//! a `JsValue` through... well... unconventional mechanisms. +//! +//! This module contains one public function, `rewrite`. The function will +//! rewrite the wasm module to correctly call closure factories and thread +//! through values into the final `Closure` object. More details about how all +//! this works can be found in the code below. + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem; + +use failure::Error; +use parity_wasm::elements::*; + +use descriptor::Descriptor; +use js::Context; +use js::js2rust::Js2Rust; + +pub fn rewrite(input: &mut Context) -> Result<(), Error> { + let info = ClosureDescriptors::new(input); + + // Sanity check to make sure things look ok and skip everything below if + // there's not calls to `Closure::new`. + assert_eq!( + info.element_removal_list.len(), + info.code_idx_to_descriptor.len(), + ); + if info.element_removal_list.len() == 0 { + return Ok(()) + } + + // Make sure the names section is available in the wasm module because we'll + // want to remap those function indices, and then actually remap all + // function indices. We're going to be injecting a few imported functions + // below which will shift the index space for all defined functions. + input.parse_wasm_names(); + Remap { + code_idx_to_descriptor: &info.code_idx_to_descriptor, + old_num_imports: input.module + .import_section() + .map(|s| s.functions()) + .unwrap_or(0) as u32, + }.remap_module(input.module); + + info.delete_function_table_entries(input); + info.inject_imports(input)?; + Ok(()) +} + +#[derive(Default)] +struct ClosureDescriptors { + /// A list of elements to remove from the function table. The first element + /// of the pair is the index of the entry in the element section, and the + /// second element of the pair is the index within that entry to remove. + element_removal_list: Vec<(usize, usize)>, + + /// A map from indexes in the code section which contain calls to + /// `__wbindgen_describe_closure` to the new function the whole function is + /// replaced with as well as the descriptor that the function describes. + /// + /// This map is later used to replace all calls to the keys of this map with + /// calls to the value of the map. + code_idx_to_descriptor: BTreeMap, +} + +impl ClosureDescriptors { + /// Find all invocations of `__wbindgen_describe_closure`. + /// + /// We'll be rewriting all calls to functions who call this import. Here we + /// iterate over all code found in the module, and anything which calls our + /// special imported function is interpreted. The result of interpretation will + /// inform of us of an entry to remove from the function table (as the describe + /// function is never needed at runtime) as well as a `Descriptor` which + /// describes the type of closure needed. + /// + /// All this information is then returned in the `ClosureDescriptors` return + /// value. + fn new(input: &mut Context) -> ClosureDescriptors { + let wbindgen_describe_closure = match input.interpreter.describe_closure_idx() { + Some(i) => i, + None => return Default::default(), + }; + let imports = input.module.import_section() + .map(|s| s.functions()) + .unwrap_or(0); + let mut ret = ClosureDescriptors::default(); + + let code = match input.module.code_section() { + Some(code) => code, + None => return Default::default(), + }; + for (i, function) in code.bodies().iter().enumerate() { + let mut call_found = false; + for instruction in function.code().elements() { + match instruction { + Instruction::Call(idx) if *idx == wbindgen_describe_closure => { + call_found = true; + break + } + _ => {} + } + } + if !call_found { + continue + } + 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 + // be injecting. Calls to the code function `i` will instead be + // rewritten to calls to `new_idx`, which is an import that we'll + // inject based on `descriptor`. + let new_idx = (ret.code_idx_to_descriptor.len() + imports) as u32; + ret.code_idx_to_descriptor.insert( + i as u32, + (new_idx, Descriptor::decode(descriptor)), + ); + } + return ret + } + + /// Here we remove elements from the function table. All our descriptor + /// functions are entries in this function table and can be removed once we + /// use them as they're not actually needed at runtime. + /// + /// One option for removal is to replace the function table entry with an + /// index to a dummy function, but for now we simply remove the table entry + /// altogether by splitting the section and having multiple `elem` sections + /// with holes in them. + fn delete_function_table_entries(&self, input: &mut Context) { + let elements = input.module.elements_section_mut().unwrap(); + let mut remove = HashMap::new(); + for (entry, idx) in self.element_removal_list.iter().cloned() { + remove.entry(entry).or_insert(HashSet::new()).insert(idx); + } + + let entries = mem::replace(elements.entries_mut(), Vec::new()); + let empty = HashSet::new(); + for (i, entry) in entries.into_iter().enumerate() { + let to_remove = remove.get(&i).unwrap_or(&empty); + + let mut current = Vec::new(); + assert_eq!(entry.offset().code().len(), 2); + let mut offset = match entry.offset().code()[0] { + Instruction::I32Const(x) => x, + _ => unreachable!(), + }; + for (j, idx) in entry.members().iter().enumerate() { + // If we keep this entry, then keep going + if !to_remove.contains(&j) { + current.push(*idx); + continue + } + + // If we have members of `current` then we save off a section + // of the function table, then update `offset` and keep going. + let next_offset = offset + (current.len() as i32) + 1; + if current.len() > 0 { + let members = mem::replace(&mut current, Vec::new()); + let offset = InitExpr::new(vec![ + Instruction::I32Const(offset), + Instruction::End, + ]); + let new_entry = ElementSegment::new(0, offset, members); + elements.entries_mut().push(new_entry); + } + offset = next_offset; + } + // Any remaining function table entries get pushed at the end. + if current.len() > 0 { + let offset = InitExpr::new(vec![ + Instruction::I32Const(offset), + Instruction::End, + ]); + let new_entry = ElementSegment::new(0, offset, current); + elements.entries_mut().push(new_entry); + } + } + } + + /// Inject new imports into the module. + /// + /// This function will inject new imported functions into the `input` module + /// described by the fields internally. These new imports will be closure + /// factories and are freshly generated shim in JS. + fn inject_imports(&self, input: &mut Context) -> Result<(), Error> { + // We'll be injecting new imports and we'll need to give them all a + // type. The signature is all `(i32, i32) -> i32` currently and we know + // that this signature already exists in the module as it's the + // signature of our `#[inline(never)]` functions. Find the type + // signature index so we can assign it below. + let type_idx = input.module.type_section() + .unwrap() + .types() + .iter() + .position(|ty| { + let fnty = match ty { + Type::Function(f) => f, + }; + fnty.params() == &[ValueType::I32, ValueType::I32] && + fnty.return_type() == Some(ValueType::I32) + }) + .unwrap(); + + // The last piece of the magic. For all our descriptors we found we + // inject a JS shim for the descriptor. This JS shim will manufacture a + // JS `function`, and prepare it to be invoked. + // + // Once all that's said and done we inject a new import into the wasm module + // of our new wrapper, and the `Remap` step above already wrote calls to + // this function within the module. + for (i, (_new_idx, descriptor)) in self.code_idx_to_descriptor.iter() { + let import_name = format!("__wbindgen_closure_wrapper{}", i); + + let closure = descriptor.closure().unwrap(); + + let (js, _ts, _js_doc) = { + let mut builder = Js2Rust::new("", input); + if closure.mutable { + builder + .prelude("let a = this.a;\n") + .prelude("this.a = 0;\n") + .rust_argument("a") + .finally("this.a = a;\n"); + } else { + builder.rust_argument("this.a"); + } + builder + .process(&closure.function)? + .finish("function", "this.f") + }; + input.expose_add_heap_object(); + input.function_table_needed = true; + let body = format!( + "function(ptr, f) {{ + let cb = {}; + cb.f = wasm.__wbg_function_table.get(f); + cb.a = ptr; + let real = cb.bind(cb); + real.original = cb; + return addHeapObject(real); + }}", + js, + ); + input.export(&import_name, &body, None); + + let new_import = ImportEntry::new( + "__wbindgen_placeholder__".to_string(), + import_name, + External::Function(type_idx as u32), + ); + input.module.import_section_mut() + .unwrap() + .entries_mut() + .push(new_import); + } + Ok(()) + } +} + +struct Remap<'a> { + code_idx_to_descriptor: &'a BTreeMap, + old_num_imports: u32, +} + +impl<'a> Remap<'a> { + fn remap_module(&self, module: &mut Module) { + for section in module.sections_mut() { + match section { + Section::Export(e) => self.remap_export_section(e), + Section::Element(e) => self.remap_element_section(e), + Section::Code(e) => self.remap_code_section(e), + Section::Start(i) => { self.remap_idx(i); } + Section::Name(n) => self.remap_name_section(n), + _ => {} + } + } + } + + fn remap_export_section(&self, section: &mut ExportSection) { + for entry in section.entries_mut() { + self.remap_export_entry(entry); + } + } + + fn remap_export_entry(&self, entry: &mut ExportEntry) { + match entry.internal_mut() { + Internal::Function(i) => { self.remap_idx(i); } + _ => {} + } + + } + + fn remap_element_section(&self, section: &mut ElementSection) { + for entry in section.entries_mut() { + self.remap_element_entry(entry); + } + } + + fn remap_element_entry(&self, entry: &mut ElementSegment) { + for member in entry.members_mut() { + self.remap_idx(member); + } + } + + fn remap_code_section(&self, section: &mut CodeSection) { + for body in section.bodies_mut() { + self.remap_func_body(body); + } + } + + fn remap_func_body(&self, body: &mut FuncBody) { + self.remap_instructions(body.code_mut()); + } + + fn remap_instructions(&self, code: &mut Instructions) { + for instr in code.elements_mut() { + self.remap_instruction(instr); + } + } + + fn remap_instruction(&self, instr: &mut Instruction) { + match instr { + Instruction::Call(i) => { self.remap_idx(i); } + _ => {} + } + } + + fn remap_name_section(&self, names: &mut NameSection) { + match names { + NameSection::Function(f) => self.remap_function_name_section(f), + NameSection::Local(f) => self.remap_local_name_section(f), + _ => {} + } + } + + fn remap_function_name_section(&self, names: &mut FunctionNameSection) { + let map = names.names_mut(); + let new = IndexMap::with_capacity(map.len()); + for (mut idx, name) in mem::replace(map, new) { + if !self.remap_idx(&mut idx) { + map.insert(idx, name); + } + } + } + + fn remap_local_name_section(&self, names: &mut LocalNameSection) { + let map = names.local_names_mut(); + let new = IndexMap::with_capacity(map.len()); + for (mut idx, name) in mem::replace(map, new) { + if !self.remap_idx(&mut idx) { + map.insert(idx, name); + } + } + } + + /// Returns whether `idx` pointed to a previously known descriptor function + /// that we're switching to an import + fn remap_idx(&self, idx: &mut u32) -> bool { + // If this was an imported function we didn't reorder those, so nothing + // to do. + if *idx < self.old_num_imports { + return false + } + let code_idx = *idx - self.old_num_imports; + + // If this `idx` points to a function which was effectively a descriptor + // function, then we want to re-point it to our imported function which + // is actually the shim factory. + if let Some((new_idx, _)) = self.code_idx_to_descriptor.get(&code_idx) { + *idx = *new_idx; + return true + } + + // And finally, otherwise this is just a normal function reference we + // don't want to touch, but we're injecting imports which shifts all + // function indices. + *idx += self.code_idx_to_descriptor.len() as u32; + false + } +} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 8017338cf..43e875582 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -16,6 +16,7 @@ mod js2rust; use self::js2rust::Js2Rust; mod rust2js; use self::rust2js::Rust2Js; +mod closures; pub struct Context<'a> { pub globals: String, @@ -394,6 +395,7 @@ impl<'a> Context<'a> { self.create_memory_export(); self.unexport_unused_internal_exports(); + closures::rewrite(self)?; self.gc()?; // Note that it's important `throw` comes last *after* we gc. The @@ -1715,7 +1717,7 @@ impl<'a> Context<'a> { fn gc(&mut self) -> Result<(), Error> { let module = mem::replace(self.module, Module::default()); - let module = module.parse_names().unwrap_or_else(|p| p.1); + self.parse_wasm_names(); let result = wasm_gc::Config::new() .demangle(self.config.demangle) .keep_debug(self.config.keep_debug || self.config.debug) @@ -1727,9 +1729,16 @@ impl<'a> Context<'a> { Ok(()) } + fn parse_wasm_names(&mut self) { + let module = mem::replace(self.module, Module::default()); + let module = module.parse_names().unwrap_or_else(|p| p.1); + *self.module = module; + } + fn describe(&mut self, name: &str) -> Option { let name = format!("__wbindgen_describe_{}", name); - Some(Descriptor::decode(self.interpreter.interpret(&name, self.module)?)) + let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?; + Some(Descriptor::decode(descriptor)) } fn global(&mut self, s: &str) { diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index a0fcd20a7..dd30d6ea9 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -275,54 +275,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return Ok(()); } - if let Some(closure) = arg.ref_closure() { - let (js, _ts, _js_doc) = { - let mut builder = Js2Rust::new("", self.cx); - if closure.mutable { - builder - .prelude("let a = this.a;\n") - .prelude("this.a = 0;\n") - .rust_argument("a") - .finally("this.a = a;\n"); - } else { - builder.rust_argument("this.a"); - } - builder - .process(&closure.function)? - .finish("function", "this.f") - }; - self.cx.expose_get_global_argument()?; - self.cx.expose_uint32_memory(); - self.cx.expose_add_heap_object(); - self.cx.function_table_needed = true; - let reset_idx = format!( - "\ - let cb{0} = {js};\n\ - cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({f}));\n\ - cb{0}.a = getGlobalArgument({a});\n\ - let real = cb{0}.bind(cb{0});\n\ - real.original = cb{0};\n\ - idx{0} = getUint32Memory()[{0} / 4] = addHeapObject(real);\n\ - ", - abi, - js = js, - f = self.global_idx(), - a = self.global_idx(), - ); - self.prelude(&format!( - "\ - let idx{0} = getUint32Memory()[{0} / 4];\n\ - if (idx{0} === 0xffffffff) {{\n\ - {1}\ - }}\n\ - ", - abi, &reset_idx - )); - self.cx.expose_get_object(); - self.js_arguments.push(format!("getObject(idx{})", abi)); - return Ok(()); - } - let invoc_arg = match *arg { ref d if d.is_number() => abi, Descriptor::Boolean => format!("{} !== 0", abi), diff --git a/crates/cli-support/src/wasm2es6js.rs b/crates/cli-support/src/wasm2es6js.rs index 952cb9c19..0562ab3a7 100644 --- a/crates/cli-support/src/wasm2es6js.rs +++ b/crates/cli-support/src/wasm2es6js.rs @@ -1,12 +1,9 @@ extern crate base64; extern crate tempfile; -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::io; -use std::process::Command; +use std::collections::HashSet; -use failure::{Error, ResultExt}; +use failure::Error; use parity_wasm::elements::*; pub struct Config { @@ -221,35 +218,3 @@ impl Output { )) } } - -fn run(cmd: &mut Command, program: &str) -> Result<(), Error> { - let output = cmd.output().with_context(|e| { - if e.kind() == io::ErrorKind::NotFound { - format!( - "failed to execute `{}`, is the tool installed \ - from the binaryen project?\ncommand line: {:?}", - program, cmd - ) - } else { - format!("failed to execute: {:?}", cmd) - } - })?; - if output.status.success() { - return Ok(()); - } - - let mut s = format!("failed to execute: {:?}\nstatus: {}\n", cmd, output.status); - if !output.stdout.is_empty() { - s.push_str(&format!( - "----- stdout ------\n{}\n", - String::from_utf8_lossy(&output.stdout) - )); - } - if !output.stderr.is_empty() { - s.push_str(&format!( - "----- stderr ------\n{}\n", - String::from_utf8_lossy(&output.stderr) - )); - } - bail!("{}", s) -} diff --git a/crates/wasm-interpreter/src/lib.rs b/crates/wasm-interpreter/src/lib.rs index da397f3d9..4f9401817 100644 --- a/crates/wasm-interpreter/src/lib.rs +++ b/crates/wasm-interpreter/src/lib.rs @@ -35,17 +35,22 @@ pub struct Interpreter { // calculations) imports: usize, - // Function index of the `__wbindgen_describe` imported function. We special - // case this to know when the environment's imported function is called. + // Function index of the `__wbindgen_describe` and + // `__wbindgen_describe_closure` imported functions. We special case this + // to know when the environment's imported function is called. describe_idx: Option, + describe_closure_idx: Option, // A mapping of string names to the function index, filled with all exported // functions. name_map: HashMap, - // The numerical index of the code section in the wasm module, indexed into + // The numerical index of the sections in the wasm module, indexed into // the module's list of sections. code_idx: Option, + types_idx: Option, + functions_idx: Option, + elements_idx: Option, // The current stack pointer (global 0) and wasm memory (the stack). Only // used in a limited capacity. @@ -60,6 +65,18 @@ pub struct Interpreter { // very specific to wasm-bindgen and is the purpose for the existence of // this module. descriptor: Vec, + + // When invoking the `__wbindgen_describe_closure` imported function, this + // stores the last table index argument, used for finding a different + // descriptor. + descriptor_table_idx: Option, +} + +struct Sections<'a> { + code: &'a CodeSection, + types: &'a TypeSection, + functions: &'a FunctionSection, + elements: &'a ElementSection, } impl Interpreter { @@ -82,6 +99,9 @@ impl Interpreter { for (i, s) in module.sections().iter().enumerate() { match s { Section::Code(_) => ret.code_idx = Some(i), + Section::Element(_) => ret.elements_idx = Some(i), + Section::Type(_) => ret.types_idx = Some(i), + Section::Function(_) => ret.functions_idx = Some(i), _ => {} } } @@ -101,10 +121,11 @@ impl Interpreter { if entry.module() != "__wbindgen_placeholder__" { continue } - if entry.field() != "__wbindgen_describe" { - continue + if entry.field() == "__wbindgen_describe" { + ret.describe_idx = Some(idx - 1 as u32); + } else if entry.field() == "__wbindgen_describe_closure" { + ret.describe_closure_idx = Some(idx - 1 as u32); } - ret.describe_idx = Some(idx - 1 as u32); } } @@ -142,47 +163,171 @@ impl Interpreter { /// /// Returns `Some` if `func` was found in the `module` and `None` if it was /// not found in the `module`. - pub fn interpret(&mut self, func: &str, module: &Module) -> Option<&[u32]> { - self.descriptor.truncate(0); + pub fn interpret_descriptor( + &mut self, + func: &str, + module: &Module, + ) -> Option<&[u32]> { let idx = *self.name_map.get(func)?; - let code = match &module.sections()[self.code_idx.unwrap()] { - Section::Code(s) => s, - _ => panic!(), - }; + self.with_sections(module, |me, sections| { + me.interpret_descriptor_idx(idx, sections) + }) + } + + fn interpret_descriptor_idx( + &mut self, + idx: u32, + sections: &Sections, + ) -> Option<&[u32]> { + self.descriptor.truncate(0); // We should have a blank wasm and LLVM stack at both the start and end // of the call. assert_eq!(self.sp, self.mem.len() as i32); assert_eq!(self.stack.len(), 0); - self.call(idx, code); + self.call(idx, sections); assert_eq!(self.stack.len(), 0); assert_eq!(self.sp, self.mem.len() as i32); Some(&self.descriptor) } - fn call(&mut self, idx: u32, code: &CodeSection) { + /// Interprets a "closure descriptor", figuring out the signature of the + /// closure that was intended. + /// + /// This function will take a `code_idx` which is known to internally + /// execute `__wbindgen_describe_closure` and interpret it. The + /// `wasm-bindgen` crate controls all callers of this internal import. It + /// will then take the index passed to `__wbindgen_describe_closure` and + /// interpret it as a function pointer. This means it'll look up within the + /// element section (function table) which index it points to. Upon finding + /// the relevant entry it'll assume that function is a descriptor function, + /// and then it will execute the descriptor function. + /// + /// The returned value is the return value of the descriptor function found. + /// The `entry_removal_list` list is also then populated with an index of + /// the entry in the elements section (and then the index within that + /// section) of the function that needs to be snip'd out. + pub fn interpret_closure_descriptor( + &mut self, + code_idx: usize, + module: &Module, + entry_removal_list: &mut Vec<(usize, usize)>, + ) -> Option<&[u32]> { + self.with_sections(module, |me, sections| { + me._interpret_closure_descriptor(code_idx, sections, entry_removal_list) + }) + } + + fn _interpret_closure_descriptor( + &mut self, + code_idx: usize, + sections: &Sections, + entry_removal_list: &mut Vec<(usize, usize)>, + ) -> Option<&[u32]> { + // Call the `code_idx` function. This is an internal `#[inline(never)]` + // whose code is completely controlled by the `wasm-bindgen` crate, so + // it should take two arguments and return one (all of which we don't + // care about here). What we're interested in is that while executing + // this function it'll call `__wbindgen_describe_closure` with an + // argument that we look for. + assert!(self.descriptor_table_idx.is_none()); + let closure_descriptor_idx = (code_idx + self.imports) as u32; + self.stack.push(0); + self.stack.push(0); + self.call(closure_descriptor_idx, sections); + assert_eq!(self.stack.len(), 1); + self.stack.pop(); + let descriptor_table_idx = self.descriptor_table_idx.take().unwrap(); + + // After we've got the table index of the descriptor function we're + // interested go take a look in the function table to find what the + // actual index of the function is. + let (entry_idx, offset, entry) = sections.elements.entries() + .iter() + .enumerate() + .filter_map(|(i, entry)| { + let code = entry.offset().code(); + if code.len() != 2 { + return None + } + if code[1] != Instruction::End { + return None + } + match code[0] { + Instruction::I32Const(x) => Some((i, x as u32, entry)), + _ => None, + } + }) + .find(|(_i, offset, entry)| { + *offset <= descriptor_table_idx && + descriptor_table_idx < (*offset + entry.members().len() as u32) + }) + .expect("failed to find index in table elements"); + let idx = (descriptor_table_idx - offset) as usize; + let descriptor_idx = entry.members()[idx]; + + // This is used later to actually remove the entry from the table, but + // we don't do the removal just yet + entry_removal_list.push((entry_idx, idx)); + + // And now execute the descriptor! + self.interpret_descriptor_idx(descriptor_idx, sections) + } + + /// Returns the function space index of the `__wbindgen_describe_closure` + /// imported function. + pub fn describe_closure_idx(&self) -> Option { + self.describe_closure_idx + } + + fn call(&mut self, idx: u32, sections: &Sections) { use parity_wasm::elements::Instruction::*; let idx = idx as usize; assert!(idx >= self.imports); // can't call imported functions - let body = &code.bodies()[idx - self.imports]; + let code_idx = idx - self.imports; + let body = §ions.code.bodies()[code_idx]; // Allocate space for our call frame's local variables. All local // variables should be of the `i32` type. assert!(body.locals().len() <= 1, "too many local types"); - let locals = body.locals() + let nlocals = body.locals() .get(0) .map(|i| { assert_eq!(i.value_type(), ValueType::I32); i.count() }) .unwrap_or(0); - let mut locals = vec![0; locals as usize]; + + let code_sig = sections.functions.entries()[code_idx].type_ref(); + let function_ty = match §ions.types.types()[code_sig as usize] { + Type::Function(t) => t, + }; + let mut locals = Vec::with_capacity(function_ty.params().len() + nlocals as usize); + // Any function parameters we have get popped off the stack and put into + // the first few locals ... + for param in function_ty.params() { + assert_eq!(*param, ValueType::I32); + locals.push(self.stack.pop().unwrap()); + } + // ... and the remaining locals all start as zero ... + for _ in 0..nlocals { + locals.push(0); + } + // ... and we expect one stack slot at the end if there's a returned + // value + let before = self.stack.len(); + let stack_after = match function_ty.return_type() { + Some(t) => { + assert_eq!(t, ValueType::I32); + before + 1 + } + None => before, + }; // Actual interpretation loop! We keep track of our stack's length to // recover it as part of the `Return` instruction, and otherwise this is // a pretty straightforward interpretation loop. - let before = self.stack.len(); for instr in body.code().elements() { match instr { I32Const(x) => self.stack.push(*x), @@ -198,8 +343,14 @@ impl Interpreter { // Otherwise this is a normal call so we recurse. if Some(*idx) == self.describe_idx { self.descriptor.push(self.stack.pop().unwrap() as u32); + } else if Some(*idx) == self.describe_closure_idx { + self.descriptor_table_idx = + Some(self.stack.pop().unwrap() as u32); + assert_eq!(self.stack.pop(), Some(0)); + assert_eq!(self.stack.pop(), Some(0)); + self.stack.push(0); } else { - self.call(*idx, code); + self.call(*idx, sections); } } GetGlobal(0) => self.stack.push(self.sp), @@ -223,7 +374,7 @@ impl Interpreter { let addr = self.stack.pop().unwrap() as u32; self.stack.push(self.mem[((addr + *offset) as usize) / 4]); } - Return => self.stack.truncate(before), + Return => self.stack.truncate(stack_after), End => break, // All other instructions shouldn't be used by our various @@ -238,6 +389,37 @@ impl Interpreter { s => panic!("unknown instruction {:?}", s), } } - assert_eq!(self.stack.len(), before); + assert_eq!(self.stack.len(), stack_after); + } + + fn with_sections<'a, T>( + &'a mut self, + module: &Module, + f: impl FnOnce(&'a mut Self, &Sections) -> T, + ) -> T { + macro_rules! access_with_defaults { + ($( + let $var: ident = module.sections[self.$field:ident] + ($name:ident); + )*) => {$( + let default = Default::default(); + let $var = match self.$field { + Some(i) => { + match &module.sections()[i] { + Section::$name(s) => s, + _ => panic!(), + } + } + None => &default, + }; + )*} + } + access_with_defaults! { + let code = module.sections[self.code_idx] (Code); + let types = module.sections[self.types_idx] (Type); + let functions = module.sections[self.functions_idx] (Function); + let elements = module.sections[self.elements_idx] (Element); + } + f(self, &Sections { code, types, functions, elements }) } } diff --git a/src/closure.rs b/src/closure.rs index 633d35b19..8f3bc76e0 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -4,8 +4,6 @@ //! closures" from Rust to JS. Some more details can be found on the `Closure` //! type itself. -#![allow(const_err)] // FIXME(rust-lang/rust#52603) - use std::cell::UnsafeCell; #[cfg(feature = "nightly")] use std::marker::Unsize; @@ -70,14 +68,12 @@ use throw; /// } /// ``` pub struct Closure { - // Actually a `Rc` pointer, but in raw form so we can easily make copies. - // See below documentation for why this is in an `Rc`. - inner: *const UnsafeCell>, - js: UnsafeCell>, + js: ManuallyDrop, + _keep_this_data_alive: Rc>>, } impl Closure - where T: ?Sized, + where T: ?Sized + WasmClosure, { /// Creates a new instance of `Closure` from the provided Rust closure. /// @@ -103,9 +99,73 @@ impl Closure /// /// This is the function where the JS closure is manufactured. pub fn wrap(t: Box) -> Closure { + let data = Rc::new(UnsafeCell::new(t)); + let ptr = &*data as *const UnsafeCell>; + + // Here we need to create a `JsValue` with the data and `T::invoke()` + // function pointer. To do that we... take a few unconventional turns. + // In essence what happens here is this: + // + // 1. First up, below we call a function, `breaks_if_inlined`. This + // function, as the name implies, does not work if it's inlined. + // More on that in a moment. + // 2. This function internally calls a special import recognized by the + // `wasm-bindgen` CLI tool, `__wbindgen_describe_closure`. This + // imported symbol is similar to `__wbindgen_describe` in that it's + // not intended to show up in the final binary but it's an + // intermediate state for a `wasm-bindgen` binary. + // 3. The `__wbindgen_describe_closure` import is namely passed a + // descriptor function, monomorphized for each invocation. + // + // Most of this doesn't actually make sense to happen at runtime! The + // real magic happens when `wasm-bindgen` comes along and updates our + // generated code. When `wasm-bindgen` runs it performs a few tasks: + // + // * First, it finds all functions that call + // `__wbindgen_describe_closure`. These are all `breaks_if_inlined` + // defined below as the symbol isn't called anywhere else. + // * Next, `wasm-bindgen` executes the `breaks_if_inlined` + // monomorphized functions, passing it dummy arguments. This will + // execute the function just enough to invoke the special import, + // namely telling us about the function pointer that is the describe + // shim. + // * This knowledge is then used to actually find the descriptor in the + // function table which is then executed to figure out the signature + // of the closure. + // * Finally, and probably most heinously, the call to + // `breaks_if_inlined` is rewritten to call an otherwise globally + // imported function. This globally imported function will generate + // the `JsValue` for this closure specialized for the signature in + // question. + // + // Later on `wasm-gc` will clean up all the dead code and ensure that + // we don't actually call `__wbindgen_describe_closure` at runtime. This + // means we will end up not actually calling `breaks_if_inlined` in the + // final binary, all calls to that function should be pruned. + // + // See crates/cli-support/src/js/closures.rs for a more information + // about what's going on here. + + extern fn describe() { + inform(CLOSURE); + T::describe() + } + + #[inline(never)] + unsafe fn breaks_if_inlined( + ptr: usize, + invoke: u32, + ) -> u32 { + super::__wbindgen_describe_closure(ptr as u32, invoke, describe:: as u32) + } + + let idx = unsafe { + breaks_if_inlined::(ptr as usize, T::invoke_fn()) + }; + Closure { - inner: Rc::into_raw(Rc::new(UnsafeCell::new(t))), - js: UnsafeCell::new(ManuallyDrop::new(JsValue { idx: !0 })), + js: ManuallyDrop::new(JsValue { idx }), + _keep_this_data_alive: data, } } @@ -122,7 +182,7 @@ impl Closure /// cleanup as it can. pub fn forget(self) { unsafe { - let idx = (*self.js.get()).idx; + let idx = self.js.idx; if idx != !0 { super::__wbindgen_cb_forget(idx); } @@ -131,12 +191,17 @@ impl Closure } } +impl AsRef for Closure { + fn as_ref(&self) -> &JsValue { + &self.js + } +} + impl WasmDescribe for Closure where T: WasmClosure + ?Sized, { fn describe() { - inform(CLOSURE); - T::describe(); + inform(ANYREF); } } @@ -147,11 +212,7 @@ impl<'a, T> IntoWasmAbi for &'a Closure type Abi = u32; fn into_abi(self, extra: &mut Stack) -> u32 { - unsafe { - extra.push(T::invoke_fn()); - extra.push(self.inner as u32); - &mut (*self.js.get()).idx as *const u32 as u32 - } + (&*self.js).into_abi(extra) } } @@ -170,11 +231,9 @@ impl Drop for Closure { fn drop(&mut self) { unsafe { - let idx = (*self.js.get()).idx; - if idx != !0 { - super::__wbindgen_cb_drop(idx); - } - drop(Rc::from_raw(self.inner)); + // this will implicitly drop our strong reference in addition to + // invalidating all future invocations of the closure + super::__wbindgen_cb_drop(self.js.idx); } } } diff --git a/src/lib.rs b/src/lib.rs index d96b59938..79dcef457 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -433,6 +433,7 @@ externs! { 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;