js-sys: WebAssembly in WebAssembly

Using the js-sys crate we can instantiate WebAssembly modules from inside WebAssembly modules!
_ [wasm-bindgen Guide]

Original [wasm-bindgen example]

PART I. Make it run

Using wasm in wasm example

1. file structure & crate type

cargo new wasm-in-wasm --lib
cd wasm-in-wasm
mkdir -p www/html www/js
cargo add wasm-bindgen

edit Cargo.toml to add crate-type

[lib]
crate-type = ["cdylib",]

2. make the wasm file to be used by wasm-in-wasm later

We want to use webassembly in our rust code. First we shall generate a wasm file called add.wasm But instead of creating another crate just to generate that wasm file, we shall temporarily use src/lib.rs to generate add.wasm. Then we'll delete that lib.rs code and continue our project with our real code in lib.rs.

// temporary src/lib.rs to generate our add.wasm file

use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen]
pub fn add(a: usize, b: usize) -> usize {
    a + b
}

Build the wasm file with the out-name set to add

wasm-pack build --release --target web --out-name add --out-dir www/pkg

Since this wasm file is on the "server side", we can move it in to src and rename from www/pkg/add_bg.wasm to src/add.wasm

and clean the project for our real code

mv www/pkg/add_bg.wasm src/add.wasm
cargo clean
rm -fr www/pkg

3. Html and Js files

In www/html/index.html we have

<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
    <title>Using wasm in Rust</title>
  </head>
  <body>
    <p>Everything happens in rust/wasm <br/ >
    The developer console should have messages in it</p>

    <script type="module" src="../js/index.js"></script>
  </body>
</html>

and in www/js/index.js

import init from "../pkg/wasm_in_wasm.js"

init();

/* 
    //async is handled directly in lib.rs
    async function run() {
        const wasm = await init();
    }

    run();
*/

Note:

The build outputs the file is wasm_in_wasm.js not wasm-in-wasm.js
we've seen that before (ie: the crate wasm-bindgen is used as wasm_bindgen)

4. Everything happens in src

First, we need to add the two new crates js-sys and wasm-bindgen-futures

cargo add js-sys
cargo add wasm-bindgen-futures

we bring them into scope in lib.rs

#![allow(unused)]
fn main() {
// src/lib.rs

use js_sys::{Function, Object, Reflect, WebAssembly};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{spawn_local, JsFuture};

// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(a: &str);
}

macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

const WASM: &[u8] = include_bytes!("add.wasm"); // path relative to lib.rs

async fn run_async() -> Result<(), JsValue> {
    console_log!("instantiating a new wasm module directly");

    let a = JsFuture::from(WebAssembly::instantiate_buffer(WASM, &Object::new())).await?;
    let b: WebAssembly::Instance = Reflect::get(&a, &"instance".into())?.dyn_into()?;

    let c = b.exports();

    let add = Reflect::get(c.as_ref(), &"add".into())?
        .dyn_into::<Function>()
        .expect("add export wasn't a function");

    let three = add.call2(&JsValue::undefined(), &1.into(), &2.into())?;
    console_log!("1 + 2 = {:?}", three);

    let mem = Reflect::get(c.as_ref(), &"memory".into())?
        .dyn_into::<WebAssembly::Memory>()
        .expect("memory export wasn't a `WebAssembly.Memory`");
    console_log!("created module has {} pages of memory", mem.grow(0));
    console_log!("giving the module 4 more pages of memory");
    mem.grow(4);
    console_log!("now the module has {} pages of memory", mem.grow(0));

    Ok(())
}

#[wasm_bindgen(start)]
fn run() {
    spawn_local(async {
        run_async().await.unwrap_throw();
    });
}
}

5. build and serve

wasm-pack build --target web --no-typescript --out-dir www/pkg

http www

open index.html

firefox http://localhost:8000/html/

wasm in wasm


PART II. Understand the Code

Understand the Code

#![allow(unused)]
fn main() {
use js_sys::{Function, Object, Reflect, WebAssembly};
}
#![allow(unused)]
fn main() {
use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen]
pub fn add(a: usize, b: usize) -> usize {
    a + b
}
}

The add.wasm would typically be used in a js file since we made it available #[wasm_bindgen].

But this time we are going to use it directly in our Rust code.

#![allow(unused)]
fn main() {
const WASM: &[u8] = include_bytes!("add.wasm"); // path relative to lib.rs
}

Remember add.wasm is a binary.

WebAssembly::instantiate_buffer(WASM, &Object::new())
  • JsFuture & WebAssembly::instantiate_buffer
#![allow(unused)]
fn main() {
    let a = JsFuture::from(WebAssembly::instantiate_buffer(WASM, &Object::new())).await?;
}

[wikipedia: Futures_and_promises]

  • WebAssembly::Instance & Reflect
#![allow(unused)]
fn main() {
    let b: WebAssembly::Instance = Reflect::get(&a, &"instance".into())?.dyn_into()?;
}
  • WebAssembly::Instance & .exports()
#![allow(unused)]
fn main() {
    let c = b.exports();

}
  • dyn_into::<>
#![allow(unused)]
fn main() {
    let add = Reflect::get(c.as_ref(), &"add".into())?
        .dyn_into::<Function>()
        .expect("add export wasn't a function");

}
  • Struct js_sys::Function & call2()
#![allow(unused)]
fn main() {
    let three = add.call2(&JsValue::undefined(), &1.into(), &2.into())?;
}
  • js-sys WebAssembly::Memory & .grow()
#![allow(unused)]
fn main() {
    let mem = Reflect::get(c.as_ref(), &"memory".into())?
        .dyn_into::<WebAssembly::Memory>()
        .expect("memory export wasn't a `WebAssembly.Memory`");
}

grow memory

#![allow(unused)]
fn main() {
    console_log!("created module has {} pages of memory", mem.grow(0));
    console_log!("giving the module 4 more pages of memory");

    mem.grow(4);

    console_log!("now the module has {} pages of memory", mem.grow(0));
}
  • spanw_local
#![allow(unused)]
fn main() {
#[wasm_bindgen(start)]
fn run() {
    spawn_local(async {
        run_async().await.unwrap_throw();
    });
}
}

What's next?

<-- Working with the char type
web-sys: DOM hello world -->