2018-07-02 16:11:21 -07:00
|
|
|
# Polyfill for "JS objects in wasm"
|
|
|
|
|
|
|
|
One of the main goals of `wasm-bindgen` is to allow working with and passing
|
|
|
|
around JS objects in wasm, but that's not allowed today! While indeed true,
|
|
|
|
that's where the polyfill comes in.
|
|
|
|
|
|
|
|
The question here is how we shoehorn JS objects into a `u32` for wasm to use.
|
|
|
|
The current strategy for this approach is to maintain two module-local variables
|
|
|
|
in the generated `foo.js` file: a stack and a heap.
|
|
|
|
|
|
|
|
### Temporary JS objects on the stack
|
|
|
|
|
|
|
|
The stack in `foo.js` is, well, a stack. JS objects are pushed on the top of the
|
|
|
|
stack, and their index in the stack is the identifier that's passed to wasm. JS
|
|
|
|
objects are then only removed from the top of the stack as well. This data
|
|
|
|
structure is mainly useful for efficiently passing a JS object into wasm without
|
|
|
|
a sort of "heap allocation". The downside of this, however, is that it only
|
|
|
|
works for when wasm doesn't hold onto a JS object (aka it only gets a
|
|
|
|
"reference" in Rust parlance).
|
|
|
|
|
|
|
|
Let's take a look at an example.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
// foo.rs
|
|
|
|
#[wasm_bindgen]
|
|
|
|
pub fn foo(a: &JsValue) {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Here we're using the special `JsValue` type from the `wasm-bindgen` library
|
|
|
|
itself. Our exported function, `foo`, takes a *reference* to an object. This
|
|
|
|
notably means that it can't persist the object past the lifetime of this
|
|
|
|
function call.
|
|
|
|
|
|
|
|
Now what we actually want to generate is a JS module that looks like (in
|
|
|
|
Typescript parlance)
|
|
|
|
|
|
|
|
```ts
|
|
|
|
// foo.d.ts
|
|
|
|
export function foo(a: any);
|
|
|
|
```
|
|
|
|
|
|
|
|
and what we actually generate looks something like:
|
|
|
|
|
|
|
|
```js
|
|
|
|
// foo.js
|
|
|
|
import * as wasm from './foo_bg';
|
|
|
|
|
2018-07-07 20:36:05 +02:00
|
|
|
const stack = [];
|
2018-07-02 16:11:21 -07:00
|
|
|
|
|
|
|
function addBorrowedObject(obj) {
|
|
|
|
stack.push(obj);
|
|
|
|
return stack.length - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function foo(arg0) {
|
|
|
|
const idx0 = addBorrowedObject(arg0);
|
|
|
|
try {
|
|
|
|
wasm.foo(idx0);
|
|
|
|
} finally {
|
|
|
|
stack.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Here we can see a few notable points of action:
|
|
|
|
|
|
|
|
* The wasm file was renamed to `foo_bg.wasm`, and we can see how the JS module
|
|
|
|
generated here is importing from the wasm file.
|
|
|
|
* Next we can see our `stack` module variable which is used to push/pop items
|
|
|
|
from the stack.
|
|
|
|
* Our exported function `foo`, takes an arbitrary argument, `arg0`, which is
|
|
|
|
converted to an index with the `addBorrowedObject` object function. The index
|
|
|
|
is then passed to wasm so wasm can operate with it.
|
|
|
|
* Finally, we have a `finally` which frees the stack slot as it's no longer
|
|
|
|
used, issuing a `pop` for what was pushed at the start of the function.
|
|
|
|
|
|
|
|
It's also helpful to dig into the Rust side of things to see what's going on
|
|
|
|
there! Let's take a look at the code that `#[wasm_bindgen]` generates in Rust:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
// what the user wrote
|
|
|
|
pub fn foo(a: &JsValue) {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
|
|
|
|
#[export_name = "foo"]
|
|
|
|
pub extern fn __wasm_bindgen_generated_foo(arg0: u32) {
|
|
|
|
let arg0 = unsafe {
|
|
|
|
ManuallyDrop::new(JsValue::__from_idx(arg0))
|
|
|
|
};
|
|
|
|
let arg0 = &*arg0;
|
|
|
|
foo(arg0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
And as with the JS, the notable points here are:
|
|
|
|
|
|
|
|
* The original function, `foo`, is unmodified in the output
|
|
|
|
* A generated function here (with a unique name) is the one that's actually
|
|
|
|
exported from the wasm module
|
|
|
|
* Our generated function takes an integer argument (our index) and then wraps it
|
|
|
|
in a `JsValue`. There's some trickery here that's not worth going into just
|
|
|
|
yet, but we'll see in a bit what's happening under the hood.
|
|
|
|
|
|
|
|
### Long-lived JS objects in a slab
|
|
|
|
|
|
|
|
The above strategy is useful when JS objects are only temporarily used in Rust,
|
|
|
|
for example only during one function call. Sometimes, though, objects may have a
|
|
|
|
dynamic lifetime or otherwise need to be stored on Rust's heap. To cope with
|
|
|
|
this there's a second half of management of JS objects, a slab.
|
|
|
|
|
|
|
|
JS Objects passed to wasm that are not references are assumed to have a dynamic
|
|
|
|
lifetime inside of the wasm module. As a result the strict push/pop of the stack
|
|
|
|
won't work and we need more permanent storage for the JS objects. To cope with
|
|
|
|
this we build our own "slab allocator" of sorts.
|
|
|
|
|
|
|
|
A picture (or code) is worth a thousand words so let's show what happens with an
|
|
|
|
example.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
// foo.rs
|
|
|
|
#[wasm_bindgen]
|
|
|
|
pub fn foo(a: JsValue) {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that the `&` is missing in front of the `JsValue` we had before, and in
|
|
|
|
Rust parlance this means it's taking ownership of the JS value. The exported ES
|
|
|
|
module interface is the same as before, but the ownership mechanics are slightly
|
|
|
|
different. Let's see the generated JS's slab in action:
|
|
|
|
|
|
|
|
```js
|
|
|
|
import * as wasm from './foo_bg'; // imports from wasm file
|
|
|
|
|
2018-07-07 20:36:05 +02:00
|
|
|
const slab = [];
|
2018-07-02 16:11:21 -07:00
|
|
|
let slab_next = 0;
|
|
|
|
|
|
|
|
function addHeapObject(obj) {
|
|
|
|
if (slab_next === slab.length)
|
|
|
|
slab.push(slab.length + 1);
|
|
|
|
const idx = slab_next;
|
|
|
|
const next = slab[idx];
|
|
|
|
slab_next = next;
|
|
|
|
slab[idx] = { obj, cnt: 1 };
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function foo(arg0) {
|
|
|
|
const idx0 = addHeapObject(arg0);
|
|
|
|
wasm.foo(idx0);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function __wbindgen_object_drop_ref(idx) {
|
|
|
|
let obj = slab[idx];
|
|
|
|
obj.cnt -= 1;
|
|
|
|
if (obj.cnt > 0)
|
|
|
|
return;
|
|
|
|
// If we hit 0 then free up our space in the slab
|
|
|
|
slab[idx] = slab_next;
|
|
|
|
slab_next = idx;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Unlike before we're now calling `addHeapObject` on the argument to `foo` rather
|
|
|
|
than `addBorrowedObject`. This function will use `slab` and `slab_next` as a
|
|
|
|
slab allocator to acquire a slot to store the object, placing a structure there
|
|
|
|
once it's found.
|
|
|
|
|
|
|
|
Note here that a reference count is used in addition to storing the object.
|
|
|
|
That's so we can create multiple references to the JS object in Rust without
|
|
|
|
using `Rc`, but it's overall not too important to worry about here.
|
|
|
|
|
|
|
|
Another curious aspect of this generated module is the
|
|
|
|
`__wbindgen_object_drop_ref` function. This is one that's actually imported from
|
|
|
|
wasm rather than used in this module! This function is used to signal the end of
|
|
|
|
the lifetime of a `JsValue` in Rust, or in other words when it goes out of
|
|
|
|
scope. Otherwise though this function is largely just a general "slab free"
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
And finally, let's take a look at the Rust generated again too:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
// what the user wrote
|
|
|
|
pub fn foo(a: JsValue) {
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
|
|
|
|
#[export_name = "foo"]
|
|
|
|
pub extern fn __wasm_bindgen_generated_foo(arg0: u32) {
|
|
|
|
let arg0 = unsafe {
|
|
|
|
JsValue::__from_idx(arg0)
|
|
|
|
};
|
|
|
|
foo(arg0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Ah that looks much more familiar! Not much interesting is happening here, so
|
|
|
|
let's move on to...
|
|
|
|
|
|
|
|
### Anatomy of `JsValue`
|
|
|
|
|
|
|
|
Currently the `JsValue` struct is actually quite simple in Rust, it's:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
pub struct JsValue {
|
|
|
|
idx: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
// "private" constructors
|
|
|
|
|
|
|
|
impl Drop for JsValue {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
__wbindgen_object_drop_ref(self.idx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Or in other words it's a newtype wrapper around a `u32`, the index that we're
|
|
|
|
passed from wasm. The destructor here is where the `__wbindgen_object_drop_ref`
|
|
|
|
function is called to relinquish our reference count of the JS object, freeing
|
|
|
|
up our slot in the `slab` that we saw above.
|
|
|
|
|
|
|
|
If you'll recall as well, when we took `&JsValue` above we generated a wrapper
|
|
|
|
of `ManuallyDrop` around the local binding, and that's because we wanted to
|
|
|
|
avoid invoking this destructor when the object comes from the stack.
|
|
|
|
|
|
|
|
### Indexing both a slab and the stack
|
|
|
|
|
|
|
|
You might be thinking at this point that this system may not work! There's
|
|
|
|
indexes into both the slab and the stack mixed up, but how do we differentiate?
|
|
|
|
It turns out that the examples above have been simplified a bit, but otherwise
|
|
|
|
the lowest bit is currently used as an indicator of whether you're a slab or a
|
|
|
|
stack index.
|