mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-17 06:51:24 +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:
@ -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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user