mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-24 02:23:11 +03:00
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:
parent
cda71757d3
commit
5a3cd893e0
4
build.rs
4
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");
|
||||
}
|
||||
|
@ -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),
|
||||
|
386
crates/cli-support/src/js/closures.rs
Normal file
386
crates/cli-support/src/js/closures.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = §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 })
|
||||
}
|
||||
}
|
||||
|
103
src/closure.rs
103
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user