Implement AsRef<JsValue> for Closure<T>

This commit adds an implementation of `AsRef<JsValue>` for the `Closure<T>`
type. Previously this was not possible because the `JsValue` didn't actually
exist until the closure was passed to JS, but the implementation has been
changed to ... something a bit more unconventional. The end result, however, is
that `Closure<T>` now always contains a `JsValue`.

The end result of this work is intended to be a precursor to binding callbacks
in `web-sys` as `JsValue` everywhere but still allowing usage with `Closure<T>`.
This commit is contained in:
Alex Crichton 2018-09-05 23:59:49 -07:00
parent cda71757d3
commit 5a3cd893e0
9 changed files with 687 additions and 138 deletions

View File

@ -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");
}

View File

@ -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),

View File

@ -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<u32, (u32, Descriptor)>,
}
impl ClosureDescriptors {
/// Find all invocations of `__wbindgen_describe_closure`.
///
/// We'll be rewriting all calls to functions who call this import. Here we
/// iterate over all code found in the module, and anything which calls our
/// special imported function is interpreted. The result of interpretation will
/// inform of us of an entry to remove from the function table (as the describe
/// function is never needed at runtime) as well as a `Descriptor` which
/// describes the type of closure needed.
///
/// All this information is then returned in the `ClosureDescriptors` return
/// value.
fn new(input: &mut Context) -> ClosureDescriptors {
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<u32, (u32, Descriptor)>,
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
}
}

View File

@ -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<Descriptor> {
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) {

View File

@ -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),

View File

@ -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)
}

View File

@ -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<u32>,
describe_closure_idx: Option<u32>,
// A mapping of string names to the function index, filled with all exported
// functions.
name_map: HashMap<String, u32>,
// 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<usize>,
types_idx: Option<usize>,
functions_idx: Option<usize>,
elements_idx: Option<usize>,
// 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<u32>,
// 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<u32>,
}
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<u32> {
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 = &sections.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 &sections.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 })
}
}

View File

@ -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<T: ?Sized> {
// 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<Box<T>>,
js: UnsafeCell<ManuallyDrop<JsValue>>,
js: ManuallyDrop<JsValue>,
_keep_this_data_alive: Rc<UnsafeCell<Box<T>>>,
}
impl<T> Closure<T>
where T: ?Sized,
where T: ?Sized + WasmClosure,
{
/// Creates a new instance of `Closure` from the provided Rust closure.
///
@ -103,9 +99,73 @@ impl<T> Closure<T>
///
/// This is the function where the JS closure is manufactured.
pub fn wrap(t: Box<T>) -> Closure<T> {
let data = Rc::new(UnsafeCell::new(t));
let ptr = &*data as *const UnsafeCell<Box<T>>;
// 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<T: WasmClosure + ?Sized>() {
inform(CLOSURE);
T::describe()
}
#[inline(never)]
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(
ptr: usize,
invoke: u32,
) -> u32 {
super::__wbindgen_describe_closure(ptr as u32, invoke, describe::<T> as u32)
}
let idx = unsafe {
breaks_if_inlined::<T>(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<T> Closure<T>
/// 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<T> Closure<T>
}
}
impl<T> AsRef<JsValue> for Closure<T> {
fn as_ref(&self) -> &JsValue {
&self.js
}
}
impl<T> WasmDescribe for Closure<T>
where T: WasmClosure + ?Sized,
{
fn describe() {
inform(CLOSURE);
T::describe();
inform(ANYREF);
}
}
@ -147,11 +212,7 @@ impl<'a, T> IntoWasmAbi for &'a Closure<T>
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<T> Drop for Closure<T>
{
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);
}
}
}

View File

@ -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;