mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2025-01-05 02:57:45 +03:00
Update DESIGN.md on recent closure/conversion changes
Hopefully covering a lot of the groundwork done for closures in case anyone's curious down the road!
This commit is contained in:
parent
5efde3abe9
commit
b45ab77972
209
DESIGN.md
209
DESIGN.md
@ -385,8 +385,9 @@ happening:
|
|||||||
free the space we allocated to pass the string argument once the function call
|
free the space we allocated to pass the string argument once the function call
|
||||||
is done.
|
is done.
|
||||||
|
|
||||||
At this point it may be predictable, but let's take a look at the Rust side of
|
Next let's take a look at the Rust side of things as well. Here we'll be looking
|
||||||
things as well
|
at a mostly abbreviated and/or "simplified" in the sense of this is what it
|
||||||
|
compiles down to:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub extern fn greet(a: &str) -> String {
|
pub extern fn greet(a: &str) -> String {
|
||||||
@ -469,7 +470,8 @@ name (was the function import in Rust said) and then the `__wbg_f_greet`
|
|||||||
function is shimming that import.
|
function is shimming that import.
|
||||||
|
|
||||||
There's some tricky ABI business going on here so let's take a look at the
|
There's some tricky ABI business going on here so let's take a look at the
|
||||||
generated Rust as well:
|
generated Rust as well. Like before this is simplified from what's actually
|
||||||
|
generated.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern fn greet(a: &str) -> String {
|
extern fn greet(a: &str) -> String {
|
||||||
@ -1042,48 +1044,181 @@ possibilities!
|
|||||||
All of these functions will call `console.log` in Rust, but each identifier
|
All of these functions will call `console.log` in Rust, but each identifier
|
||||||
will have only one signature in Rust.
|
will have only one signature in Rust.
|
||||||
|
|
||||||
## Closures
|
## Rust Type conversions
|
||||||
|
|
||||||
Closures are a particularly tricky topic in wasm-bindgen right now. They use
|
Previously we've been seeing mostly abridged versions of type conversions when
|
||||||
somewhat advanced language features to currently be implemented and *still* the
|
values enter Rust. Here we'll go into some more depth about how this is
|
||||||
amount of functionality you can use is quite limiting.
|
implemented. There are two categories of traits for converting values, traits
|
||||||
|
for converting values from Rust to JS and traits for the other way around.
|
||||||
|
|
||||||
Most of the implementation details of closures can be found in `src/convert.rs`
|
### From Rust to JS
|
||||||
and `src/closure.rs`, effectively the `ToRefWasmBoundary` implementations for
|
|
||||||
closure types. Stack closures are pretty straightforward in that they pass
|
|
||||||
a function pointer and a data pointer to JS. This function pointer is accessed
|
|
||||||
via the exported `WebAssembly.Table` in JS, and the data pointer is passed along
|
|
||||||
eventually when the JS closure is invoked.
|
|
||||||
|
|
||||||
Stack closures currently only support `Fn` because there's no great location to
|
First up let's take a look at going from Rust to JS:
|
||||||
insert a `RefCell` for types like `FnMut`. This restriction may be lift-able
|
|
||||||
though in the future...
|
|
||||||
|
|
||||||
Long-lived closures are a bit more complicated. The general idea there is:
|
```rust
|
||||||
|
pub trait IntoWasmAbi: WasmDescribe {
|
||||||
|
type Abi: WasmAbi;
|
||||||
|
fn into_abi(self, extra: &mut Stack) -> Self::Abi;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* First you create a `Closure`. This manufactures a JS callback and "passes it"
|
And that's it! This is actually the only trait needed currently for translating
|
||||||
to Rust so Rust can store it.
|
a Rust value to a JS one. There's a few points here:
|
||||||
* Next you later pass it as `&Closure<...>` to JS. This extracts the callback
|
|
||||||
from Rust and passes it to JS.
|
|
||||||
* Finally you eventually drop the Rust `Closure` which invalidates the JS
|
|
||||||
closure.
|
|
||||||
|
|
||||||
Creation of the initial JS function is done with a bunch of
|
* We'll get to `WasmDescribe` later in this section
|
||||||
`__wbindgen_cb_arityN` functions. These functions create a JS closure with the
|
* The associated type `Abi` is what will actually be generated as an argument to
|
||||||
given arity (number of arguments). This isn't really that scalable unfortunately
|
the wasm export. The bound `WasmAbi` is only implemented for types like `u32`
|
||||||
and also means that it's very difficult to support richer types one day. Unsure
|
and `f64`, those which can be placed on the boundary and transmitted
|
||||||
how to solve this.
|
losslessly.
|
||||||
|
* And finally we have the `into_abi` function, returning the `Abi` associated
|
||||||
|
type which will be actually passed to JS. There's also this `Stack` parameter,
|
||||||
|
however. Not all Rust values can be communicated in 32 bits to the `Stack`
|
||||||
|
parameter allows transmitting more data, explained in a moment.
|
||||||
|
|
||||||
The `ToRefWasmBoundary` is quite straightforward for `Closure` as it just plucks
|
This trait is implemented for all types that can be converted to JS and is
|
||||||
out the JS closure and passes it along. The real meat comes down to the
|
unconditionally used during codegen. For example you'll often see `IntoWasmAbi
|
||||||
`WasmShim` internal trait. This is implemented for all the *unsized* closure
|
for Foo` but also `IntoWasmAbi for &'a Foo`.
|
||||||
types to avoid running afoul with coherence. Each trait impl defines a shim
|
|
||||||
function to be invokeable from JS as well as the ability to wrap up the sized
|
|
||||||
verion (aka transition from `F: FnMut()` to `FnMut()`). Impls for `FnMut` also
|
|
||||||
embed the `RefCell` internally.
|
|
||||||
|
|
||||||
The `WasmShim` design is basically the first thing that got working today. It's
|
The `IntoWasmAbi` trait is used in two locations. First it's used to convert
|
||||||
not great and will likely change in the future to hopefully be more flexible!
|
return values of Rust exported functions to JS. Second it's used to convert the
|
||||||
|
Rust arguments of JS functions imported to Rust.
|
||||||
|
|
||||||
|
### From JS to Rust
|
||||||
|
|
||||||
|
Unfortunately the opposite direction from above, going from JS to Rust, is a bit
|
||||||
|
mroe complicated. Here we've got three traits:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait FromWasmAbi: WasmDescribe {
|
||||||
|
type Abi: WasmAbi;
|
||||||
|
unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RefFromWasmAbi: WasmDescribe {
|
||||||
|
type Abi: WasmAbi;
|
||||||
|
type Anchor: Deref<Target=Self>;
|
||||||
|
unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RefMutFromWasmAbi: WasmDescribe {
|
||||||
|
type Abi: WasmAbi;
|
||||||
|
type Anchor: DerefMut<Target=Self>;
|
||||||
|
unsafe fn ref_mut_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `FromWasmAbi` is relatively straightforward, basically the opposite of
|
||||||
|
`IntoWasmAbi`. It takes the ABI argument (typically the same as
|
||||||
|
`IntoWasmAbi::Abi`) and then the auxiliary stack to produce an instance of
|
||||||
|
`Self`. This trait is implemented primarily for types that *don't* have internal
|
||||||
|
lifetimes or are references.
|
||||||
|
|
||||||
|
The latter two traits here are mostly the same, and are intended for generating
|
||||||
|
references (both shared and mutable references). They look almost the same as
|
||||||
|
`FromWasmAbi` except that they return an `Anchor` type which implements a
|
||||||
|
`Deref` trait rather than `Self`.
|
||||||
|
|
||||||
|
The `Ref*` traits allow having arguments in functions that are references rather
|
||||||
|
than bare types, for example `&str`, `&JsValue`, or `&[u8]`. The `Anchor` here
|
||||||
|
is required to ensure that the lifetimes don't persist beyond one function call
|
||||||
|
and remain anonymous.
|
||||||
|
|
||||||
|
The `From*` family of traits are used for converting the Rust arguments in Rust
|
||||||
|
exported functions to JS. They are also used for the return value in JS
|
||||||
|
functions imported into Rust.
|
||||||
|
|
||||||
|
### Global stack
|
||||||
|
|
||||||
|
Mentioned above not all Rust types will fit within 32 bits. While we can
|
||||||
|
communicate an `f64` we don't necessarily have the ability to use all the bits.
|
||||||
|
Types like `&str` need to communicate two items, a pointer and a length (64
|
||||||
|
bits). Other types like `&Closure<Fn()>` have even more information to
|
||||||
|
transmit.
|
||||||
|
|
||||||
|
As a result we need a method of communicating more data through the signatures
|
||||||
|
of functions. While we could add more arguments this is somewhat difficult to do
|
||||||
|
in the world of closures where code generation isn't quite as dynamic as a
|
||||||
|
procedural macro. Consequently a "global stack" is used to transmit extra
|
||||||
|
data for a function call.
|
||||||
|
|
||||||
|
The global stack is a fixed-sized static allocation in the wasm module. This
|
||||||
|
stack is temporary scratch space for any one function call from either JS to
|
||||||
|
Rust or Rust ot JS. Both Rust and the JS shim generated have pointers to this
|
||||||
|
global stack and will read/write information from it.
|
||||||
|
|
||||||
|
Using this scheme whenever we want to pass `&str` from JS to Rust we can pass
|
||||||
|
the pointer as the actual ABI argument and the length is then placed in the next
|
||||||
|
spot on the global stack.
|
||||||
|
|
||||||
|
The `Stack` argument to the conversion traits above looks like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait Stack {
|
||||||
|
fn push(&mut self, bits: u32);
|
||||||
|
fn pop(&mut self) -> u32;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A trait is used here to facilitate testing but typically the calls don't end up
|
||||||
|
being virtually dispatched at runtime.
|
||||||
|
|
||||||
|
### Communicating types to `wasm-bindgen`
|
||||||
|
|
||||||
|
The last aspect to talk about when converting Rust/JS types amongst one another
|
||||||
|
is how this information is actually communicated. The `#[wasm_bindgen]` macro is
|
||||||
|
running over the syntactical (unresolved) structure of the Rust code and is then
|
||||||
|
responsible for generating information that `wasm-bindgen` the CLI tool later
|
||||||
|
reads.
|
||||||
|
|
||||||
|
To accomplish this a slightly unconventional approach is taken. Static
|
||||||
|
information about the structure of the Rust code is serialized via JSON
|
||||||
|
(currently) to a custom section of the wasm executable. Other information, like
|
||||||
|
what the types actually are, unfortunately isn't known until later in the
|
||||||
|
compiler due to things like associated type projections and typedefs. It also
|
||||||
|
turns out that we want to convey "rich" types like `FnMut(String, Foo,
|
||||||
|
&JsValue)` to the `wasm-bindgen` CLI, and handling all this is pretty tricky!
|
||||||
|
|
||||||
|
To solve this issue the `#[wasm_bindgen]` macro generates **executable
|
||||||
|
functions** which "describe the type signature of an import or export". These
|
||||||
|
executable functions are what the `WasmDescribe` trait is all about:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait WasmDescribe {
|
||||||
|
fn describe();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
While deceptively simple this trait is actually quite important. When you write,
|
||||||
|
an export like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[wasm_bindgen]
|
||||||
|
fn greet(a: &str) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to the shims we talked about above which JS generates the macro
|
||||||
|
*also* generates something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn __wbindgen_describe_greet() {
|
||||||
|
<Fn(&str)>::describe();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in other words it generates invocations of `describe` functions. In doing so
|
||||||
|
the `__wbindgen_describe_greet` shim is a programmatic description of the type
|
||||||
|
layouts of an import/export. These are then executed when `wasm-bindgen` runs!
|
||||||
|
These executions rely on an import called `__wbindgen_describe` which passes one
|
||||||
|
`u32` to the host, and when called multiple times gives a `Vec<u32>`
|
||||||
|
effectively. This `Vec<u32>` can then be reparsed into an `enum Descriptor`
|
||||||
|
which fully describes a type.
|
||||||
|
|
||||||
|
All in all this is a bit roundabout but shouldn't have any impact on the
|
||||||
|
generated code or runtime at all. All these descriptor functions are pruned from
|
||||||
|
the emitted wasm file.
|
||||||
|
|
||||||
## Wrapping up
|
## Wrapping up
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user