Migrate `wasm-bindgen` to using `walrus`
This commit moves `wasm-bindgen` the CLI tool from internally using
`parity-wasm` for wasm parsing/serialization to instead use `walrus`.
The `walrus` crate is something we've been working on recently with an
aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the
current CLI tool more maintainable as well as more future-proof.
The `walrus` crate provides a much nicer AST to work with as well as a
structured `Module`, whereas `parity-wasm` provides a very raw interface
to the wasm module which isn't really appropriate for our use case. The
many transformations and tweaks that wasm-bindgen does have a huge
amount of ad-hoc index management to carefully craft a final wasm
binary, but this is all entirely taken care for us with the `walrus`
crate.
Additionally, `wasm-bindgen` will ingest and rewrite the wasm file,
often changing the binary offsets of functions. Eventually with DWARF
debug information we'll need to be sure to preserve the debug
information throughout the transformations that `wasm-bindgen` does
today. This is practically impossible to do with the `parity-wasm`
architecture, but `walrus` was designed from the get-go to solve this
problem transparently in the `walrus` crate itself. (it doesn't today,
but this is planned work)
It is the intention that this does not end up regressing any
`wasm-bindgen` use cases, neither in functionality or in speed. As a
large change and refactoring, however, it's likely that at least
something will arise! We'll want to continue to remain vigilant to any
issues that come up with this commit.
Note that the `gc` crate has been deleted as part of this change, as the
`gc` crate is no longer necessary since `walrus` does it automatically.
Additionally the `gc` crate was one of the main problems with preserving
debug information as it often deletes wasm items!
Finally, this also starts moving crates to the 2018 edition where
necessary since `walrus` requires the 2018 edition, and in general it's
more pleasant to work within the 2018 edition!
2019-01-31 09:54:23 -08:00
|
|
|
use crate::descriptor::{Descriptor, Function};
|
|
|
|
use crate::js::Context;
|
|
|
|
use failure::{bail, Error};
|
2018-04-14 09:13:07 -07:00
|
|
|
|
2019-04-25 18:44:28 +02:00
|
|
|
pub struct JsArgument {
|
|
|
|
pub optional: bool,
|
|
|
|
pub name: String,
|
|
|
|
pub type_: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl JsArgument {
|
|
|
|
fn required(name: String, type_: String) -> Self {
|
2019-05-28 09:52:44 -05:00
|
|
|
Self {
|
|
|
|
optional: false,
|
|
|
|
name,
|
|
|
|
type_,
|
|
|
|
}
|
2019-04-25 18:44:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn optional(name: String, type_: String) -> Self {
|
2019-05-28 09:52:44 -05:00
|
|
|
Self {
|
|
|
|
optional: true,
|
|
|
|
name,
|
|
|
|
type_,
|
|
|
|
}
|
2019-04-25 18:44:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 12:34:41 -05:00
|
|
|
/// Helper struct for manufacturing a shim in JS used to translate JS types to
|
2018-04-14 09:13:07 -07:00
|
|
|
/// Rust, aka pass from JS back into Rust
|
|
|
|
pub struct Js2Rust<'a, 'b: 'a> {
|
|
|
|
cx: &'a mut Context<'b>,
|
|
|
|
|
|
|
|
/// Arguments passed to the invocation of the wasm function, aka things that
|
|
|
|
/// are only numbers.
|
|
|
|
rust_arguments: Vec<String>,
|
|
|
|
|
|
|
|
/// Arguments and their types to the JS shim.
|
2019-04-25 18:44:28 +02:00
|
|
|
pub js_arguments: Vec<JsArgument>,
|
2018-04-14 09:13:07 -07:00
|
|
|
|
|
|
|
/// Conversions that happen before we invoke the wasm function, such as
|
|
|
|
/// converting a string to a ptr/length pair.
|
|
|
|
prelude: String,
|
|
|
|
|
|
|
|
/// "Destructors" or cleanup that must happen after the wasm function
|
|
|
|
/// finishes. This is scheduled in a `finally` block.
|
|
|
|
finally: String,
|
|
|
|
|
|
|
|
/// Index of the next argument for unique name generation purposes.
|
|
|
|
arg_idx: usize,
|
|
|
|
|
|
|
|
/// Typescript expression representing the type of the return value of this
|
|
|
|
/// function.
|
2019-05-30 10:56:59 -07:00
|
|
|
pub ret_ty: String,
|
2018-04-14 09:13:07 -07:00
|
|
|
|
|
|
|
/// Expression used to generate the return value. The string "RET" in this
|
|
|
|
/// expression is replaced with the actual wasm invocation eventually.
|
|
|
|
ret_expr: String,
|
|
|
|
|
|
|
|
/// Name of the JS shim/function that we're generating, primarily for
|
|
|
|
/// TypeScript right now.
|
|
|
|
js_name: String,
|
2018-09-21 15:45:31 -07:00
|
|
|
|
|
|
|
/// whether or not this generated function body will act like a constructor,
|
|
|
|
/// meaning it doesn't actually return something but rather assigns to
|
|
|
|
/// `this`
|
|
|
|
///
|
|
|
|
/// The string value here is the class that this should be a constructor
|
|
|
|
/// for.
|
|
|
|
constructor: Option<String>,
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> Js2Rust<'a, 'b> {
|
|
|
|
pub fn new(js_name: &str, cx: &'a mut Context<'b>) -> Js2Rust<'a, 'b> {
|
|
|
|
Js2Rust {
|
|
|
|
cx,
|
|
|
|
js_name: js_name.to_string(),
|
|
|
|
rust_arguments: Vec::new(),
|
|
|
|
js_arguments: Vec::new(),
|
|
|
|
prelude: String::new(),
|
|
|
|
finally: String::new(),
|
|
|
|
arg_idx: 0,
|
|
|
|
ret_ty: String::new(),
|
|
|
|
ret_expr: String::new(),
|
2018-09-21 15:45:31 -07:00
|
|
|
constructor: None,
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates all bindings necessary for the signature in `Function`,
|
|
|
|
/// creating necessary argument conversions and return value processing.
|
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.
2019-05-23 09:15:26 -07:00
|
|
|
pub fn process(
|
2019-03-14 08:46:42 -03:00
|
|
|
&mut self,
|
|
|
|
function: &Function,
|
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.
2019-05-23 09:15:26 -07:00
|
|
|
opt_arg_names: &Option<Vec<String>>,
|
|
|
|
) -> Result<&mut Self, Error> {
|
2019-05-31 10:54:03 -07:00
|
|
|
let arg_names = match opt_arg_names {
|
|
|
|
Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(),
|
|
|
|
None => vec![None; function.arguments.len()],
|
|
|
|
};
|
|
|
|
assert_eq!(arg_names.len(), function.arguments.len());
|
|
|
|
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
|
|
|
// Process the function argument and assert that the metadata about
|
|
|
|
// the number of arguments on the Rust side required is correct.
|
|
|
|
let before = self.rust_arguments.len();
|
|
|
|
self.argument(arg, arg_name)?;
|
|
|
|
arg.assert_abi_arg_correct(before, self.rust_arguments.len());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process the return argument, and assert that the metadata returned
|
|
|
|
// about the descriptor is indeed correct.
|
|
|
|
let before = self.rust_arguments.len();
|
2018-04-25 11:42:22 -07:00
|
|
|
self.ret(&function.ret)?;
|
2019-05-31 10:54:03 -07:00
|
|
|
function
|
|
|
|
.ret
|
|
|
|
.assert_abi_return_correct(before, self.rust_arguments.len());
|
2018-04-25 11:42:22 -07:00
|
|
|
Ok(self)
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-09-21 15:45:31 -07:00
|
|
|
pub fn constructor(&mut self, class: Option<&str>) -> &mut Self {
|
|
|
|
self.constructor = class.map(|s| s.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
/// Flag this shim as a method call into Rust, so the first Rust argument
|
|
|
|
/// passed should be `this.ptr`.
|
2019-04-30 10:26:03 -03:00
|
|
|
pub fn method(&mut self, consumed: bool) -> &mut Self {
|
|
|
|
if self.cx.config.debug {
|
|
|
|
self.prelude(
|
|
|
|
"if (this.ptr === 0) {
|
|
|
|
throw new Error('Attempt to use a moved value');
|
|
|
|
}",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if consumed {
|
|
|
|
self.prelude(
|
|
|
|
"\
|
|
|
|
const ptr = this.ptr;\n\
|
|
|
|
this.ptr = 0;\n\
|
|
|
|
",
|
|
|
|
);
|
|
|
|
self.rust_arguments.insert(0, "ptr".to_string());
|
|
|
|
} else {
|
|
|
|
self.rust_arguments.insert(0, "this.ptr".to_string());
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add extra processing to the prelude of this shim.
|
|
|
|
pub fn prelude(&mut self, s: &str) -> &mut Self {
|
2018-04-14 09:30:38 -07:00
|
|
|
for line in s.lines() {
|
|
|
|
self.prelude.push_str(line);
|
|
|
|
self.prelude.push_str("\n");
|
|
|
|
}
|
2018-04-14 09:13:07 -07:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add extra processing to the finally block of this shim.
|
|
|
|
pub fn finally(&mut self, s: &str) -> &mut Self {
|
2018-04-14 09:30:38 -07:00
|
|
|
for line in s.lines() {
|
|
|
|
self.finally.push_str(line);
|
|
|
|
self.finally.push_str("\n");
|
|
|
|
}
|
2018-04-14 09:13:07 -07:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add an Rust argument to be passed manually.
|
|
|
|
pub fn rust_argument(&mut self, s: &str) -> &mut Self {
|
|
|
|
self.rust_arguments.push(s.to_string());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-03-14 12:21:41 -03:00
|
|
|
fn abi_arg(&mut self, opt_arg_name: Option<&str>) -> String {
|
2019-03-14 08:46:42 -03:00
|
|
|
let ret = if let Some(x) = opt_arg_name {
|
2019-03-14 12:21:41 -03:00
|
|
|
x.into()
|
2019-03-14 08:46:42 -03:00
|
|
|
} else {
|
|
|
|
format!("arg{}", self.arg_idx)
|
|
|
|
};
|
2018-05-02 21:03:50 -07:00
|
|
|
self.arg_idx += 1;
|
2019-03-14 08:46:42 -03:00
|
|
|
ret
|
2018-05-02 21:03:50 -07:00
|
|
|
}
|
|
|
|
|
2019-05-31 10:54:03 -07:00
|
|
|
fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> {
|
2018-04-14 09:13:07 -07:00
|
|
|
let i = self.arg_idx;
|
2019-05-31 10:54:03 -07:00
|
|
|
let name = self.abi_arg(arg_name);
|
2018-04-14 09:13:07 -07:00
|
|
|
|
2018-07-19 14:44:23 -05:00
|
|
|
let (arg, optional) = match arg {
|
|
|
|
Descriptor::Option(t) => (&**t, true),
|
|
|
|
_ => (arg, false),
|
|
|
|
};
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
if let Some(kind) = arg.vector_kind() {
|
2018-06-27 22:42:34 -07:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::required(name.clone(), kind.js_ty().to_string()));
|
2018-04-14 09:13:07 -07:00
|
|
|
|
2018-04-25 11:42:22 -07:00
|
|
|
let func = self.cx.pass_to_wasm_function(kind)?;
|
2018-07-19 14:44:23 -05:00
|
|
|
let val = if optional {
|
|
|
|
self.cx.expose_is_like_none();
|
|
|
|
format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name)
|
|
|
|
} else {
|
|
|
|
format!("{}({})", func, name)
|
|
|
|
};
|
2018-06-27 22:42:34 -07:00
|
|
|
self.prelude(&format!(
|
2018-11-13 08:10:05 -08:00
|
|
|
"const ptr{i} = {val};\nconst len{i} = WASM_VECTOR_LEN;",
|
2018-06-27 22:42:34 -07:00
|
|
|
i = i,
|
2018-07-19 14:44:23 -05:00
|
|
|
val = val,
|
2018-06-27 22:42:34 -07:00
|
|
|
));
|
2019-06-11 11:51:36 -07:00
|
|
|
if arg.is_by_ref() {
|
2018-07-19 14:44:23 -05:00
|
|
|
if optional {
|
|
|
|
bail!("optional slices aren't currently supported");
|
|
|
|
}
|
2018-05-01 10:06:35 -07:00
|
|
|
if arg.is_mut_ref() {
|
|
|
|
let get = self.cx.memview_function(kind);
|
2018-06-27 22:42:34 -07:00
|
|
|
self.finally(&format!(
|
|
|
|
"\
|
|
|
|
{arg}.set({get}().subarray(\
|
|
|
|
ptr{i} / {size}, \
|
|
|
|
ptr{i} / {size} + len{i}\
|
|
|
|
));\n\
|
|
|
|
",
|
|
|
|
i = i,
|
|
|
|
arg = name,
|
|
|
|
get = get,
|
|
|
|
size = kind.size()
|
|
|
|
));
|
2018-05-01 10:06:35 -07:00
|
|
|
}
|
2018-06-27 22:42:34 -07:00
|
|
|
self.finally(&format!(
|
|
|
|
"\
|
|
|
|
wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\
|
|
|
|
",
|
|
|
|
i = i,
|
|
|
|
size = kind.size()
|
|
|
|
));
|
2018-04-25 11:42:22 -07:00
|
|
|
self.cx.require_internal_export("__wbindgen_free")?;
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
self.rust_arguments.push(format!("ptr{}", i));
|
2018-05-02 21:03:50 -07:00
|
|
|
self.rust_arguments.push(format!("len{}", i));
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-07-19 14:44:23 -05:00
|
|
|
if arg.is_anyref() {
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), "any".to_string()));
|
2018-10-18 08:43:36 -07:00
|
|
|
if self.cx.config.anyref {
|
|
|
|
if optional {
|
|
|
|
self.cx.expose_add_to_anyref_table()?;
|
|
|
|
self.cx.expose_is_like_none();
|
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
|
|
|
|
} else {
|
|
|
|
self.rust_arguments.push(name);
|
|
|
|
}
|
2018-07-19 14:44:23 -05:00
|
|
|
} else {
|
2018-10-18 08:43:36 -07:00
|
|
|
self.cx.expose_add_heap_object();
|
|
|
|
if optional {
|
|
|
|
self.cx.expose_is_like_none();
|
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name));
|
|
|
|
} else {
|
|
|
|
self.rust_arguments.push(format!("addHeapObject({})", name));
|
|
|
|
}
|
2018-07-19 14:44:23 -05:00
|
|
|
}
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if optional {
|
2019-03-26 18:27:49 +00:00
|
|
|
self.cx.expose_is_like_none();
|
|
|
|
|
2018-08-03 18:40:55 +03:00
|
|
|
if arg.is_wasm_native() {
|
2019-01-23 20:23:47 +09:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
2018-08-03 16:28:35 +03:00
|
|
|
|
|
|
|
if self.cx.config.debug {
|
|
|
|
self.cx.expose_assert_num();
|
|
|
|
self.prelude(&format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
if (!isLikeNone({0})) {{
|
|
|
|
_assertNum({0});
|
|
|
|
}}
|
2018-08-03 16:28:35 +03:00
|
|
|
",
|
|
|
|
name
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.rust_arguments.push(format!("!isLikeNone({0})", name));
|
2018-09-26 08:26:00 -07:00
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? 0 : {0}", name));
|
2018-08-03 16:28:35 +03:00
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
2018-08-03 18:40:55 +03:00
|
|
|
if arg.is_abi_as_u32() {
|
2019-01-23 20:23:47 +09:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
2018-08-03 16:28:35 +03:00
|
|
|
|
|
|
|
if self.cx.config.debug {
|
|
|
|
self.cx.expose_assert_num();
|
|
|
|
self.prelude(&format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
if (!isLikeNone({0})) {{
|
|
|
|
_assertNum({0});
|
|
|
|
}}
|
2018-08-03 16:28:35 +03:00
|
|
|
",
|
|
|
|
name
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2018-09-26 08:26:00 -07:00
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", name));
|
2018-08-03 16:28:35 +03:00
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(signed) = arg.get_64() {
|
|
|
|
let f = if signed {
|
|
|
|
self.cx.expose_int64_cvt_shim()
|
|
|
|
} else {
|
|
|
|
self.cx.expose_uint64_cvt_shim()
|
|
|
|
};
|
|
|
|
self.cx.expose_uint32_memory();
|
2019-01-23 20:23:47 +09:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "BigInt".to_string()));
|
2018-08-03 16:28:35 +03:00
|
|
|
self.prelude(&format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
{f}[0] = isLikeNone({name}) ? BigInt(0) : {name};
|
|
|
|
const low{i} = isLikeNone({name}) ? 0 : u32CvtShim[0];
|
|
|
|
const high{i} = isLikeNone({name}) ? 0 : u32CvtShim[1];
|
2018-08-03 16:28:35 +03:00
|
|
|
",
|
|
|
|
i = i,
|
|
|
|
f = f,
|
|
|
|
name = name,
|
|
|
|
));
|
|
|
|
self.rust_arguments.push(format!("!isLikeNone({})", name));
|
|
|
|
self.rust_arguments.push(format!("0"));
|
|
|
|
self.rust_arguments.push(format!("low{}", i));
|
|
|
|
self.rust_arguments.push(format!("high{}", i));
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
2018-08-03 19:07:12 +03:00
|
|
|
match *arg {
|
|
|
|
Descriptor::Boolean => {
|
2018-09-26 08:26:00 -07:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "boolean".to_string()));
|
2018-08-03 19:07:12 +03:00
|
|
|
if self.cx.config.debug {
|
|
|
|
self.cx.expose_assert_bool();
|
|
|
|
self.prelude(&format!(
|
|
|
|
"
|
|
|
|
if (!isLikeNone({0})) {{
|
|
|
|
_assertBoolean({0});
|
|
|
|
}}
|
|
|
|
",
|
|
|
|
name,
|
|
|
|
));
|
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", name));
|
|
|
|
}
|
2018-08-03 20:45:57 +03:00
|
|
|
Descriptor::Char => {
|
2019-01-23 20:23:47 +09:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "string".to_string()));
|
2019-04-16 10:52:27 -07:00
|
|
|
self.rust_arguments.push(format!(
|
|
|
|
"isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
|
|
|
|
name
|
|
|
|
));
|
2018-09-26 08:26:00 -07:00
|
|
|
}
|
2019-01-28 14:12:02 -08:00
|
|
|
Descriptor::Enum { hole } => {
|
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
2019-01-28 14:12:02 -08:00
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("isLikeNone({0}) ? {1} : {0}", name, hole));
|
|
|
|
}
|
2019-02-19 09:08:37 -08:00
|
|
|
Descriptor::RustStruct(ref s) => {
|
2019-03-26 08:00:16 -07:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::optional(name.clone(), s.to_string()));
|
2019-02-19 09:08:37 -08:00
|
|
|
self.prelude(&format!("let ptr{} = 0;", i));
|
2019-03-26 18:27:49 +00:00
|
|
|
self.prelude(&format!("if (!isLikeNone({0})) {{", name));
|
2019-02-19 09:08:37 -08:00
|
|
|
self.assert_class(&name, s);
|
|
|
|
self.assert_not_moved(&name);
|
|
|
|
self.prelude(&format!("ptr{} = {}.ptr;", i, name));
|
|
|
|
self.prelude(&format!("{}.ptr = 0;", name));
|
|
|
|
self.prelude("}");
|
|
|
|
self.rust_arguments.push(format!("ptr{}", i));
|
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
_ => bail!(
|
|
|
|
"unsupported optional argument type for calling Rust function from JS: {:?}",
|
|
|
|
arg
|
|
|
|
),
|
2019-03-26 18:27:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(self);
|
2018-07-19 14:44:23 -05:00
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
if let Some(s) = arg.rust_struct() {
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), s.to_string()));
|
2019-02-19 09:08:37 -08:00
|
|
|
self.assert_class(&name, s);
|
|
|
|
self.assert_not_moved(&name);
|
2018-04-14 09:13:07 -07:00
|
|
|
if arg.is_by_ref() {
|
|
|
|
self.rust_arguments.push(format!("{}.ptr", name));
|
|
|
|
} else {
|
2019-02-19 09:08:37 -08:00
|
|
|
self.prelude(&format!("const ptr{} = {}.ptr;", i, name));
|
|
|
|
self.prelude(&format!("{}.ptr = 0;", name));
|
2018-04-14 09:13:07 -07:00
|
|
|
self.rust_arguments.push(format!("ptr{}", i));
|
|
|
|
}
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2019-03-26 17:29:53 -07:00
|
|
|
if arg.number().is_some() {
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), "number".to_string()));
|
2018-04-14 09:13:07 -07:00
|
|
|
|
|
|
|
if self.cx.config.debug {
|
|
|
|
self.cx.expose_assert_num();
|
2018-04-14 09:30:38 -07:00
|
|
|
self.prelude(&format!("_assertNum({});", name));
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
self.rust_arguments.push(name);
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-08-03 16:28:35 +03:00
|
|
|
if let Some(signed) = arg.get_64() {
|
2018-05-05 14:10:25 -07:00
|
|
|
let f = if signed {
|
|
|
|
self.cx.expose_int64_cvt_shim()
|
|
|
|
} else {
|
|
|
|
self.cx.expose_uint64_cvt_shim()
|
|
|
|
};
|
|
|
|
self.cx.expose_uint32_memory();
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), "BigInt".to_string()));
|
2018-06-27 22:42:34 -07:00
|
|
|
self.prelude(&format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
{f}[0] = {name};
|
|
|
|
const low{i} = u32CvtShim[0];
|
|
|
|
const high{i} = u32CvtShim[1];
|
2018-06-27 22:42:34 -07:00
|
|
|
",
|
2018-05-05 14:10:25 -07:00
|
|
|
i = i,
|
|
|
|
f = f,
|
|
|
|
name = name,
|
|
|
|
));
|
2018-08-03 16:28:35 +03:00
|
|
|
self.rust_arguments.push(format!("low{}", i));
|
|
|
|
self.rust_arguments.push(format!("high{}", i));
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-05-05 14:10:25 -07:00
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
if arg.is_ref_anyref() {
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), "any".to_string()));
|
2018-10-18 08:43:36 -07:00
|
|
|
if self.cx.config.anyref {
|
|
|
|
self.rust_arguments.push(name);
|
|
|
|
} else {
|
|
|
|
// the "stack-ful" nature means that we're always popping from the
|
|
|
|
// stack, and make sure that we actually clear our reference to
|
|
|
|
// allow stale values to get GC'd
|
|
|
|
self.cx.expose_borrowed_objects();
|
|
|
|
self.cx.expose_global_stack_pointer();
|
|
|
|
self.finally("heap[stack_pointer++] = undefined;");
|
|
|
|
self.rust_arguments
|
|
|
|
.push(format!("addBorrowedObject({})", name));
|
|
|
|
}
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
match *arg {
|
|
|
|
Descriptor::Boolean => {
|
2018-06-27 22:42:34 -07:00
|
|
|
self.js_arguments
|
2019-04-25 18:44:28 +02:00
|
|
|
.push(JsArgument::required(name.clone(), "boolean".to_string()));
|
2018-04-14 09:13:07 -07:00
|
|
|
if self.cx.config.debug {
|
|
|
|
self.cx.expose_assert_bool();
|
2018-06-27 22:42:34 -07:00
|
|
|
self.prelude(&format!(
|
|
|
|
"\
|
|
|
|
_assertBoolean({name});\n\
|
|
|
|
",
|
|
|
|
name = name
|
|
|
|
));
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
2018-11-08 13:06:03 -08:00
|
|
|
self.rust_arguments.push(format!("{}", name));
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
2018-05-22 12:34:41 -05:00
|
|
|
Descriptor::Char => {
|
2019-05-28 09:52:44 -05:00
|
|
|
self.js_arguments
|
|
|
|
.push(JsArgument::required(name.clone(), "string".to_string()));
|
2018-05-22 12:34:41 -05:00
|
|
|
self.rust_arguments.push(format!("{}.codePointAt(0)", name))
|
2018-06-27 22:42:34 -07:00
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
_ => bail!(
|
|
|
|
"unsupported argument type for calling Rust function from JS: {:?}",
|
|
|
|
arg
|
|
|
|
),
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
2018-04-25 11:42:22 -07:00
|
|
|
Ok(self)
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2019-05-31 10:54:03 -07:00
|
|
|
fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
2018-09-21 15:45:31 -07:00
|
|
|
if let Some(name) = ty.rust_struct() {
|
|
|
|
match &self.constructor {
|
|
|
|
Some(class) if class == name => {
|
|
|
|
self.ret_expr = format!("this.ptr = RET;");
|
|
|
|
if self.cx.config.weak_refs {
|
2018-09-26 08:26:00 -07:00
|
|
|
self.ret_expr.push_str(&format!(
|
|
|
|
"\
|
2019-05-26 09:30:33 -05:00
|
|
|
{}FinalizationGroup.register(this, this.ptr, this.ptr);
|
|
|
|
",
|
2018-09-26 08:26:00 -07:00
|
|
|
name
|
|
|
|
));
|
2018-09-21 15:45:31 -07:00
|
|
|
}
|
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
Some(class) => bail!("constructor for `{}` cannot return `{}`", class, name),
|
2018-09-21 15:45:31 -07:00
|
|
|
None => {
|
|
|
|
self.ret_ty = name.to_string();
|
|
|
|
self.cx.require_class_wrap(name);
|
|
|
|
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.constructor.is_some() {
|
|
|
|
bail!("constructor functions must return a Rust structure")
|
|
|
|
}
|
|
|
|
|
2018-09-17 18:26:45 -07:00
|
|
|
if let Descriptor::Unit = ty {
|
|
|
|
self.ret_ty = "void".to_string();
|
|
|
|
self.ret_expr = format!("return RET;");
|
|
|
|
return Ok(self);
|
|
|
|
}
|
2018-04-14 09:13:07 -07:00
|
|
|
|
2018-07-19 14:44:23 -05:00
|
|
|
let (ty, optional) = match ty {
|
|
|
|
Descriptor::Option(t) => (&**t, true),
|
|
|
|
_ => (ty, false),
|
|
|
|
};
|
2018-04-14 09:13:07 -07:00
|
|
|
|
|
|
|
if let Some(ty) = ty.vector_kind() {
|
|
|
|
self.ret_ty = ty.js_ty().to_string();
|
2018-10-18 08:43:36 -07:00
|
|
|
let f = self.cx.expose_get_vector_from_wasm(ty)?;
|
2018-05-02 21:03:50 -07:00
|
|
|
self.cx.expose_global_argument_ptr()?;
|
|
|
|
self.cx.expose_uint32_memory();
|
2018-04-25 11:42:22 -07:00
|
|
|
self.cx.require_internal_export("__wbindgen_free")?;
|
2018-05-02 21:03:50 -07:00
|
|
|
self.prelude("const retptr = globalArgumentPtr();");
|
|
|
|
self.rust_arguments.insert(0, "retptr".to_string());
|
2018-06-27 22:42:34 -07:00
|
|
|
self.ret_expr = format!(
|
|
|
|
"\
|
|
|
|
RET;\n\
|
|
|
|
const mem = getUint32Memory();\n\
|
2018-08-07 07:53:30 -07:00
|
|
|
const rustptr = mem[retptr / 4];\n\
|
|
|
|
const rustlen = mem[retptr / 4 + 1];\n\
|
2018-07-19 14:44:23 -05:00
|
|
|
{guard}
|
2018-08-07 07:53:30 -07:00
|
|
|
const realRet = {}(rustptr, rustlen).slice();\n\
|
|
|
|
wasm.__wbindgen_free(rustptr, rustlen * {});\n\
|
2018-06-27 22:42:34 -07:00
|
|
|
return realRet;\n\
|
|
|
|
",
|
|
|
|
f,
|
2018-07-19 14:44:23 -05:00
|
|
|
ty.size(),
|
2018-09-26 08:26:00 -07:00
|
|
|
guard = if optional {
|
|
|
|
"if (rustptr === 0) return;"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
},
|
2018-06-27 22:42:34 -07:00
|
|
|
);
|
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-07-19 14:44:23 -05:00
|
|
|
// No need to worry about `optional` here, the abi representation means
|
|
|
|
// that `takeObject` will naturally pluck out `undefined`.
|
|
|
|
if ty.is_anyref() {
|
|
|
|
self.ret_ty = "any".to_string();
|
2018-10-18 08:43:36 -07:00
|
|
|
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
2018-07-19 14:44:23 -05:00
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if optional {
|
2018-08-03 18:40:55 +03:00
|
|
|
if ty.is_wasm_native() {
|
2018-11-04 10:26:20 +01:00
|
|
|
self.ret_ty = "number | undefined".to_string();
|
2018-08-03 16:28:35 +03:00
|
|
|
self.cx.expose_global_argument_ptr()?;
|
|
|
|
self.cx.expose_uint32_memory();
|
|
|
|
match ty {
|
|
|
|
Descriptor::I32 => self.cx.expose_int32_memory(),
|
|
|
|
Descriptor::U32 => (),
|
|
|
|
Descriptor::F32 => self.cx.expose_f32_memory(),
|
|
|
|
Descriptor::F64 => self.cx.expose_f64_memory(),
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
self.prelude("const retptr = globalArgumentPtr();");
|
|
|
|
self.rust_arguments.insert(0, "retptr".to_string());
|
|
|
|
self.ret_expr = format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
RET;
|
|
|
|
const present = getUint32Memory()[retptr / 4];
|
|
|
|
const value = {mem}[retptr / {size} + 1];
|
|
|
|
return present === 0 ? undefined : value;
|
2018-08-03 16:28:35 +03:00
|
|
|
",
|
|
|
|
size = match ty {
|
|
|
|
Descriptor::I32 => 4,
|
|
|
|
Descriptor::U32 => 4,
|
|
|
|
Descriptor::F32 => 4,
|
|
|
|
Descriptor::F64 => 8,
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
mem = match ty {
|
|
|
|
Descriptor::I32 => "getInt32Memory()",
|
|
|
|
Descriptor::U32 => "getUint32Memory()",
|
|
|
|
Descriptor::F32 => "getFloat32Memory()",
|
|
|
|
Descriptor::F64 => "getFloat64Memory()",
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
2018-08-03 18:40:55 +03:00
|
|
|
if ty.is_abi_as_u32() {
|
2018-11-04 10:26:20 +01:00
|
|
|
self.ret_ty = "number | undefined".to_string();
|
2018-08-03 16:28:35 +03:00
|
|
|
self.ret_expr = "
|
|
|
|
const ret = RET;
|
|
|
|
return ret === 0xFFFFFF ? undefined : ret;
|
2018-11-27 12:07:59 -08:00
|
|
|
"
|
|
|
|
.to_string();
|
2018-08-03 16:28:35 +03:00
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(signed) = ty.get_64() {
|
2018-11-04 10:26:20 +01:00
|
|
|
self.ret_ty = "BigInt | undefined".to_string();
|
2018-08-03 16:28:35 +03:00
|
|
|
self.cx.expose_global_argument_ptr()?;
|
|
|
|
let f = if signed {
|
|
|
|
self.cx.expose_int64_memory();
|
|
|
|
"getInt64Memory"
|
|
|
|
} else {
|
|
|
|
self.cx.expose_uint64_memory();
|
|
|
|
"getUint64Memory"
|
|
|
|
};
|
|
|
|
self.prelude("const retptr = globalArgumentPtr();");
|
|
|
|
self.rust_arguments.insert(0, "retptr".to_string());
|
|
|
|
self.ret_expr = format!(
|
2018-08-03 18:40:55 +03:00
|
|
|
"
|
|
|
|
RET;
|
|
|
|
const present = getUint32Memory()[retptr / 4];
|
|
|
|
const value = {}()[retptr / 8 + 1];
|
|
|
|
return present === 0 ? undefined : value;
|
2018-08-03 16:28:35 +03:00
|
|
|
",
|
|
|
|
f
|
|
|
|
);
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
2018-08-03 19:07:12 +03:00
|
|
|
match *ty {
|
|
|
|
Descriptor::Boolean => {
|
2018-11-04 10:26:20 +01:00
|
|
|
self.ret_ty = "boolean | undefined".to_string();
|
2018-08-03 19:07:12 +03:00
|
|
|
self.ret_expr = "
|
|
|
|
const ret = RET;
|
|
|
|
return ret === 0xFFFFFF ? undefined : ret !== 0;
|
2018-11-27 12:07:59 -08:00
|
|
|
"
|
|
|
|
.to_string();
|
2018-08-03 20:45:57 +03:00
|
|
|
return Ok(self);
|
2018-09-26 08:26:00 -07:00
|
|
|
}
|
2018-08-03 20:45:57 +03:00
|
|
|
Descriptor::Char => {
|
2018-11-04 10:26:20 +01:00
|
|
|
self.ret_ty = "string | undefined".to_string();
|
2018-08-03 20:45:57 +03:00
|
|
|
self.ret_expr = "
|
2019-03-26 18:12:24 +00:00
|
|
|
const ret = RET;
|
|
|
|
return ret === 0xFFFFFF ? undefined : String.fromCodePoint(ret);
|
2019-04-16 10:52:27 -07:00
|
|
|
"
|
|
|
|
.to_string();
|
2018-08-03 19:07:12 +03:00
|
|
|
return Ok(self);
|
2018-09-26 08:26:00 -07:00
|
|
|
}
|
2019-01-28 14:12:02 -08:00
|
|
|
Descriptor::Enum { hole } => {
|
|
|
|
self.ret_ty = "number | undefined".to_string();
|
Migrate `wasm-bindgen` to using `walrus`
This commit moves `wasm-bindgen` the CLI tool from internally using
`parity-wasm` for wasm parsing/serialization to instead use `walrus`.
The `walrus` crate is something we've been working on recently with an
aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the
current CLI tool more maintainable as well as more future-proof.
The `walrus` crate provides a much nicer AST to work with as well as a
structured `Module`, whereas `parity-wasm` provides a very raw interface
to the wasm module which isn't really appropriate for our use case. The
many transformations and tweaks that wasm-bindgen does have a huge
amount of ad-hoc index management to carefully craft a final wasm
binary, but this is all entirely taken care for us with the `walrus`
crate.
Additionally, `wasm-bindgen` will ingest and rewrite the wasm file,
often changing the binary offsets of functions. Eventually with DWARF
debug information we'll need to be sure to preserve the debug
information throughout the transformations that `wasm-bindgen` does
today. This is practically impossible to do with the `parity-wasm`
architecture, but `walrus` was designed from the get-go to solve this
problem transparently in the `walrus` crate itself. (it doesn't today,
but this is planned work)
It is the intention that this does not end up regressing any
`wasm-bindgen` use cases, neither in functionality or in speed. As a
large change and refactoring, however, it's likely that at least
something will arise! We'll want to continue to remain vigilant to any
issues that come up with this commit.
Note that the `gc` crate has been deleted as part of this change, as the
`gc` crate is no longer necessary since `walrus` does it automatically.
Additionally the `gc` crate was one of the main problems with preserving
debug information as it often deletes wasm items!
Finally, this also starts moving crates to the 2018 edition where
necessary since `walrus` requires the 2018 edition, and in general it's
more pleasant to work within the 2018 edition!
2019-01-31 09:54:23 -08:00
|
|
|
self.ret_expr = format!(
|
|
|
|
"
|
2019-01-28 14:12:02 -08:00
|
|
|
const ret = RET;
|
|
|
|
return ret === {} ? undefined : ret;
|
Migrate `wasm-bindgen` to using `walrus`
This commit moves `wasm-bindgen` the CLI tool from internally using
`parity-wasm` for wasm parsing/serialization to instead use `walrus`.
The `walrus` crate is something we've been working on recently with an
aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the
current CLI tool more maintainable as well as more future-proof.
The `walrus` crate provides a much nicer AST to work with as well as a
structured `Module`, whereas `parity-wasm` provides a very raw interface
to the wasm module which isn't really appropriate for our use case. The
many transformations and tweaks that wasm-bindgen does have a huge
amount of ad-hoc index management to carefully craft a final wasm
binary, but this is all entirely taken care for us with the `walrus`
crate.
Additionally, `wasm-bindgen` will ingest and rewrite the wasm file,
often changing the binary offsets of functions. Eventually with DWARF
debug information we'll need to be sure to preserve the debug
information throughout the transformations that `wasm-bindgen` does
today. This is practically impossible to do with the `parity-wasm`
architecture, but `walrus` was designed from the get-go to solve this
problem transparently in the `walrus` crate itself. (it doesn't today,
but this is planned work)
It is the intention that this does not end up regressing any
`wasm-bindgen` use cases, neither in functionality or in speed. As a
large change and refactoring, however, it's likely that at least
something will arise! We'll want to continue to remain vigilant to any
issues that come up with this commit.
Note that the `gc` crate has been deleted as part of this change, as the
`gc` crate is no longer necessary since `walrus` does it automatically.
Additionally the `gc` crate was one of the main problems with preserving
debug information as it often deletes wasm items!
Finally, this also starts moving crates to the 2018 edition where
necessary since `walrus` requires the 2018 edition, and in general it's
more pleasant to work within the 2018 edition!
2019-01-31 09:54:23 -08:00
|
|
|
",
|
|
|
|
hole
|
|
|
|
);
|
2019-01-28 14:12:02 -08:00
|
|
|
return Ok(self);
|
|
|
|
}
|
2019-02-19 09:08:37 -08:00
|
|
|
Descriptor::RustStruct(ref name) => {
|
|
|
|
self.ret_ty = format!("{} | undefined", name);
|
|
|
|
self.cx.require_class_wrap(name);
|
2019-03-26 08:00:16 -07:00
|
|
|
self.ret_expr = format!(
|
|
|
|
"
|
2019-02-19 09:08:37 -08:00
|
|
|
const ptr = RET;
|
|
|
|
return ptr === 0 ? undefined : {}.__wrap(ptr);
|
|
|
|
",
|
|
|
|
name,
|
|
|
|
);
|
|
|
|
return Ok(self);
|
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
_ => bail!(
|
|
|
|
"unsupported optional return type for calling Rust function from JS: {:?}",
|
|
|
|
ty
|
|
|
|
),
|
2018-08-03 19:07:12 +03:00
|
|
|
};
|
2018-07-19 14:44:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if ty.is_ref_anyref() {
|
|
|
|
self.ret_ty = "any".to_string();
|
|
|
|
self.cx.expose_get_object();
|
|
|
|
self.ret_expr = format!("return getObject(RET);");
|
|
|
|
return Ok(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ty.is_by_ref() {
|
|
|
|
bail!("cannot return references from Rust to JS yet")
|
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
if let Some(name) = ty.rust_struct() {
|
|
|
|
self.ret_ty = name.to_string();
|
2018-09-21 15:45:31 -07:00
|
|
|
self.cx.require_class_wrap(name);
|
|
|
|
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2019-03-26 17:29:53 -07:00
|
|
|
if let Some(num) = ty.number() {
|
2018-04-14 09:13:07 -07:00
|
|
|
self.ret_ty = "number".to_string();
|
2019-03-26 17:29:53 -07:00
|
|
|
if num.is_u32() {
|
|
|
|
self.ret_expr = format!("return RET >>> 0;");
|
|
|
|
} else {
|
|
|
|
self.ret_expr = format!("return RET;");
|
|
|
|
}
|
2018-06-27 22:42:34 -07:00
|
|
|
return Ok(self);
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-08-03 16:28:35 +03:00
|
|
|
if let Some(signed) = ty.get_64() {
|
2018-05-05 14:10:25 -07:00
|
|
|
self.ret_ty = "BigInt".to_string();
|
|
|
|
self.cx.expose_global_argument_ptr()?;
|
|
|
|
let f = if signed {
|
|
|
|
self.cx.expose_int64_memory();
|
|
|
|
"getInt64Memory"
|
|
|
|
} else {
|
|
|
|
self.cx.expose_uint64_memory();
|
|
|
|
"getUint64Memory"
|
|
|
|
};
|
|
|
|
self.prelude("const retptr = globalArgumentPtr();");
|
|
|
|
self.rust_arguments.insert(0, "retptr".to_string());
|
2018-06-27 22:42:34 -07:00
|
|
|
self.ret_expr = format!(
|
|
|
|
"\
|
|
|
|
RET;\n\
|
|
|
|
return {}()[retptr / 8];\n\
|
|
|
|
",
|
|
|
|
f
|
|
|
|
);
|
|
|
|
return Ok(self);
|
2018-05-05 14:10:25 -07:00
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
match *ty {
|
|
|
|
Descriptor::Boolean => {
|
|
|
|
self.ret_ty = "boolean".to_string();
|
|
|
|
self.ret_expr = format!("return (RET) !== 0;");
|
|
|
|
}
|
2018-05-22 12:34:41 -05:00
|
|
|
Descriptor::Char => {
|
|
|
|
self.ret_ty = "string".to_string();
|
2018-06-15 12:55:37 -05:00
|
|
|
self.ret_expr = format!("return String.fromCodePoint(RET);")
|
2018-05-22 12:34:41 -05:00
|
|
|
}
|
2018-09-26 08:26:00 -07:00
|
|
|
_ => bail!(
|
|
|
|
"unsupported return type for calling Rust function from JS: {:?}",
|
|
|
|
ty
|
|
|
|
),
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
2018-04-25 11:42:22 -07:00
|
|
|
Ok(self)
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
|
|
|
|
2018-07-09 11:07:57 -05:00
|
|
|
pub fn js_doc_comments(&self) -> String {
|
2018-09-26 08:26:00 -07:00
|
|
|
let mut ret: String = self
|
|
|
|
.js_arguments
|
|
|
|
.iter()
|
2019-05-28 09:52:44 -05:00
|
|
|
.map(|a| {
|
|
|
|
if a.optional {
|
|
|
|
format!("@param {{{} | undefined}} {}\n", a.type_, a.name)
|
|
|
|
} else {
|
|
|
|
format!("@param {{{}}} {}\n", a.type_, a.name)
|
|
|
|
}
|
2019-04-25 18:44:28 +02:00
|
|
|
})
|
2018-09-26 08:26:00 -07:00
|
|
|
.collect();
|
2018-07-09 11:07:57 -05:00
|
|
|
ret.push_str(&format!("@returns {{{}}}", self.ret_ty));
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2018-04-14 09:13:07 -07:00
|
|
|
/// Generate the actual function.
|
|
|
|
///
|
|
|
|
/// The `prefix` specified is typically the string "function" but may be
|
|
|
|
/// different for classes. The `invoc` is the function expression that we're
|
|
|
|
/// invoking, like `wasm.bar` or `this.f`.
|
|
|
|
///
|
|
|
|
/// Returns two strings, the first of which is the JS expression for the
|
2018-05-22 12:34:41 -05:00
|
|
|
/// generated function shim and the second is a TypeScript signature of the
|
2018-04-14 09:13:07 -07:00
|
|
|
/// JS expression.
|
2019-05-31 10:54:03 -07:00
|
|
|
pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) {
|
2018-06-27 22:42:34 -07:00
|
|
|
let js_args = self
|
|
|
|
.js_arguments
|
2018-04-14 09:13:07 -07:00
|
|
|
.iter()
|
2019-04-25 18:44:28 +02:00
|
|
|
.map(|s| &s.name[..])
|
2018-04-14 09:13:07 -07:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(", ");
|
|
|
|
let mut js = format!("{}({}) {{\n", prefix, js_args);
|
2018-06-15 12:55:37 -05:00
|
|
|
js.push_str(&self.prelude);
|
2018-04-14 09:13:07 -07:00
|
|
|
let rust_args = self.rust_arguments.join(", ");
|
|
|
|
|
2018-06-27 22:42:34 -07:00
|
|
|
let invoc = self
|
|
|
|
.ret_expr
|
|
|
|
.replace("RET", &format!("{}({})", invoc, rust_args));
|
2018-04-14 09:30:38 -07:00
|
|
|
let invoc = if self.finally.len() == 0 {
|
|
|
|
invoc
|
2018-04-14 09:13:07 -07:00
|
|
|
} else {
|
2018-06-27 22:42:34 -07:00
|
|
|
format!(
|
|
|
|
"\
|
2018-04-14 09:30:38 -07:00
|
|
|
try {{\n\
|
2018-06-15 12:55:37 -05:00
|
|
|
{}
|
|
|
|
\n}} finally {{\n\
|
|
|
|
{}
|
2018-04-14 09:30:38 -07:00
|
|
|
}}\n\
|
2018-06-15 23:39:51 -07:00
|
|
|
",
|
2018-06-27 22:42:34 -07:00
|
|
|
&invoc, &self.finally,
|
2018-04-14 09:30:38 -07:00
|
|
|
)
|
|
|
|
};
|
2018-06-15 12:55:37 -05:00
|
|
|
js.push_str(&invoc);
|
|
|
|
js.push_str("\n}");
|
2019-05-09 18:22:44 +02:00
|
|
|
|
|
|
|
// Determine TS parameter list
|
|
|
|
let mut omittable = true;
|
|
|
|
let mut ts_args = Vec::with_capacity(self.js_arguments.len());
|
|
|
|
for arg in self.js_arguments.iter().rev() {
|
|
|
|
// In TypeScript, we can mark optional parameters as omittable
|
|
|
|
// using the `?` suffix, but only if they're not followed by
|
|
|
|
// non-omittable parameters. Therefore iterate the parameter list
|
|
|
|
// in reverse and stop using the `?` suffix for optional params as
|
|
|
|
// soon as a non-optional parameter is encountered.
|
|
|
|
if arg.optional {
|
|
|
|
if omittable {
|
|
|
|
ts_args.push(format!("{}?: {}", arg.name, arg.type_));
|
|
|
|
} else {
|
|
|
|
ts_args.push(format!("{}: {} | undefined", arg.name, arg.type_));
|
|
|
|
}
|
2019-04-25 18:44:28 +02:00
|
|
|
} else {
|
2019-05-09 18:22:44 +02:00
|
|
|
omittable = false;
|
|
|
|
ts_args.push(format!("{}: {}", arg.name, arg.type_));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ts_args.reverse();
|
|
|
|
let ts_args = ts_args.join(", ");
|
|
|
|
|
2019-02-01 16:07:12 +09:00
|
|
|
let mut ts = if prefix.is_empty() {
|
|
|
|
format!("{}({})", self.js_name, ts_args)
|
|
|
|
} else {
|
|
|
|
format!("{} {}({})", prefix, self.js_name, ts_args)
|
|
|
|
};
|
2018-10-03 00:00:34 -07:00
|
|
|
if self.constructor.is_none() {
|
|
|
|
ts.push_str(": ");
|
|
|
|
ts.push_str(&self.ret_ty);
|
|
|
|
}
|
2019-02-01 16:07:31 +09:00
|
|
|
ts.push(';');
|
2018-10-18 08:43:36 -07:00
|
|
|
|
2018-07-09 11:07:57 -05:00
|
|
|
(js, ts, self.js_doc_comments())
|
2018-04-14 09:13:07 -07:00
|
|
|
}
|
2019-02-19 09:08:37 -08:00
|
|
|
|
|
|
|
fn assert_class(&mut self, arg: &str, class: &str) {
|
|
|
|
if !self.cx.config.debug {
|
2019-03-26 08:00:16 -07:00
|
|
|
return;
|
2019-02-19 09:08:37 -08:00
|
|
|
}
|
|
|
|
self.cx.expose_assert_class();
|
|
|
|
self.prelude(&format!("_assertClass({}, {});", arg, class));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assert_not_moved(&mut self, arg: &str) {
|
|
|
|
if !self.cx.config.debug {
|
2019-03-26 08:00:16 -07:00
|
|
|
return;
|
2019-02-19 09:08:37 -08:00
|
|
|
}
|
|
|
|
self.prelude(&format!(
|
|
|
|
"\
|
|
|
|
if ({0}.ptr === 0) {{
|
|
|
|
throw new Error('Attempt to use a moved value');
|
|
|
|
}}
|
|
|
|
",
|
|
|
|
arg,
|
|
|
|
));
|
|
|
|
}
|
2018-06-27 22:42:34 -07:00
|
|
|
}
|