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:
Alex Crichton
2019-05-23 09:15:26 -07:00
parent b11b6dfe5d
commit 68c5233f80
18 changed files with 2834 additions and 2217 deletions

View File

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

View File

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

View File

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