mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-11 20:11:22 +00:00
First refactor for WebIDL bindings
This commit starts the `wasm-bindgen` CLI tool down the road to being a true polyfill for WebIDL bindings. This refactor is probably the first of a few, but is hopefully the largest and most sprawling and everything will be a bit more targeted from here on out. The goal of this refactoring is to separate out the massive `crates/cli-support/src/js/mod.rs` into a number of separate pieces of functionality. It currently takes care of basically everything including: * Binding intrinsics * Handling anyref transformations * Generating all JS for imports/exports * All the logic for how to import and how to name imports * Execution and management of wasm-bindgen closures Many of these are separable concerns and most overlap with WebIDL bindings. The internal refactoring here is intended to make it more clear who's responsible for what as well as making some existing operations much more straightforward. At a high-level, the following changes are done: 1. A `src/webidl.rs` module is introduced. The purpose of this module is to take all of the raw wasm-bindgen custom sections from the module and transform them into a WebIDL bindings section. This module has a placeholder `WebidlCustomSection` which is nowhere near the actual custom section but if you squint is in theory very similar. It's hoped that this will eventually become the true WebIDL custom section, currently being developed in an external crate. Currently, however, the WebIDL bindings custom section only covers a subset of the functionality we export to wasm-bindgen users. To avoid leaving them high and dry this module also contains an auxiliary custom section named `WasmBindgenAux`. This custom section isn't intended to have a binary format, but is intended to represent a theoretical custom section necessary to couple with WebIDL bindings to achieve all our desired functionality in `wasm-bindgen`. It'll never be standardized, but it'll also never be serialized :) 2. The `src/webidl.rs` module now takes over quite a bit of functionality from `src/js/mod.rs`. Namely it handles synthesis of an `export_map` and an `import_map` mapping export/import IDs to exactly what's expected to be hooked up there. This does not include type information (as that's in the bindings section) but rather includes things like "this is the method of class A" or "this import is from module `foo`" and things like that. These could arguably be subsumed by future JS features as well, but that's for another time! 3. All handling of wasm-bindgen "descriptor functions" now happens in a dedicated `src/descriptors.rs` module. The output of this module is its own custom section (intended to be immediately consumed by the WebIDL module) which is in theory what we want to ourselves emit one day but rustc isn't capable of doing so right now. 4. Invocations and generations of imports are completely overhauled. Using the `import_map` generated in the WebIDL step all imports are now handled much more precisely in one location rather than haphazardly throughout the module. This means we have precise information about each import of the module and we only modify exactly what we're looking at. This also vastly simplifies intrinsic generation since it's all simply a codegen part of the `rust2js.rs` module now. 5. Handling of direct imports which don't have a JS shim generated is slightly different from before and is intended to be future-compatible with WebIDL bindings in its full glory, but we'll need to update it to handle cases for constructors and method calls eventually as well. 6. Intrinsic definitions now live in their own file (`src/intrinsic.rs`) and have a separated definition for their symbol name and signature. The actual implementation of each intrinsic lives in `rust2js.rs` There's a number of TODO items to finish before this merges. This includes reimplementing the anyref pass and actually implementing import maps for other targets. Those will come soon in follow-up commits, but the entire `tests/wasm/main.rs` suite is currently passing and this seems like a good checkpoint.
This commit is contained in:
@ -90,3 +90,4 @@ wasm-bindgen = { path = '.' }
|
||||
wasm-bindgen-futures = { path = 'crates/futures' }
|
||||
js-sys = { path = 'crates/js-sys' }
|
||||
web-sys = { path = 'crates/web-sys' }
|
||||
walrus = { git = 'https://github.com/rustwasm/walrus' }
|
||||
|
@ -38,7 +38,7 @@ tys! {
|
||||
CLAMPED
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Descriptor {
|
||||
I8,
|
||||
U8,
|
||||
@ -67,14 +67,14 @@ pub enum Descriptor {
|
||||
Clamped(Box<Descriptor>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub arguments: Vec<Descriptor>,
|
||||
pub shim_idx: u32,
|
||||
pub ret: Descriptor,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Closure {
|
||||
pub shim_idx: u32,
|
||||
pub dtor_idx: u32,
|
||||
@ -146,9 +146,9 @@ impl Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_function(&self) -> &Function {
|
||||
match *self {
|
||||
Descriptor::Function(ref f) => f,
|
||||
pub fn unwrap_function(self) -> Function {
|
||||
match self {
|
||||
Descriptor::Function(f) => *f,
|
||||
_ => panic!("not a function"),
|
||||
}
|
||||
}
|
||||
@ -199,10 +199,10 @@ impl Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn closure(&self) -> Option<&Closure> {
|
||||
match *self {
|
||||
Descriptor::Closure(ref s) => Some(s),
|
||||
_ => None,
|
||||
pub fn unwrap_closure(self) -> Closure {
|
||||
match self {
|
||||
Descriptor::Closure(s) => *s,
|
||||
_ => panic!("not a closure"),
|
||||
}
|
||||
}
|
||||
|
||||
|
200
crates/cli-support/src/descriptors.rs
Normal file
200
crates/cli-support/src/descriptors.rs
Normal file
@ -0,0 +1,200 @@
|
||||
//! Management of wasm-bindgen descriptor functions.
|
||||
//!
|
||||
//! The purpose of this module is to basically execute a pass on a raw wasm
|
||||
//! module that just came out of the compiler. The pass will execute all
|
||||
//! relevant descriptor functions contained in the module which wasm-bindgen
|
||||
//! uses to convey type infomation here, to the CLI.
|
||||
//!
|
||||
//! All descriptor functions are removed after this pass runs and in their stead
|
||||
//! a new custom section, defined in this module, is inserted into the
|
||||
//! `walrus::Module` which contains all the results of all the descriptor
|
||||
//! functions.
|
||||
|
||||
use crate::descriptor::{Closure, Descriptor};
|
||||
use failure::Error;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::mem;
|
||||
use walrus::ImportId;
|
||||
use walrus::{CustomSection, FunctionId, LocalFunction, Module, TypedCustomSectionId};
|
||||
use wasm_bindgen_wasm_interpreter::Interpreter;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WasmBindgenDescriptorsSection {
|
||||
pub descriptors: HashMap<String, Descriptor>,
|
||||
pub closure_imports: HashMap<ImportId, Closure>,
|
||||
}
|
||||
|
||||
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId<WasmBindgenDescriptorsSection>;
|
||||
|
||||
/// Execute all `__wbindgen_describe_*` functions in a module, inserting a
|
||||
/// custom section which represents the executed value of each descriptor.
|
||||
///
|
||||
/// Afterwards this will delete all descriptor functions from the module.
|
||||
pub fn execute(module: &mut Module) -> Result<WasmBindgenDescriptorsSectionId, Error> {
|
||||
let mut section = WasmBindgenDescriptorsSection::default();
|
||||
let mut interpreter = Interpreter::new(module)?;
|
||||
|
||||
section.execute_exports(module, &mut interpreter)?;
|
||||
section.execute_closures(module, &mut interpreter)?;
|
||||
|
||||
// Delete all descriptor functions and imports from the module now that
|
||||
// we've executed all of them.
|
||||
walrus::passes::gc::run(module);
|
||||
|
||||
Ok(module.customs.add(section))
|
||||
}
|
||||
|
||||
impl WasmBindgenDescriptorsSection {
|
||||
fn execute_exports(
|
||||
&mut self,
|
||||
module: &mut Module,
|
||||
interpreter: &mut Interpreter,
|
||||
) -> Result<(), Error> {
|
||||
let mut to_remove = Vec::new();
|
||||
for export in module.exports.iter() {
|
||||
let prefix = "__wbindgen_describe_";
|
||||
if !export.name.starts_with(prefix) {
|
||||
continue;
|
||||
}
|
||||
let id = match export.item {
|
||||
walrus::ExportItem::Function(id) => id,
|
||||
_ => panic!("{} export not a function", export.name),
|
||||
};
|
||||
if let Some(d) = interpreter.interpret_descriptor(id, module) {
|
||||
let name = &export.name[prefix.len()..];
|
||||
let descriptor = Descriptor::decode(d);
|
||||
self.descriptors.insert(name.to_string(), descriptor);
|
||||
}
|
||||
to_remove.push(export.id());
|
||||
}
|
||||
|
||||
for id in to_remove {
|
||||
module.exports.delete(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_closures(
|
||||
&mut self,
|
||||
module: &mut Module,
|
||||
interpreter: &mut Interpreter,
|
||||
) -> Result<(), Error> {
|
||||
use walrus::ir::*;
|
||||
|
||||
// If our describe closure intrinsic isn't present or wasn't linked
|
||||
// then there's no closures, so nothing to do!
|
||||
let wbindgen_describe_closure = match interpreter.describe_closure_id() {
|
||||
Some(i) => i,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Find all functions which call `wbindgen_describe_closure`. These are
|
||||
// specially codegen'd so we know the rough structure of them. For each
|
||||
// one we delegate to the interpreter to figure out the actual result.
|
||||
let mut element_removal_list = HashSet::new();
|
||||
let mut func_to_descriptor = HashMap::new();
|
||||
for (id, local) in module.funcs.iter_local() {
|
||||
let entry = local.entry_block();
|
||||
let mut find = FindDescribeClosure {
|
||||
func: local,
|
||||
wbindgen_describe_closure,
|
||||
cur: entry.into(),
|
||||
call: None,
|
||||
};
|
||||
find.visit_block_id(&entry);
|
||||
if let Some(call) = find.call {
|
||||
let descriptor = interpreter
|
||||
.interpret_closure_descriptor(id, module, &mut element_removal_list)
|
||||
.unwrap();
|
||||
func_to_descriptor.insert(id, (call, Descriptor::decode(descriptor)));
|
||||
}
|
||||
}
|
||||
|
||||
// For all indirect functions that were closure descriptors, delete them
|
||||
// from the function table since we've executed them and they're not
|
||||
// necessary in the final binary.
|
||||
let table_id = match interpreter.function_table_id() {
|
||||
Some(id) => id,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let table = module.tables.get_mut(table_id);
|
||||
let table = match &mut table.kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
for idx in element_removal_list {
|
||||
log::trace!("delete element {}", idx);
|
||||
assert!(table.elements[idx].is_some());
|
||||
table.elements[idx] = None;
|
||||
}
|
||||
|
||||
// And finally replace all calls of `wbindgen_describe_closure` with a
|
||||
// freshly manufactured import. Save off the type of this import in
|
||||
// ourselves, and then we're good to go.
|
||||
let ty = module.funcs.get(wbindgen_describe_closure).ty();
|
||||
for (func, (call_instr, descriptor)) in func_to_descriptor {
|
||||
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
|
||||
let id = module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
|
||||
let import_id = module
|
||||
.imports
|
||||
.iter()
|
||||
.find(|i| i.name == import_name)
|
||||
.unwrap()
|
||||
.id();
|
||||
module.funcs.get_mut(id).name = Some(import_name);
|
||||
|
||||
let local = match &mut module.funcs.get_mut(func).kind {
|
||||
walrus::FunctionKind::Local(l) => l,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match local.get_mut(call_instr) {
|
||||
Expr::Call(e) => {
|
||||
assert_eq!(e.func, wbindgen_describe_closure);
|
||||
e.func = id;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
self.closure_imports
|
||||
.insert(import_id, descriptor.unwrap_closure());
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
struct FindDescribeClosure<'a> {
|
||||
func: &'a LocalFunction,
|
||||
wbindgen_describe_closure: FunctionId,
|
||||
cur: ExprId,
|
||||
call: Option<ExprId>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for FindDescribeClosure<'a> {
|
||||
fn local_function(&self) -> &'a LocalFunction {
|
||||
self.func
|
||||
}
|
||||
|
||||
fn visit_expr_id(&mut self, id: &ExprId) {
|
||||
let prev = mem::replace(&mut self.cur, *id);
|
||||
id.visit(self);
|
||||
self.cur = prev;
|
||||
}
|
||||
|
||||
fn visit_call(&mut self, call: &Call) {
|
||||
call.visit(self);
|
||||
if call.func == self.wbindgen_describe_closure {
|
||||
assert!(self.call.is_none());
|
||||
self.call = Some(self.cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomSection for WasmBindgenDescriptorsSection {
|
||||
fn name(&self) -> &str {
|
||||
"wasm-bindgen descriptors"
|
||||
}
|
||||
|
||||
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
|
||||
panic!("shouldn't emit custom sections just yet");
|
||||
}
|
||||
}
|
149
crates/cli-support/src/intrinsic.rs
Normal file
149
crates/cli-support/src/intrinsic.rs
Normal file
@ -0,0 +1,149 @@
|
||||
//! Definition of all wasm-bindgen intrinsics.
|
||||
//!
|
||||
//! This contains a definition of all intrinsics used by `src/lib.rs` in the
|
||||
//! wasm-bindgen crate. Each intrinsic listed here is part of an `enum
|
||||
//! Intrinsic` and is generated through a macro to reduce repetition.
|
||||
//!
|
||||
//! Intrinsics in this module currently largely contain their expected symbol
|
||||
//! name as well as the signature of the function that it expects.
|
||||
|
||||
use crate::descriptor::{self, Descriptor, Function};
|
||||
|
||||
macro_rules! intrinsics {
|
||||
(pub enum Intrinsic {
|
||||
$(
|
||||
#[symbol = $sym:tt]
|
||||
#[signature = fn($($arg:expr),*) -> $ret:ident]
|
||||
$name:ident,
|
||||
)*
|
||||
}) => {
|
||||
/// All wasm-bindgen intrinsics that could be depended on by a wasm
|
||||
/// module.
|
||||
#[derive(Debug)]
|
||||
pub enum Intrinsic {
|
||||
$($name,)*
|
||||
}
|
||||
|
||||
impl Intrinsic {
|
||||
/// Returns the corresponding intrinsic for a symbol name, if one
|
||||
/// matches.
|
||||
pub fn from_symbol(symbol: &str) -> Option<Intrinsic> {
|
||||
match symbol {
|
||||
$($sym => Some(Intrinsic::$name),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the expected signature of this intrinsic, used for
|
||||
/// generating a JS shim.
|
||||
pub fn binding(&self) -> Function {
|
||||
use crate::descriptor::Descriptor::*;
|
||||
match self {
|
||||
$(
|
||||
Intrinsic::$name => {
|
||||
descriptor::Function {
|
||||
shim_idx: 0,
|
||||
arguments: vec![$($arg),*],
|
||||
ret: $ret,
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn ref_anyref() -> Descriptor {
|
||||
Descriptor::Ref(Box::new(Descriptor::Anyref))
|
||||
}
|
||||
|
||||
fn ref_string() -> Descriptor {
|
||||
Descriptor::Ref(Box::new(Descriptor::String))
|
||||
}
|
||||
|
||||
intrinsics! {
|
||||
pub enum Intrinsic {
|
||||
#[symbol = "__wbindgen_jsval_eq"]
|
||||
#[signature = fn(ref_anyref(), ref_anyref()) -> Boolean]
|
||||
JsvalEq,
|
||||
#[symbol = "__wbindgen_is_function"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsFunction,
|
||||
#[symbol = "__wbindgen_is_undefined"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsUndefined,
|
||||
#[symbol = "__wbindgen_is_null"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsNull,
|
||||
#[symbol = "__wbindgen_is_object"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsObject,
|
||||
#[symbol = "__wbindgen_is_symbol"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsSymbol,
|
||||
#[symbol = "__wbindgen_is_string"]
|
||||
#[signature = fn(ref_anyref()) -> Boolean]
|
||||
IsString,
|
||||
#[symbol = "__wbindgen_object_clone_ref"]
|
||||
#[signature = fn(ref_anyref()) -> Anyref]
|
||||
ObjectCloneRef,
|
||||
#[symbol = "__wbindgen_object_drop_ref"]
|
||||
#[signature = fn(Anyref) -> Unit]
|
||||
ObjectDropRef,
|
||||
#[symbol = "__wbindgen_cb_drop"]
|
||||
#[signature = fn(Anyref) -> Boolean]
|
||||
CallbackDrop,
|
||||
#[symbol = "__wbindgen_cb_forget"]
|
||||
#[signature = fn(Anyref) -> Unit]
|
||||
CallbackForget,
|
||||
#[symbol = "__wbindgen_number_new"]
|
||||
#[signature = fn(F64) -> Anyref]
|
||||
NumberNew,
|
||||
#[symbol = "__wbindgen_string_new"]
|
||||
#[signature = fn(ref_string()) -> Anyref]
|
||||
StringNew,
|
||||
#[symbol = "__wbindgen_symbol_new"]
|
||||
#[signature = fn(I32, I32) -> Anyref]
|
||||
SymbolNew,
|
||||
#[symbol = "__wbindgen_number_get"]
|
||||
#[signature = fn(ref_anyref(), F64) -> F64]
|
||||
NumberGet,
|
||||
#[symbol = "__wbindgen_string_get"]
|
||||
#[signature = fn(ref_anyref(), I32) -> I32]
|
||||
StringGet,
|
||||
#[symbol = "__wbindgen_boolean_get"]
|
||||
#[signature = fn(ref_anyref()) -> F64]
|
||||
BooleanGet,
|
||||
#[symbol = "__wbindgen_throw"]
|
||||
#[signature = fn(ref_string()) -> Unit]
|
||||
Throw,
|
||||
#[symbol = "__wbindgen_rethrow"]
|
||||
#[signature = fn(Anyref) -> Unit]
|
||||
Rethrow,
|
||||
#[symbol = "__wbindgen_memory"]
|
||||
#[signature = fn() -> Anyref]
|
||||
Memory,
|
||||
#[symbol = "__wbindgen_module"]
|
||||
#[signature = fn() -> Anyref]
|
||||
Module,
|
||||
#[symbol = "__wbindgen_function_table"]
|
||||
#[signature = fn() -> Anyref]
|
||||
FunctionTable,
|
||||
#[symbol = "__wbindgen_debug_string"]
|
||||
#[signature = fn(ref_anyref()) -> String]
|
||||
DebugString,
|
||||
#[symbol = "__wbindgen_json_parse"]
|
||||
#[signature = fn(ref_string()) -> Anyref]
|
||||
JsonParse,
|
||||
#[symbol = "__wbindgen_json_serialize"]
|
||||
#[signature = fn(ref_anyref()) -> String]
|
||||
JsonSerialize,
|
||||
#[symbol = "__wbindgen_anyref_heap_live_count"]
|
||||
#[signature = fn() -> F64]
|
||||
AnyrefHeapLiveCount,
|
||||
#[symbol = "__wbindgen_init_nyref_table"]
|
||||
#[signature = fn() -> Unit]
|
||||
InitAnyrefTable,
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
//! 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 crate::descriptor::Descriptor;
|
||||
use crate::js::js2rust::{ExportedShim, Js2Rust};
|
||||
use crate::js::Context;
|
||||
use failure::Error;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::mem;
|
||||
use walrus::ir::{Expr, ExprId};
|
||||
use walrus::{FunctionId, LocalFunction};
|
||||
|
||||
pub fn rewrite(input: &mut Context) -> Result<(), Error> {
|
||||
let info = ClosureDescriptors::new(input);
|
||||
|
||||
if info.element_removal_list.len() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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: HashSet<usize>,
|
||||
|
||||
/// A map from local functions which contain calls to
|
||||
/// `__wbindgen_describe_closure` to the information about the closure
|
||||
/// descriptor it contains.
|
||||
///
|
||||
/// This map is later used to replace all calls to the keys of this map with
|
||||
/// calls to the value of the map.
|
||||
func_to_descriptor: BTreeMap<FunctionId, DescribeInstruction>,
|
||||
}
|
||||
|
||||
struct DescribeInstruction {
|
||||
call: ExprId,
|
||||
descriptor: 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 {
|
||||
use walrus::ir::*;
|
||||
|
||||
let wbindgen_describe_closure = match input.interpreter.describe_closure_id() {
|
||||
Some(i) => i,
|
||||
None => return Default::default(),
|
||||
};
|
||||
let mut ret = ClosureDescriptors::default();
|
||||
|
||||
for (id, local) in input.module.funcs.iter_local() {
|
||||
let entry = local.entry_block();
|
||||
let mut find = FindDescribeClosure {
|
||||
func: local,
|
||||
wbindgen_describe_closure,
|
||||
cur: entry.into(),
|
||||
call: None,
|
||||
};
|
||||
find.visit_block_id(&entry);
|
||||
if let Some(call) = find.call {
|
||||
let descriptor = input
|
||||
.interpreter
|
||||
.interpret_closure_descriptor(id, input.module, &mut ret.element_removal_list)
|
||||
.unwrap();
|
||||
ret.func_to_descriptor.insert(
|
||||
id,
|
||||
DescribeInstruction {
|
||||
call,
|
||||
descriptor: Descriptor::decode(descriptor),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
struct FindDescribeClosure<'a> {
|
||||
func: &'a LocalFunction,
|
||||
wbindgen_describe_closure: FunctionId,
|
||||
cur: ExprId,
|
||||
call: Option<ExprId>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for FindDescribeClosure<'a> {
|
||||
fn local_function(&self) -> &'a LocalFunction {
|
||||
self.func
|
||||
}
|
||||
|
||||
fn visit_expr_id(&mut self, id: &ExprId) {
|
||||
let prev = mem::replace(&mut self.cur, *id);
|
||||
id.visit(self);
|
||||
self.cur = prev;
|
||||
}
|
||||
|
||||
fn visit_call(&mut self, call: &Call) {
|
||||
call.visit(self);
|
||||
if call.func == self.wbindgen_describe_closure {
|
||||
assert!(self.call.is_none());
|
||||
self.call = Some(self.cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 table_id = match input.interpreter.function_table_id() {
|
||||
Some(id) => id,
|
||||
None => return,
|
||||
};
|
||||
let table = input.module.tables.get_mut(table_id);
|
||||
let table = match &mut table.kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
for idx in self.element_removal_list.iter().cloned() {
|
||||
log::trace!("delete element {}", idx);
|
||||
assert!(table.elements[idx].is_some());
|
||||
table.elements[idx] = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let wbindgen_describe_closure = match input.interpreter.describe_closure_id() {
|
||||
Some(i) => i,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// We'll be injecting new imports and we'll need to give them all a
|
||||
// type. The signature is all `(i32, i32, i32) -> i32` currently
|
||||
let ty = input.module.funcs.get(wbindgen_describe_closure).ty();
|
||||
|
||||
// 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 then rewrite the appropriate call
|
||||
// instruction.
|
||||
for (func, instr) in self.func_to_descriptor.iter() {
|
||||
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
|
||||
|
||||
let closure = instr.descriptor.closure().unwrap();
|
||||
|
||||
let mut shim = closure.shim_idx;
|
||||
let (js, _ts, _js_doc) = {
|
||||
let mut builder = Js2Rust::new("", input);
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
builder.prelude("this.cnt++;");
|
||||
|
||||
if closure.mutable {
|
||||
// For mutable closures they can't be invoked recursively.
|
||||
// To handle that we swap out the `this.a` pointer with zero
|
||||
// while we invoke it. If we finish and the closure wasn't
|
||||
// destroyed, then we put back the pointer so a future
|
||||
// invocation can succeed.
|
||||
builder
|
||||
.prelude("let a = this.a;")
|
||||
.prelude("this.a = 0;")
|
||||
.rust_argument("a")
|
||||
.rust_argument("b")
|
||||
.finally("if (--this.cnt === 0) d(a, b);")
|
||||
.finally("else this.a = a;");
|
||||
} else {
|
||||
// For shared closures they can be invoked recursively so we
|
||||
// just immediately pass through `this.a`. If we end up
|
||||
// executing the destructor, however, we clear out the
|
||||
// `this.a` pointer to prevent it being used again the
|
||||
// future.
|
||||
builder
|
||||
.rust_argument("this.a")
|
||||
.rust_argument("b")
|
||||
.finally("if (--this.cnt === 0) { d(this.a, b); this.a = 0; }");
|
||||
}
|
||||
builder.process(&closure.function, None)?.finish(
|
||||
"function",
|
||||
"f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
)
|
||||
};
|
||||
input.function_table_needed = true;
|
||||
let body = format!(
|
||||
"function(a, b, _ignored) {{
|
||||
const f = wasm.__wbg_function_table.get({});
|
||||
const d = wasm.__wbg_function_table.get({});
|
||||
const cb = {};
|
||||
cb.a = a;
|
||||
cb.cnt = 1;
|
||||
let real = cb.bind(cb);
|
||||
real.original = cb;
|
||||
return {};
|
||||
}}",
|
||||
shim,
|
||||
closure.dtor_idx,
|
||||
js,
|
||||
input.add_heap_object("real"),
|
||||
);
|
||||
input.export(&import_name, &body, None)?;
|
||||
|
||||
let module = "__wbindgen_placeholder__";
|
||||
let id = input.module.add_import_func(module, &import_name, ty);
|
||||
input.anyref.import_xform(module, &import_name, &[], true);
|
||||
input.module.funcs.get_mut(id).name = Some(import_name);
|
||||
|
||||
let local = match &mut input.module.funcs.get_mut(*func).kind {
|
||||
walrus::FunctionKind::Local(l) => l,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match local.get_mut(instr.call) {
|
||||
Expr::Call(e) => {
|
||||
assert_eq!(e.func, wbindgen_describe_closure);
|
||||
e.func = id;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -99,15 +99,13 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
|
||||
/// Generates all bindings necessary for the signature in `Function`,
|
||||
/// creating necessary argument conversions and return value processing.
|
||||
pub fn process<'c, I>(
|
||||
pub fn process(
|
||||
&mut self,
|
||||
function: &Function,
|
||||
opt_arg_names: I,
|
||||
) -> Result<&mut Self, Error>
|
||||
where
|
||||
I: Into<Option<&'c Vec<String>>>,
|
||||
{
|
||||
if let Some(arg_names) = opt_arg_names.into() {
|
||||
opt_arg_names: &Option<Vec<String>>,
|
||||
) -> Result<&mut Self, Error> {
|
||||
if let Some(arg_names) = opt_arg_names {
|
||||
assert_eq!(arg_names.len(), function.arguments.len());
|
||||
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
||||
self.argument(arg, arg_name.as_str())?;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,14 @@
|
||||
use crate::descriptor::{Descriptor, Function};
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::intrinsic::Intrinsic;
|
||||
use crate::js::js2rust::ExportedShim;
|
||||
use crate::js::{Context, ImportTarget, Js2Rust};
|
||||
use crate::js::{Context, Js2Rust};
|
||||
use crate::webidl::{AuxImport, AuxValue, ImportBinding};
|
||||
use failure::{bail, Error};
|
||||
|
||||
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
||||
/// JS, then invoking an imported JS function.
|
||||
pub struct Rust2Js<'a, 'b: 'a> {
|
||||
pub cx: &'a mut Context<'b>,
|
||||
cx: &'a mut Context<'b>,
|
||||
|
||||
/// Arguments of the JS shim that we're generating, aka the variables passed
|
||||
/// from Rust which are only numbers.
|
||||
@ -41,10 +43,27 @@ pub struct Rust2Js<'a, 'b: 'a> {
|
||||
/// Whether or not the last argument is a slice representing variadic arguments.
|
||||
variadic: bool,
|
||||
|
||||
/// What sort of style this invocation will be like, see the variants of
|
||||
/// this enum for more information.
|
||||
style: Style,
|
||||
|
||||
/// list of arguments that are anyref, and whether they're an owned anyref
|
||||
/// or not.
|
||||
pub anyref_args: Vec<(usize, bool)>,
|
||||
pub ret_anyref: bool,
|
||||
anyref_args: Vec<(usize, bool)>,
|
||||
ret_anyref: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Style {
|
||||
/// The imported function is expected to be invoked with `new` to create a
|
||||
/// JS object.
|
||||
Constructor,
|
||||
/// The imported function is expected to be invoked where the first
|
||||
/// parameter is the `this` and the rest of the arguments are the
|
||||
/// function's arguments.
|
||||
Method,
|
||||
/// Just a normal function call.
|
||||
Function,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
@ -63,6 +82,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
variadic: false,
|
||||
anyref_args: Vec::new(),
|
||||
ret_anyref: false,
|
||||
style: Style::Function,
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +103,21 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
|
||||
/// Generates all bindings necessary for the signature in `Function`,
|
||||
/// creating necessary argument conversions and return value processing.
|
||||
pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> {
|
||||
pub fn process(&mut self, binding: &ImportBinding) -> Result<&mut Self, Error> {
|
||||
let function = match binding {
|
||||
ImportBinding::Constructor(f) => {
|
||||
self.style = Style::Constructor;
|
||||
f
|
||||
}
|
||||
ImportBinding::Method(f) => {
|
||||
self.style = Style::Method;
|
||||
f
|
||||
}
|
||||
ImportBinding::Function(f) => {
|
||||
self.style = Style::Function;
|
||||
f
|
||||
}
|
||||
};
|
||||
for arg in function.arguments.iter() {
|
||||
self.argument(arg)?;
|
||||
}
|
||||
@ -286,7 +320,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
} else {
|
||||
builder.rust_argument("this.a");
|
||||
}
|
||||
builder.rust_argument("this.b").process(f, None)?.finish(
|
||||
builder.rust_argument("this.b").process(f, &None)?.finish(
|
||||
"function",
|
||||
"this.f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
@ -575,7 +609,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
///
|
||||
/// This is used as an optimization to wire up imports directly where
|
||||
/// possible and avoid a shim in some circumstances.
|
||||
pub fn is_noop(&self) -> bool {
|
||||
fn is_noop(&self) -> bool {
|
||||
let Rust2Js {
|
||||
// fields which may affect whether we do nontrivial work
|
||||
catch,
|
||||
@ -594,6 +628,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
global_idx: _,
|
||||
anyref_args: _,
|
||||
ret_anyref: _,
|
||||
style,
|
||||
} = self;
|
||||
|
||||
!catch &&
|
||||
@ -607,96 +642,277 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
// similarly we want to make sure that all the arguments are simply
|
||||
// forwarded from the shim we would generate to the import,
|
||||
// requiring no transformations
|
||||
js_arguments == shim_arguments
|
||||
js_arguments == shim_arguments &&
|
||||
// method/constructor invocations require some JS shimming right
|
||||
// now, so only normal function-style invocations may get wired up
|
||||
*style == Style::Function
|
||||
}
|
||||
|
||||
pub fn finish(&mut self, invoc: &ImportTarget, shim: &str) -> Result<String, Error> {
|
||||
let mut ret = String::new();
|
||||
ret.push_str("function(");
|
||||
ret.push_str(&self.shim_arguments.join(", "));
|
||||
if self.catch {
|
||||
if self.shim_arguments.len() > 0 {
|
||||
ret.push_str(", ")
|
||||
}
|
||||
ret.push_str("exnptr");
|
||||
}
|
||||
ret.push_str(") {\n");
|
||||
ret.push_str(&self.prelude);
|
||||
|
||||
pub fn finish(&mut self, target: &AuxImport) -> Result<String, Error> {
|
||||
let variadic = self.variadic;
|
||||
let ret_expr = &self.ret_expr;
|
||||
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
||||
let ret = if variadic {
|
||||
let variadic_args = |js_arguments: &[String]| {
|
||||
Ok(if !variadic {
|
||||
format!("{}", js_arguments.join(", "))
|
||||
} else {
|
||||
let (last_arg, args) = match js_arguments.split_last() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("a function with no arguments cannot be variadic"),
|
||||
};
|
||||
if args.len() > 0 {
|
||||
ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}({}, ...{})", invoc, args.join(", "), last_arg),
|
||||
)
|
||||
format!("{}, ...{}", args.join(", "), last_arg)
|
||||
} else {
|
||||
ret_expr.replace("JS", &format!("{}(...{})", invoc, last_arg))
|
||||
format!("...{}", last_arg)
|
||||
}
|
||||
} else {
|
||||
ret_expr.replace("JS", &format!("{}({})", invoc, js_arguments.join(", ")))
|
||||
};
|
||||
Ok(ret)
|
||||
})
|
||||
};
|
||||
|
||||
let js_arguments = &self.js_arguments;
|
||||
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
||||
if variadic {
|
||||
bail!("{} cannot be variadic", desc);
|
||||
}
|
||||
match (class, js_arguments.len()) {
|
||||
(None, n) if n == amt + 1 => Ok((js_arguments[0].clone(), &js_arguments[1..])),
|
||||
(None, _) => bail!("setters must have {} arguments", amt + 1),
|
||||
(Some(class), n) if n == amt => Ok((class.clone(), &js_arguments[..])),
|
||||
(Some(_), _) => bail!("static setters must have {} arguments", amt),
|
||||
}
|
||||
};
|
||||
let invoc = match target {
|
||||
AuxImport::Value(val) => match self.style {
|
||||
Style::Constructor => {
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||
_ => bail!("invalid import set for constructor"),
|
||||
};
|
||||
format!("new {}({})", js, variadic_args(&self.js_arguments)?)
|
||||
}
|
||||
Style::Method => {
|
||||
let descriptor = |anchor: &str, extra: &str, field: &str, which:&str| {
|
||||
format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
|
||||
anchor, extra, field, which
|
||||
)
|
||||
};
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||
AuxValue::Getter(class, field) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
let class = self.cx.import_name(class)?;
|
||||
descriptor(&class, ".prototype", field, "get")
|
||||
}
|
||||
AuxValue::ClassGetter(class, field) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
let class = self.cx.import_name(class)?;
|
||||
descriptor(&class, "", field, "get")
|
||||
}
|
||||
AuxValue::Setter(class, field) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
let class = self.cx.import_name(class)?;
|
||||
descriptor(&class, ".prototype", field, "set")
|
||||
}
|
||||
AuxValue::ClassSetter(class, field) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
let class = self.cx.import_name(class)?;
|
||||
descriptor(&class, "", field, "set")
|
||||
}
|
||||
};
|
||||
format!("{}.call({})", js, variadic_args(&self.js_arguments)?)
|
||||
}
|
||||
Style::Function => {
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||
_ => bail!("invalid import set for constructor"),
|
||||
};
|
||||
if self.is_noop() {
|
||||
self.cx.expose_does_not_exist();
|
||||
// TODO: comment this
|
||||
let js = format!("typeof {} === 'undefined' ? doesNotExist : {0}", js);
|
||||
return Ok(js);
|
||||
}
|
||||
format!("{}({})", js, variadic_args(&self.js_arguments)?)
|
||||
}
|
||||
},
|
||||
|
||||
let mut invoc = match invoc {
|
||||
ImportTarget::Function(f) => handle_variadic(&f, &self.js_arguments)?,
|
||||
ImportTarget::Constructor(c) => {
|
||||
handle_variadic(&format!("new {}", c), &self.js_arguments)?
|
||||
AuxImport::Instanceof(js) => {
|
||||
let js = self.cx.import_name(js)?;
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("{} instanceof {}", self.js_arguments[0], js)
|
||||
}
|
||||
ImportTarget::Method(f) => handle_variadic(&format!("{}.call", f), &self.js_arguments)?,
|
||||
ImportTarget::StructuralMethod(f) => {
|
||||
|
||||
AuxImport::Static(js) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
self.cx.import_name(js)?
|
||||
}
|
||||
|
||||
AuxImport::Closure(closure) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 3);
|
||||
let mut shim = closure.shim_idx;
|
||||
let (js, _ts, _js_doc) = {
|
||||
let mut builder = Js2Rust::new("", self.cx);
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
builder.prelude("this.cnt++;");
|
||||
|
||||
if closure.mutable {
|
||||
// For mutable closures they can't be invoked recursively.
|
||||
// To handle that we swap out the `this.a` pointer with zero
|
||||
// while we invoke it. If we finish and the closure wasn't
|
||||
// destroyed, then we put back the pointer so a future
|
||||
// invocation can succeed.
|
||||
builder
|
||||
.prelude("let a = this.a;")
|
||||
.prelude("this.a = 0;")
|
||||
.rust_argument("a")
|
||||
.rust_argument("b")
|
||||
.finally("if (--this.cnt === 0) d(a, b);")
|
||||
.finally("else this.a = a;");
|
||||
} else {
|
||||
// For shared closures they can be invoked recursively so we
|
||||
// just immediately pass through `this.a`. If we end up
|
||||
// executing the destructor, however, we clear out the
|
||||
// `this.a` pointer to prevent it being used again the
|
||||
// future.
|
||||
builder
|
||||
.rust_argument("this.a")
|
||||
.rust_argument("b")
|
||||
.finally("if (--this.cnt === 0) {")
|
||||
.finally("d(this.a, b);")
|
||||
.finally("this.a = 0;")
|
||||
.finally("}");
|
||||
}
|
||||
builder.process(&closure.function, &None)?.finish(
|
||||
"function",
|
||||
"f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
)
|
||||
};
|
||||
self.cx.function_table_needed = true;
|
||||
let body = format!(
|
||||
"
|
||||
const f = wasm.__wbg_function_table.get({});
|
||||
const d = wasm.__wbg_function_table.get({});
|
||||
const b = {};
|
||||
const cb = {};
|
||||
cb.a = {};
|
||||
cb.cnt = 1;
|
||||
let real = cb.bind(cb);
|
||||
real.original = cb;
|
||||
",
|
||||
shim,
|
||||
closure.dtor_idx,
|
||||
&self.js_arguments[1],
|
||||
js,
|
||||
&self.js_arguments[0],
|
||||
);
|
||||
self.prelude(&body);
|
||||
"real".to_string()
|
||||
}
|
||||
|
||||
AuxImport::StructuralMethod(name) => {
|
||||
assert!(self.style == Style::Function);
|
||||
let (receiver, args) = match self.js_arguments.split_first() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("methods must have at least one argument"),
|
||||
None => bail!("structural method calls must have at least one argument"),
|
||||
};
|
||||
handle_variadic(&format!("{}.{}", receiver, f), args)?
|
||||
format!("{}.{}({})", receiver, name, variadic_args(args)?)
|
||||
}
|
||||
ImportTarget::StructuralGetter(class, field) => {
|
||||
let (receiver, _) = fixed("getter", class, 0)?;
|
||||
let expr = format!("{}.{}", receiver, field);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralGetter(field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("{}.{}", self.js_arguments[0], field)
|
||||
}
|
||||
ImportTarget::StructuralSetter(class, field) => {
|
||||
let (receiver, val) = fixed("setter", class, 1)?;
|
||||
let expr = format!("{}.{} = {}", receiver, field, val[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralClassGetter(class, field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
let class = self.cx.import_name(class)?;
|
||||
format!("{}.{}", class, field)
|
||||
}
|
||||
ImportTarget::StructuralIndexingGetter(class) => {
|
||||
let (receiver, field) = fixed("indexing getter", class, 1)?;
|
||||
let expr = format!("{}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralSetter(field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
format!(
|
||||
"{}.{} = {}",
|
||||
self.js_arguments[0], field, self.js_arguments[1]
|
||||
)
|
||||
}
|
||||
ImportTarget::StructuralIndexingSetter(class) => {
|
||||
let (receiver, field) = fixed("indexing setter", class, 2)?;
|
||||
let expr = format!("{}[{}] = {}", receiver, field[0], field[1]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralClassSetter(class, field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
let class = self.cx.import_name(class)?;
|
||||
format!("{}.{} = {}", class, field, self.js_arguments[0])
|
||||
}
|
||||
ImportTarget::StructuralIndexingDeleter(class) => {
|
||||
let (receiver, field) = fixed("indexing deleter", class, 1)?;
|
||||
let expr = format!("delete {}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::IndexingGetterOfClass(class) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
let class = self.cx.import_name(class)?;
|
||||
format!("{}[{}]", class, self.js_arguments[0])
|
||||
}
|
||||
|
||||
AuxImport::IndexingGetterOfObject => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
format!("{}[{}]", self.js_arguments[0], self.js_arguments[1])
|
||||
}
|
||||
|
||||
AuxImport::IndexingSetterOfClass(class) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
let class = self.cx.import_name(class)?;
|
||||
format!(
|
||||
"{}[{}] = {}",
|
||||
class, self.js_arguments[0], self.js_arguments[1]
|
||||
)
|
||||
}
|
||||
|
||||
AuxImport::IndexingSetterOfObject => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 3);
|
||||
format!(
|
||||
"{}[{}] = {}",
|
||||
self.js_arguments[0], self.js_arguments[1], self.js_arguments[2]
|
||||
)
|
||||
}
|
||||
|
||||
AuxImport::IndexingDeleterOfClass(class) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
let class = self.cx.import_name(class)?;
|
||||
format!("delete {}[{}]", class, self.js_arguments[0])
|
||||
}
|
||||
|
||||
AuxImport::IndexingDeleterOfObject => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
format!("delete {}[{}]", self.js_arguments[0], self.js_arguments[1])
|
||||
}
|
||||
|
||||
AuxImport::WrapInExportedClass(class) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.cx.require_class_wrap(class);
|
||||
format!("{}.__wrap({})", class, self.js_arguments[0])
|
||||
}
|
||||
|
||||
AuxImport::Intrinsic(intrinsic) => {
|
||||
assert!(self.style == Style::Function);
|
||||
assert!(!variadic);
|
||||
self.intrinsic_expr(intrinsic)?
|
||||
}
|
||||
};
|
||||
let mut invoc = self.ret_expr.replace("JS", &invoc);
|
||||
|
||||
if self.catch {
|
||||
self.cx.expose_handle_error()?;
|
||||
@ -725,13 +941,13 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
return \"<failed to stringify thrown value>\";
|
||||
}}
|
||||
}}());
|
||||
console.error(\"wasm-bindgen: imported JS function `{}` that \
|
||||
console.error(\"wasm-bindgen: imported JS function that \
|
||||
was not marked as `catch` threw an error:\", \
|
||||
error);
|
||||
throw e;
|
||||
}}\
|
||||
",
|
||||
&invoc, shim,
|
||||
&invoc,
|
||||
);
|
||||
}
|
||||
|
||||
@ -747,35 +963,46 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
&invoc, &self.finally
|
||||
);
|
||||
}
|
||||
ret.push_str(&invoc);
|
||||
let mut ret = String::new();
|
||||
ret.push_str("function(");
|
||||
ret.push_str(&self.shim_arguments.join(", "));
|
||||
if self.catch {
|
||||
if self.shim_arguments.len() > 0 {
|
||||
ret.push_str(", ")
|
||||
}
|
||||
ret.push_str("exnptr");
|
||||
}
|
||||
ret.push_str(") {\n");
|
||||
ret.push_str(&self.prelude);
|
||||
|
||||
ret.push_str(&invoc);
|
||||
ret.push_str("\n}\n");
|
||||
|
||||
if self.ret_anyref || self.anyref_args.len() > 0 {
|
||||
// Some return values go at the the beginning of the argument list
|
||||
// (they force a return pointer). Handle that here by offsetting all
|
||||
// our arg indices by one, but throw in some sanity checks for if
|
||||
// this ever changes.
|
||||
if let Some(start) = self.shim_arguments.get(0) {
|
||||
if start == "ret" {
|
||||
assert!(!self.ret_anyref);
|
||||
if let Some(next) = self.shim_arguments.get(1) {
|
||||
assert_eq!(next, "arg0");
|
||||
}
|
||||
for (idx, _) in self.anyref_args.iter_mut() {
|
||||
*idx += 1;
|
||||
}
|
||||
} else {
|
||||
assert_eq!(start, "arg0");
|
||||
}
|
||||
}
|
||||
self.cx.anyref.import_xform(
|
||||
"__wbindgen_placeholder__",
|
||||
shim,
|
||||
&self.anyref_args,
|
||||
self.ret_anyref,
|
||||
);
|
||||
}
|
||||
// if self.ret_anyref || self.anyref_args.len() > 0 {
|
||||
// // Some return values go at the the beginning of the argument list
|
||||
// // (they force a return pointer). Handle that here by offsetting all
|
||||
// // our arg indices by one, but throw in some sanity checks for if
|
||||
// // this ever changes.
|
||||
// if let Some(start) = self.shim_arguments.get(0) {
|
||||
// if start == "ret" {
|
||||
// assert!(!self.ret_anyref);
|
||||
// if let Some(next) = self.shim_arguments.get(1) {
|
||||
// assert_eq!(next, "arg0");
|
||||
// }
|
||||
// for (idx, _) in self.anyref_args.iter_mut() {
|
||||
// *idx += 1;
|
||||
// }
|
||||
// } else {
|
||||
// assert_eq!(start, "arg0");
|
||||
// }
|
||||
// }
|
||||
// self.cx.anyref.import_xform(
|
||||
// "__wbindgen_placeholder__",
|
||||
// shim,
|
||||
// &self.anyref_args,
|
||||
// self.ret_anyref,
|
||||
// );
|
||||
// }
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
@ -801,4 +1028,213 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn intrinsic_expr(&mut self, intrinsic: &Intrinsic) -> Result<String, Error> {
|
||||
let expr = match intrinsic {
|
||||
Intrinsic::JsvalEq => {
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
format!("{} == {}", self.js_arguments[0], self.js_arguments[1])
|
||||
}
|
||||
|
||||
Intrinsic::IsFunction => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("typeof({}) === 'function'", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsUndefined => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("{} === undefined", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsNull => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("{} === null", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsObject => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.prelude(&format!("const val = {};", self.js_arguments[0]));
|
||||
format!("typeof(val) === 'object' && val !== null ? 1 : 0")
|
||||
}
|
||||
|
||||
Intrinsic::IsSymbol => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("typeof({}) === 'symbol'", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsString => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("typeof({}) === 'string'", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::ObjectCloneRef => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.js_arguments[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::ObjectDropRef => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.js_arguments[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::CallbackDrop => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.prelude(&format!("const obj = {}.original;", self.js_arguments[0]));
|
||||
self.prelude("if (obj.cnt-- == 1) {");
|
||||
self.prelude("obj.a = 0;");
|
||||
self.prelude("return true;");
|
||||
self.prelude("}");
|
||||
"false".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::CallbackForget => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.js_arguments[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::NumberNew => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.js_arguments[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::StringNew => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.js_arguments[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::SymbolNew => {
|
||||
// FIXME: should probably have two intrinsics, one for a
|
||||
// new anonymous symbol and one for a symbol with a string
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
self.cx.expose_get_string_from_wasm()?;
|
||||
format!(
|
||||
"{} === 0 ? Symbol() : Symbol(getStringFromWasm({0}, {}))",
|
||||
self.js_arguments[0], self.js_arguments[1]
|
||||
)
|
||||
}
|
||||
|
||||
Intrinsic::NumberGet => {
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
self.cx.expose_uint8_memory();
|
||||
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
||||
self.prelude("if (typeof(obj) === 'number') return obj;");
|
||||
self.prelude(&format!("getUint8Memory()[{}] = 1;", self.js_arguments[1]));
|
||||
"0".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::StringGet => {
|
||||
self.cx.expose_pass_string_to_wasm()?;
|
||||
self.cx.expose_uint32_memory();
|
||||
assert_eq!(self.js_arguments.len(), 2);
|
||||
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
||||
self.prelude("if (typeof(obj) !== 'string') return 0;");
|
||||
self.prelude("const ptr = passStringToWasm(obj);");
|
||||
self.prelude(&format!(
|
||||
"getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;",
|
||||
self.js_arguments[1],
|
||||
));
|
||||
"ptr".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::BooleanGet => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.prelude(&format!("const v = {};", self.js_arguments[0]));
|
||||
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
|
||||
}
|
||||
Intrinsic::Throw => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("throw new Error({})", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::Rethrow => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("throw {}", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::Module => {
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
if !self.cx.config.mode.no_modules() && !self.cx.config.mode.web() {
|
||||
bail!(
|
||||
"`wasm_bindgen::module` is currently only supported with \
|
||||
`--target no-modules` and `--target web`"
|
||||
);
|
||||
}
|
||||
format!("init.__wbindgen_wasm_module")
|
||||
}
|
||||
|
||||
Intrinsic::Memory => {
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
self.cx.memory().to_string()
|
||||
}
|
||||
|
||||
Intrinsic::FunctionTable => {
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
self.cx.function_table_needed = true;
|
||||
format!("wasm.__wbg_function_table")
|
||||
}
|
||||
|
||||
Intrinsic::DebugString => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
self.cx.expose_debug_string();
|
||||
format!("debugString({})", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::JsonParse => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("JSON.parse({})", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::JsonSerialize => {
|
||||
assert_eq!(self.js_arguments.len(), 1);
|
||||
format!("JSON.stringify({})", self.js_arguments[0])
|
||||
}
|
||||
|
||||
Intrinsic::AnyrefHeapLiveCount => {
|
||||
assert_eq!(self.js_arguments.len(), 0);
|
||||
if self.cx.config.anyref {
|
||||
// Eventually we should add support to the anyref-xform to
|
||||
// re-write calls to the imported
|
||||
// `__wbindgen_anyref_heap_live_count` function into calls to
|
||||
// the exported `__wbindgen_anyref_heap_live_count_impl`
|
||||
// function, and to un-export that function.
|
||||
//
|
||||
// But for now, we just bounce wasm -> js -> wasm because it is
|
||||
// easy.
|
||||
"wasm.__wbindgen_anyref_heap_live_count_impl()".into()
|
||||
} else {
|
||||
self.cx.expose_global_heap();
|
||||
self.prelude(
|
||||
"
|
||||
let free_count = 0;
|
||||
let next = heap_next;
|
||||
while (next < heap.length) {
|
||||
free_count += 1;
|
||||
next = heap[next];
|
||||
}
|
||||
",
|
||||
);
|
||||
format!(
|
||||
"heap.length - free_count - {} - {}",
|
||||
super::INITIAL_HEAP_OFFSET,
|
||||
super::INITIAL_HEAP_VALUES.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Intrinsic::InitAnyrefTable => {
|
||||
self.cx.expose_anyref_table();
|
||||
String::from(
|
||||
"
|
||||
const table = wasm.__wbg_anyref_table;
|
||||
const offset = table.grow(4);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
",
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,12 @@ use std::str;
|
||||
use walrus::Module;
|
||||
|
||||
mod decode;
|
||||
mod intrinsic;
|
||||
mod descriptor;
|
||||
mod descriptors;
|
||||
mod js;
|
||||
pub mod wasm2es6js;
|
||||
mod webidl;
|
||||
|
||||
pub struct Bindgen {
|
||||
input: Input,
|
||||
@ -282,99 +285,69 @@ impl Bindgen {
|
||||
);
|
||||
}
|
||||
|
||||
let mut program_storage = Vec::new();
|
||||
let programs = extract_programs(&mut module, &mut program_storage)
|
||||
.with_context(|_| "failed to extract wasm-bindgen custom sections")?;
|
||||
|
||||
if let Some(cfg) = &self.threads {
|
||||
cfg.run(&mut module)
|
||||
.with_context(|_| "failed to prepare module for threading")?;
|
||||
}
|
||||
|
||||
// If requested, turn all mangled symbols into prettier unmangled
|
||||
// symbols with the help of `rustc-demangle`.
|
||||
if self.demangle {
|
||||
demangle(&mut module);
|
||||
}
|
||||
unexported_unused_lld_things(&mut module);
|
||||
|
||||
// Here we're actually instantiating the module we've parsed above for
|
||||
// execution. Why, you might be asking, are we executing wasm code? A
|
||||
// good question!
|
||||
//
|
||||
// Transmitting information from `#[wasm_bindgen]` here to the CLI tool
|
||||
// is pretty tricky. Specifically information about the types involved
|
||||
// with a function signature (especially generic ones) can be hefty to
|
||||
// translate over. As a result, the macro emits a bunch of shims which,
|
||||
// when executed, will describe to us what the types look like.
|
||||
//
|
||||
// This means that whenever we encounter an import or export we'll
|
||||
// execute a shim function which informs us about its type so we can
|
||||
// then generate the appropriate bindings.
|
||||
let mut instance = wasm_bindgen_wasm_interpreter::Interpreter::new(&module)?;
|
||||
// We're making quite a few changes, list ourselves as a producer.
|
||||
module
|
||||
.producers
|
||||
.add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
|
||||
|
||||
let mut memories = module.memories.iter().map(|m| m.id());
|
||||
let memory = memories.next();
|
||||
if memories.next().is_some() {
|
||||
bail!("multiple memories currently not supported");
|
||||
// Learn about the type signatures of all wasm-bindgen imports and
|
||||
// exports by executing `__wbindgen_describe_*` functions. This'll
|
||||
// effectively move all the descriptor functions to their own custom
|
||||
// sections.
|
||||
descriptors::execute(&mut module)?;
|
||||
|
||||
// Process and remove our raw custom sections emitted by the
|
||||
// #[wasm_bindgen] macro and the compiler. In their stead insert a
|
||||
// forward-compatible WebIDL bindings section (forward-compatible with
|
||||
// the webidl bindings proposal) as well as an auxiliary section for all
|
||||
// sorts of miscellaneous information and features #[wasm_bindgen]
|
||||
// supports that aren't covered by WebIDL bindings.
|
||||
webidl::process(&mut module)?;
|
||||
|
||||
// If we're in a testing mode then remove the start function since we
|
||||
// shouldn't execute it.
|
||||
if !self.emit_start {
|
||||
module.start = None;
|
||||
}
|
||||
drop(memories);
|
||||
let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None));
|
||||
|
||||
// Now that our module is massaged and good to go, feed it into the JS
|
||||
// shim generation which will actually generate JS for all this.
|
||||
let (js, ts) = {
|
||||
let mut cx = js::Context {
|
||||
globals: String::new(),
|
||||
imports: String::new(),
|
||||
imports_post: String::new(),
|
||||
footer: String::new(),
|
||||
typescript: format!("/* tslint:disable */\n"),
|
||||
exposed_globals: Some(Default::default()),
|
||||
required_internal_exports: Default::default(),
|
||||
imported_names: Default::default(),
|
||||
defined_identifiers: Default::default(),
|
||||
exported_classes: Some(Default::default()),
|
||||
config: &self,
|
||||
module: &mut module,
|
||||
function_table_needed: false,
|
||||
interpreter: &mut instance,
|
||||
memory,
|
||||
imported_functions: Default::default(),
|
||||
imported_statics: Default::default(),
|
||||
direct_imports: Default::default(),
|
||||
local_modules: Default::default(),
|
||||
start: None,
|
||||
anyref: Default::default(),
|
||||
snippet_offsets: Default::default(),
|
||||
npm_dependencies: Default::default(),
|
||||
package_json_read: Default::default(),
|
||||
};
|
||||
let mut cx = js::Context::new(&mut module, self)?;
|
||||
cx.anyref.enabled = self.anyref;
|
||||
cx.anyref.prepare(cx.module)?;
|
||||
for program in programs.iter() {
|
||||
js::SubContext {
|
||||
program,
|
||||
cx: &mut cx,
|
||||
vendor_prefixes: Default::default(),
|
||||
}
|
||||
.generate()?;
|
||||
|
||||
let offset = cx
|
||||
.snippet_offsets
|
||||
.entry(program.unique_crate_identifier)
|
||||
.or_insert(0);
|
||||
for js in program.inline_js.iter() {
|
||||
let name = format!("inline{}.js", *offset);
|
||||
*offset += 1;
|
||||
let aux = cx.module.customs.delete_typed::<webidl::WasmBindgenAux>()
|
||||
.expect("aux section should be present");
|
||||
cx.generate(&aux)?;
|
||||
|
||||
// Write out all local JS snippets to the final destination now that
|
||||
// we've collected them from all the programs.
|
||||
for (identifier, list) in aux.snippets.iter() {
|
||||
for (i, js) in list.iter().enumerate() {
|
||||
let name = format!("inline{}.js", i);
|
||||
let path = out_dir
|
||||
.join("snippets")
|
||||
.join(program.unique_crate_identifier)
|
||||
.join(identifier)
|
||||
.join(name);
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(&path, js)
|
||||
.with_context(|_| format!("failed to write `{}`", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out all local JS snippets to the final destination now that
|
||||
// we've collected them from all the programs.
|
||||
for (path, contents) in cx.local_modules.iter() {
|
||||
for (path, contents) in aux.local_modules.iter() {
|
||||
let path = out_dir.join("snippets").join(path);
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(&path, contents)
|
||||
@ -394,6 +367,8 @@ impl Bindgen {
|
||||
cx.finalize(stem)?
|
||||
};
|
||||
|
||||
// And now that we've got all our JS and TypeScript, actually write it
|
||||
// out to the filesystem.
|
||||
let extension = if self.mode.nodejs_experimental_modules() {
|
||||
"mjs"
|
||||
} else {
|
||||
@ -503,127 +478,6 @@ impl Bindgen {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_programs<'a>(
|
||||
module: &mut Module,
|
||||
program_storage: &'a mut Vec<Vec<u8>>,
|
||||
) -> Result<Vec<decode::Program<'a>>, Error> {
|
||||
let my_version = wasm_bindgen_shared::version();
|
||||
assert!(program_storage.is_empty());
|
||||
|
||||
while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") {
|
||||
log::debug!(
|
||||
"custom section '{}' looks like a wasm bindgen section",
|
||||
raw.name
|
||||
);
|
||||
program_storage.push(raw.data);
|
||||
}
|
||||
|
||||
let mut ret = Vec::new();
|
||||
for program in program_storage.iter() {
|
||||
let mut payload = &program[..];
|
||||
while let Some(data) = get_remaining(&mut payload) {
|
||||
// Historical versions of wasm-bindgen have used JSON as the custom
|
||||
// data section format. Newer versions, however, are using a custom
|
||||
// serialization protocol that looks much more like the wasm spec.
|
||||
//
|
||||
// We, however, want a sanity check to ensure that if we're running
|
||||
// against the wrong wasm-bindgen we get a nicer error than an
|
||||
// internal decode error. To that end we continue to verify a tiny
|
||||
// bit of json at the beginning of each blob before moving to the
|
||||
// next blob. This should keep us compatible with older wasm-bindgen
|
||||
// instances as well as forward-compatible for now.
|
||||
//
|
||||
// Note, though, that as `wasm-pack` picks up steam it's hoped we
|
||||
// can just delete this entirely. The `wasm-pack` project already
|
||||
// manages versions for us, so we in theory should need this check
|
||||
// less and less over time.
|
||||
if let Some(their_version) = verify_schema_matches(data)? {
|
||||
bail!(
|
||||
"
|
||||
|
||||
it looks like the Rust project used to create this wasm file was linked against
|
||||
a different version of wasm-bindgen than this binary:
|
||||
|
||||
rust wasm file: {}
|
||||
this binary: {}
|
||||
|
||||
Currently the bindgen format is unstable enough that these two version must
|
||||
exactly match, so it's required that these two version are kept in sync by
|
||||
either updating the wasm-bindgen dependency or this binary. You should be able
|
||||
to update the wasm-bindgen dependency with:
|
||||
|
||||
cargo update -p wasm-bindgen
|
||||
|
||||
or you can update the binary with
|
||||
|
||||
cargo install -f wasm-bindgen-cli
|
||||
|
||||
if this warning fails to go away though and you're not sure what to do feel free
|
||||
to open an issue at https://github.com/rustwasm/wasm-bindgen/issues!
|
||||
",
|
||||
their_version,
|
||||
my_version,
|
||||
);
|
||||
}
|
||||
let next = get_remaining(&mut payload).unwrap();
|
||||
log::debug!("found a program of length {}", next.len());
|
||||
ret.push(<decode::Program as decode::Decode>::decode_all(next));
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> {
|
||||
if data.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
let len = ((data[0] as usize) << 0)
|
||||
| ((data[1] as usize) << 8)
|
||||
| ((data[2] as usize) << 16)
|
||||
| ((data[3] as usize) << 24);
|
||||
let (a, b) = data[4..].split_at(len);
|
||||
*data = b;
|
||||
Some(a)
|
||||
}
|
||||
|
||||
fn verify_schema_matches<'a>(data: &'a [u8]) -> Result<Option<&'a str>, Error> {
|
||||
macro_rules! bad {
|
||||
() => {
|
||||
bail!("failed to decode what looked like wasm-bindgen data")
|
||||
};
|
||||
}
|
||||
let data = match str::from_utf8(data) {
|
||||
Ok(s) => s,
|
||||
Err(_) => bad!(),
|
||||
};
|
||||
log::debug!("found version specifier {}", data);
|
||||
if !data.starts_with("{") || !data.ends_with("}") {
|
||||
bad!()
|
||||
}
|
||||
let needle = "\"schema_version\":\"";
|
||||
let rest = match data.find(needle) {
|
||||
Some(i) => &data[i + needle.len()..],
|
||||
None => bad!(),
|
||||
};
|
||||
let their_schema_version = match rest.find("\"") {
|
||||
Some(i) => &rest[..i],
|
||||
None => bad!(),
|
||||
};
|
||||
if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION {
|
||||
return Ok(None);
|
||||
}
|
||||
let needle = "\"version\":\"";
|
||||
let rest = match data.find(needle) {
|
||||
Some(i) => &data[i + needle.len()..],
|
||||
None => bad!(),
|
||||
};
|
||||
let their_version = match rest.find("\"") {
|
||||
Some(i) => &rest[..i],
|
||||
None => bad!(),
|
||||
};
|
||||
Ok(Some(their_version))
|
||||
}
|
||||
|
||||
fn reset_indentation(s: &str) -> String {
|
||||
let mut indent: u32 = 0;
|
||||
let mut dst = String::new();
|
||||
@ -728,3 +582,21 @@ impl OutputMode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a number of internal exports that are synthesized by Rust's linker,
|
||||
/// LLD. These exports aren't typically ever needed and just add extra space to
|
||||
/// the binary.
|
||||
fn unexported_unused_lld_things(module: &mut Module) {
|
||||
let mut to_remove = Vec::new();
|
||||
for export in module.exports.iter() {
|
||||
match export.name.as_str() {
|
||||
"__heap_base" | "__data_end" | "__indirect_function_table" => {
|
||||
to_remove.push(export.id());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for id in to_remove {
|
||||
module.exports.delete(id);
|
||||
}
|
||||
}
|
||||
|
1294
crates/cli-support/src/webidl.rs
Normal file
1294
crates/cli-support/src/webidl.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -71,7 +71,7 @@ fn rmain() -> Result<(), Error> {
|
||||
// that any exported function with the prefix `__wbg_test` is a test we need
|
||||
// to execute.
|
||||
let wasm = fs::read(&wasm_file_to_test).context("failed to read wasm file")?;
|
||||
let wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?;
|
||||
let mut wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?;
|
||||
let mut tests = Vec::new();
|
||||
|
||||
for export in wasm.exports.iter() {
|
||||
@ -94,11 +94,8 @@ fn rmain() -> Result<(), Error> {
|
||||
// `wasm_bindgen_test_configure` macro, which emits a custom section for us
|
||||
// to read later on.
|
||||
let mut node = true;
|
||||
for (_id, custom) in wasm.customs.iter() {
|
||||
if custom.name() != "__wasm_bindgen_test_unstable" {
|
||||
continue;
|
||||
}
|
||||
node = !custom.data().contains(&0x01);
|
||||
if let Some(section) = wasm.customs.remove_raw("__wasm_bindgen_test_unstable") {
|
||||
node = !section.data.contains(&0x01);
|
||||
}
|
||||
let headless = env::var("NO_HEADLESS").is_err();
|
||||
let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err();
|
||||
|
@ -22,10 +22,10 @@ pub fn execute(
|
||||
const og = console[method];
|
||||
const on_method = `on_console_${{method}}`;
|
||||
console[method] = function (...args) {{
|
||||
og.apply(this, args);
|
||||
if (handlers[on_method]) {{
|
||||
handlers[on_method](args);
|
||||
}}
|
||||
og.apply(this, args);
|
||||
}};
|
||||
}};
|
||||
|
||||
|
@ -127,12 +127,7 @@ impl Interpreter {
|
||||
///
|
||||
/// Returns `Some` if `func` was found in the `module` and `None` if it was
|
||||
/// not found in the `module`.
|
||||
pub fn interpret_descriptor(&mut self, func: &str, module: &Module) -> Option<&[u32]> {
|
||||
let id = *self.name_map.get(func)?;
|
||||
self.interpret_descriptor_id(id, module)
|
||||
}
|
||||
|
||||
fn interpret_descriptor_id(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> {
|
||||
pub fn interpret_descriptor(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> {
|
||||
self.descriptor.truncate(0);
|
||||
|
||||
// We should have a blank wasm and LLVM stack at both the start and end
|
||||
@ -212,7 +207,7 @@ impl Interpreter {
|
||||
entry_removal_list.insert(descriptor_table_idx);
|
||||
|
||||
// And now execute the descriptor!
|
||||
self.interpret_descriptor_id(descriptor_id, module)
|
||||
self.interpret_descriptor(descriptor_id, module)
|
||||
}
|
||||
|
||||
/// Returns the function id of the `__wbindgen_describe_closure`
|
||||
|
22
src/lib.rs
22
src/lib.rs
@ -220,9 +220,9 @@ impl JsValue {
|
||||
T: for<'a> serde::de::Deserialize<'a>,
|
||||
{
|
||||
unsafe {
|
||||
let mut ptr = ptr::null_mut();
|
||||
let len = __wbindgen_json_serialize(self.idx, &mut ptr);
|
||||
let s = Vec::from_raw_parts(ptr, len, len);
|
||||
let mut ret = [0usize; 2];
|
||||
__wbindgen_json_serialize(&mut ret, self.idx);
|
||||
let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||
let s = String::from_utf8_unchecked(s);
|
||||
serde_json::from_str(&s)
|
||||
}
|
||||
@ -333,14 +333,10 @@ impl JsValue {
|
||||
#[cfg(feature = "std")]
|
||||
fn as_debug_string(&self) -> String {
|
||||
unsafe {
|
||||
let mut len = 0;
|
||||
let ptr = __wbindgen_debug_string(self.idx, &mut len);
|
||||
if ptr.is_null() {
|
||||
unreachable!("`__wbindgen_debug_string` must return a valid string")
|
||||
} else {
|
||||
let data = Vec::from_raw_parts(ptr, len, len);
|
||||
String::from_utf8_unchecked(data)
|
||||
}
|
||||
let mut ret = [0; 2];
|
||||
__wbindgen_debug_string(&mut ret, self.idx);
|
||||
let data = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||
String::from_utf8_unchecked(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -507,7 +503,7 @@ externs! {
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
|
||||
|
||||
fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8;
|
||||
fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();
|
||||
|
||||
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
||||
fn __wbindgen_rethrow(a: u32) -> !;
|
||||
@ -519,7 +515,7 @@ externs! {
|
||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
||||
|
||||
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
|
||||
fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> ();
|
||||
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
|
||||
|
||||
fn __wbindgen_memory() -> u32;
|
||||
|
@ -126,3 +126,13 @@ exports.pass_reference_first_arg_twice = (a, b, c) => {
|
||||
exports.call_destroyed = f => {
|
||||
assert.throws(f, /invoked recursively or destroyed/);
|
||||
};
|
||||
|
||||
let FORGOTTEN_CLOSURE = null;
|
||||
|
||||
exports.js_store_forgotten_closure = f => {
|
||||
FORGOTTEN_CLOSURE = f;
|
||||
};
|
||||
|
||||
exports.js_call_forgotten_closure = () => {
|
||||
FORGOTTEN_CLOSURE();
|
||||
};
|
||||
|
@ -108,6 +108,9 @@ extern "C" {
|
||||
c: &mut FnMut(&RefFirstArgument),
|
||||
);
|
||||
fn call_destroyed(a: &JsValue);
|
||||
|
||||
fn js_store_forgotten_closure(closure: &Closure<Fn()>);
|
||||
fn js_call_forgotten_closure();
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@ -573,3 +576,11 @@ fn call_destroyed_doesnt_segfault() {
|
||||
drop(a);
|
||||
call_destroyed(&b);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn forget_works() {
|
||||
let a = Closure::wrap(Box::new(|| {}) as Box<Fn()>);
|
||||
js_store_forgotten_closure(&a);
|
||||
a.forget();
|
||||
js_call_forgotten_closure();
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ fn switch_methods() {
|
||||
|
||||
assert!(!switch_methods_called());
|
||||
SwitchMethods::new().b();
|
||||
assert!(switch_methods_called());
|
||||
assert!(!switch_methods_called());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@ -213,3 +213,12 @@ fn string_roundtrip() {
|
||||
pub fn do_string_roundtrip(s: String) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn anyref_heap_live_count() {
|
||||
let x = wasm_bindgen::anyref_heap_live_count();
|
||||
let y = JsValue::null().clone();
|
||||
assert!(wasm_bindgen::anyref_heap_live_count() > x);
|
||||
drop(y);
|
||||
assert_eq!(x, wasm_bindgen::anyref_heap_live_count());
|
||||
}
|
||||
|
Reference in New Issue
Block a user