Rust and WASM
Now let's play with WASM in Rust without rustwasm toolchain.
Empty module
chapter_rust/rust/empty_module/src/lib.rs
#![allow(unused)] fn main() { #[no_mangle] pub fn empty() {} }
chapter_rust/rust/empty_module/Cargo.toml
[package]
name = "empty_module"
version = "0.1.0"
authors = ["Sam Liu <ontouchstart@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
chapter_rust/rust/empty_module/Makefile
build:
cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/empty_module.wasm ../../wasm
chapter_rust/js/empty_module.js
(async () => {
var importObject = {};
const response = await fetch('chapter_rust/wasm/empty_module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, importObject);
const { exports } = instance;
console.log('Empty WASM Module', {response, bytes, instance, exports});
document.getElementById('empty_module').innerHTML = `FROM WASM:
byteLength = ${bytes.byteLength}
`;
})();
<pre id="empty_module"></pre>
<script src="chapter_rust/js/empty_module.js"></script>
The wasm straight out of the rust is way bigger than the 8 bytes empty module created by hand.
$ hexdump -C wasm/empty_module.wasm | head
00000000 00 61 73 6d 01 00 00 00 01 04 01 60 00 00 03 02 |.asm.......`....|
00000010 01 00 04 05 01 70 01 01 01 05 03 01 00 10 06 19 |.....p..........|
00000020 03 7f 01 41 80 80 c0 00 0b 7f 00 41 80 80 c0 00 |...A.......A....|
00000030 0b 7f 00 41 80 80 c0 00 0b 07 2d 04 06 6d 65 6d |...A......-..mem|
00000040 6f 72 79 02 00 05 65 6d 70 74 79 00 00 0a 5f 5f |ory...empty...__|
00000050 64 61 74 61 5f 65 6e 64 03 01 0b 5f 5f 68 65 61 |data_end...__hea|
00000060 70 5f 62 61 73 65 03 02 0a 04 01 02 00 0b 00 da |p_base..........|
00000070 92 18 0b 2e 64 65 62 75 67 5f 69 6e 66 6f 80 15 |....debug_info..|
00000080 03 00 04 00 00 00 00 00 04 01 00 00 00 00 1c 00 |................|
00000090 39 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 |9.......P.......|
$ hexdump -C wasm/empty_module.wasm | tail
001828e0 6d 5f 6d 65 6d 63 70 79 5f 65 6c 65 6d 65 6e 74 |m_memcpy_element|
001828f0 5f 75 6e 6f 72 64 65 72 65 64 5f 61 74 6f 6d 69 |_unordered_atomi|
00182900 63 5f 31 00 00 00 00 00 00 0f 04 6e 61 6d 65 01 |c_1........name.|
00182910 08 01 00 05 65 6d 70 74 79 00 4d 09 70 72 6f 64 |....empty.M.prod|
00182920 75 63 65 72 73 02 08 6c 61 6e 67 75 61 67 65 01 |ucers..language.|
00182930 04 52 75 73 74 00 0c 70 72 6f 63 65 73 73 65 64 |.Rust..processed|
00182940 2d 62 79 01 05 72 75 73 74 63 1d 31 2e 35 30 2e |-by..rustc.1.50.|
00182950 30 20 28 63 62 37 35 61 64 35 64 62 20 32 30 32 |0 (cb75ad5db 202|
00182960 31 2d 30 32 2d 31 30 29 |1-02-10)|
00182968
The file size is 1583464 (0x00182968)
bytes.
ArrayBuffer
We still have the first 4 bytes WASM_BINARY_MAGIC
00 61 73 6d ('\0asm')
and the next 4 bytes represent WASM_BINARY_VERSION
01 00 00 00
See binary module specification.
A simple function that returns the input
chapter_rust/rust/return_module/src/lib.rs
#![allow(unused)] fn main() { #[no_mangle] pub fn return_input(input: i32) -> i32 { input } }
Since return
is a rust keyword, we changed the function name to return_input
.
chapter_rust/js/return_module.js
(async () => {
var importObject = {};
const response = await fetch('chapter_rust/wasm/return_module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, importObject);
const { exports } = instance;
console.log('WASM Module', {response, bytes, instance, exports});
const input = 42;
document.getElementById('return_module').innerHTML = `FROM WASM:
byteLength = ${bytes.byteLength}
return_input(${input}) = ${exports.return_input(input)}
`;
})();
<pre id="return_module"></pre>
<script src="chapter_rust/js/return_module.js"></script>
$ hexdump -C wasm/return_module.wasm | head
00000000 00 61 73 6d 01 00 00 00 01 06 01 60 01 7f 01 7f |.asm.......`....|
00000010 03 02 01 00 04 05 01 70 01 01 01 05 03 01 00 10 |.......p........|
00000020 06 19 03 7f 01 41 80 80 c0 00 0b 7f 00 41 80 80 |.....A.......A..|
00000030 c0 00 0b 7f 00 41 80 80 c0 00 0b 07 34 04 06 6d |.....A......4..m|
00000040 65 6d 6f 72 79 02 00 0c 72 65 74 75 72 6e 5f 69 |emory...return_i|
00000050 6e 70 75 74 00 00 0a 5f 5f 64 61 74 61 5f 65 6e |nput...__data_en|
00000060 64 03 01 0b 5f 5f 68 65 61 70 5f 62 61 73 65 03 |d...__heap_base.|
00000070 02 0a 06 01 04 00 20 00 0b 00 da 92 18 0b 2e 64 |...... ........d|
00000080 65 62 75 67 5f 69 6e 66 6f 80 15 03 00 04 00 00 |ebug_info.......|
00000090 00 00 00 04 01 00 00 00 00 1c 00 39 00 00 00 00 |...........9....|
$ hexdump -C wasm/return_module.wasm | tail
001828f0 63 70 79 5f 65 6c 65 6d 65 6e 74 5f 75 6e 6f 72 |cpy_element_unor|
00182900 64 65 72 65 64 5f 61 74 6f 6d 69 63 5f 31 00 00 |dered_atomic_1..|
00182910 00 00 00 00 16 04 6e 61 6d 65 01 0f 01 00 0c 72 |......name.....r|
00182920 65 74 75 72 6e 5f 69 6e 70 75 74 00 4d 09 70 72 |eturn_input.M.pr|
00182930 6f 64 75 63 65 72 73 02 08 6c 61 6e 67 75 61 67 |oducers..languag|
00182940 65 01 04 52 75 73 74 00 0c 70 72 6f 63 65 73 73 |e..Rust..process|
00182950 65 64 2d 62 79 01 05 72 75 73 74 63 1d 31 2e 35 |ed-by..rustc.1.5|
00182960 30 2e 30 20 28 63 62 37 35 61 64 35 64 62 20 32 |0.0 (cb75ad5db 2|
00182970 30 32 31 2d 30 32 2d 31 30 29 |021-02-10)|
0018297a
The file size is 1583482 (0x0018297a)
bytes. 18
bytes bigger than the empty version.
Add only module
chapter_rust/rust/add_only_module/src/lib.rs
#![allow(unused)] fn main() { #[no_mangle] pub fn return_input(input: i32) -> i32 { input } }
chapter_rust/js/add_only_module.js
(async () => {
var importObject = { };
const response = await fetch('chapter_rust/wasm/add_only_module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, importObject);
const { exports } = instance;
console.log('WASM Module', {response, bytes, instance, exports});
const i32max = (0xffffffff - 1)/2;
document.getElementById(`add_only_module_output`).innerHTML = `FROM WASM:
byteLength = ${bytes.byteLength}
1 + 1 = ${exports.add(1, 1)}
3 + 4 = ${exports.add(3, 4)}
${i32max} + 1 = ${exports.add(i32max, 1)}
`;
})();
<pre id="add_only_module_output"></pre>
<script src="chapter_rust/js/add_only_module.js"></script>
$ hexdump -C wasm/add_only_module.wasm | head
00000000 00 61 73 6d 01 00 00 00 01 07 01 60 02 7f 7f 01 |.asm.......`....|
00000010 7f 03 02 01 00 04 05 01 70 01 01 01 05 03 01 00 |........p.......|
00000020 10 06 19 03 7f 01 41 80 80 c0 00 0b 7f 00 41 80 |......A.......A.|
00000030 80 c0 00 0b 7f 00 41 80 80 c0 00 0b 07 2b 04 06 |......A......+..|
00000040 6d 65 6d 6f 72 79 02 00 03 61 64 64 00 00 0a 5f |memory...add..._|
00000050 5f 64 61 74 61 5f 65 6e 64 03 01 0b 5f 5f 68 65 |_data_end...__he|
00000060 61 70 5f 62 61 73 65 03 02 0a 09 01 07 00 20 01 |ap_base....... .|
00000070 20 00 6a 0b 00 da 92 18 0b 2e 64 65 62 75 67 5f | .j.......debug_|
00000080 69 6e 66 6f 80 15 03 00 04 00 00 00 00 00 04 01 |info............|
00000090 00 00 00 00 1c 00 39 00 00 00 00 00 00 00 50 00 |......9.......P.|
$ hexdump -C wasm/add_only_module.wasm | tail
001828e0 00 5f 5f 6c 6c 76 6d 5f 6d 65 6d 63 70 79 5f 65 |.__llvm_memcpy_e|
001828f0 6c 65 6d 65 6e 74 5f 75 6e 6f 72 64 65 72 65 64 |lement_unordered|
00182900 5f 61 74 6f 6d 69 63 5f 31 00 00 00 00 00 00 0d |_atomic_1.......|
00182910 04 6e 61 6d 65 01 06 01 00 03 61 64 64 00 4d 09 |.name.....add.M.|
00182920 70 72 6f 64 75 63 65 72 73 02 08 6c 61 6e 67 75 |producers..langu|
00182930 61 67 65 01 04 52 75 73 74 00 0c 70 72 6f 63 65 |age..Rust..proce|
00182940 73 73 65 64 2d 62 79 01 05 72 75 73 74 63 1d 31 |ssed-by..rustc.1|
00182950 2e 35 30 2e 30 20 28 63 62 37 35 61 64 35 64 62 |.50.0 (cb75ad5db|
00182960 20 32 30 32 31 2d 30 32 2d 31 30 29 | 2021-02-10)|
0018296c
The file size is 1583468 (0x0018296c)
bytes.
Call JS function from WASM
We are going to use wasm-bindgen in this implementation.
chapter_rust/rust/call_js_func_module/Cargo.toml
[package]
name = "call_js_func_module"
version = "0.1.0"
authors = ["Sam Liu <ontouchstart@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.71"
chapter_rust/rust/call_js_func_module/src/lib.rs
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = dom)] fn log(x: i32, y: i32, output: i32); } fn add(x: i32, y: i32) -> i32 { x + y } #[wasm_bindgen] pub fn show_add(x: i32, y: i32) { let output = add(x, y); log(x, y, output); } }
chapter_rust/rust/call_js_func_module/Makefile
build:
wasm-pack build --target web
rm pkg/.gitignore
cp pkg/call_js_func_module_bg.wasm ../../wasm
The JavaScript part is going to be a little bit complicated. Hopefully we can clean up in the future.
chapter_rust/js/call_js_func_module_dom.js
// make a global dom object to be used in
// chapter_rust/js/module/call_js_func_module.js
// which imports
// chapter_rust/rust/call_js_func_module/pkg/call_js_func_module.js
window.dom = {
log: function (x, y, output) {
console.log('Call from WASM', { x, y, output });
document.getElementById(`call_js_func_module_output`).innerHTML += `
${x} + ${y} = ${output}`
}
};
chapter_rust/js/module/call_js_func_module.js
import init, {show_add} from '/chapter_rust/rust/call_js_func_module/pkg/call_js_func_module.js';
(async ()=> {
const result = await init();
console.log({result})
const { memory } = result;
console.log ( { memory });
const { buffer } = memory;
console.log({ buffer });
console.log(buffer.byteLength);
const i32max = (0xffffffff - 1) / 2;
document.getElementById(`call_js_func_module_output`).innerHTML += `
FROM chapter/js/module/call_js_func_modules.js
Memory buffer ${buffer.byteLength} bytes
`
show_add(1,2);
show_add(3,4);
show_add(i32max, 1);
})();
This is the wrapper JS code generated by wasm-bindgen
:
chapter_rust/rust/call_js_func_module/pkg/call_js_func_module.js
let wasm;
function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }
/**
* @param {number} x
* @param {number} y
*/
export function show_add(x, y) {
wasm.show_add(x, y);
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('call_js_func_module_bg.wasm', import.meta.url);
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_log_da4acde71f32b64a = typeof dom.log == 'function' ? dom.log : notDefined('dom.log');
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
const { instance, module } = await load(await input, imports);
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
return wasm;
}
export default init;
There is a lot of magic up there genearted by wasm-bindgen and wasm-pack. Let's see if we can make it simpler.
JavaScript modules
Wasm-pack use JavaScript modules to load WASM from the "machine generated" chapter_rust/rust/call_js_func_module/pkg/call_js_func_module.js
.
<script src="/chapter_rust/js/call_js_func_module_dom.js"></script>
<script type="module" src="/chapter_rust/js/module/call_js_func_module.js"></script>
In our other examples, we use simple script tags. Let's see if we can make it work the same way without modules.
<script src="chapter_rust/js/call_js_func_module.js"></script>
chapter_rust/js/call_js_func_module.js
(async () => {
const response = await fetch('chapter_rust/wasm/call_js_func_module_bg.wasm');
const bytes = await response.arrayBuffer();
const dom = {
log: function (x, y, output) {
console.log('Call from WASM', { x, y, output });
document.getElementById(`call_js_func_module_output`).innerHTML += `
${x} + ${y} = ${output}`
}
}
console.log({dom});
const importObject = {};
importObject.wbg = {};
importObject.wbg.__wbg_log_da4acde71f32b64a = dom.log;
console.log({ response, bytes, importObject });
const { instance } = await WebAssembly.instantiate(bytes, importObject);
const { exports } = instance;
console.log('WASM Module', {response, bytes, instance, exports});
const i32max = (0xffffffff - 1) / 2;
document.getElementById(`call_js_func_module_output`).innerHTML += `
FROM chapter/js/call_js_func_modules.js
WASM ${bytes.byteLength} bytes
Memory buffer ${exports.memory.buffer.byteLength} bytes
`
exports.show_add(5,6);
exports.show_add(7,8);
exports.show_add(i32max, 1);
})();
As we can see it works for both JavaScript loaders, although they come in asynchronously.
<pre id ="call_js_func_module_output"></pre>