Rust 编写 wasm(手动版本)
安装了 rust 后, 开始编写 hello wasm, 然后是 checker 的编写, 下面记录整个流程.
# step1 - install wasm32 target:
rustup target install wasm32-unknown-unknown
# step2 - create rust cdylib:
cargo new --lib rust-hello-wasm
# step3 - modify rust code(no_mangle and extern "C"): ...
# step4 - debug and release build:
cargo build --target wasm32-unknown-unknown
cargo build --release --target wasm32-unknown-unknown
# step5 - wasm to wat:
wasm2wat target/wasm32-unknown-unknown/debug/rust_hello_wasm.wasm -o da-debug.wat
wasm2wat target/wasm32-unknown-unknown/release/rust_hello_wasm.wasm -o da-release.wat
# step6 -wasm obj dump:
wasm-objdump -x target/wasm32-unknown-unknown/debug/rust_hello_wasm.wasm
wasm-objdump -x target/wasm32-unknown-unknown/release/rust_hello_wasm.wasm
rust 代码如下所示:
#[no_mangle]
pub extern "C" fn add_one(left: usize, right: usize) -> usize {
left + right + 1
}
release 进行 wasm2wat 后:
(module
(type (;0;) (func (param i32 i32) (result i32)))
(func $add_one (type 0) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
i32.const 1
i32.add)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global $__stack_pointer (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "add_one" (func $add_one))
(export "__data_end" (global 1))
(export "__heap_base" (global 2)))
release 进行 objdump 后:
rust_hello_wasm.wasm: file format wasm 0x1
Section Details:
Type[1]:
- type[0] (i32, i32) -> i32
Function[1]:
- func[0] sig=0 <add_one>
Table[1]:
- table[0] type=funcref initial=1 max=1
Memory[1]:
- memory[0] pages: initial=16
Global[3]:
- global[0] i32 mutable=1 <__stack_pointer> - init i32=1048576
- global[1] i32 mutable=0 <__data_end> - init i32=1048576
- global[2] i32 mutable=0 <__heap_base> - init i32=1048576
Export[4]:
- memory[0] -> "memory"
- func[0] <add_one> -> "add_one"
- global[1] -> "__data_end"
- global[2] -> "__heap_base"
Code[1]:
- func[0] size=10 <add_one>
Custom:
- name: ".debug_info"
Custom:
- name: ".debug_pubtypes"
Custom:
- name: ".debug_ranges"
Custom:
- name: ".debug_abbrev"
Custom:
- name: ".debug_line"
Custom:
- name: ".debug_str"
Custom:
- name: ".debug_pubnames"
Custom:
- name: "name"
- func[0] <add_one>
- global[0] <__stack_pointer>
Custom:
- name: "producers"
Custom:
- name: "target_features"
- [+] mutable-globals
- [+] sign-ext
观察上述信息:
- Page 初始值为 16, 即至少分配 16 * 64KB(即 1 MB)虚拟线性栈空间.
- 只有一个 function table
- 可以看到
add_one
函数被 export 了
被 export 的函数作为该 WebAssembly 的对外接口, 这一点和典型的 FFI 一样.
而如果要调用 host 侧的函数, 也和典型 FFI 做法一样, 将 wasm 的两侧使用 "C" 调用约定进行即可:
extern "C" {
fn some_kind_of_callback_on_host(val: i32);
}
下一步就是让 JS 和 wasm module 交互, 总的流程是:
- JS 中
fetch
加载.wasm
二进制文件fetch('./hello.wasm').then(response) => {
response.arrayBuffer()
} - 返回的 arrayBuffer(即字节数组) 通过
WebAssembly.instantiate
初始化, 而在初始化时的env
成员中就可以指定 host 向 module 暴露的回调接口(默认情况下是env
名空间下)..then(bytes => WebAssembly.instantiate(bytes, {
env: {
some_callback_func1: (val) => {
//...
},
some_callback_func2: //...
}
})) - 通过初始化返回的结果, 就可以获取到 WebAssembly 在 host 侧的示例表示:
.then(results => {
instance = results.instance;
// 从 instance 中就可以获取到 wasm 对外 export 的接口:
let res = instance.exports.add_one(1, 2);
// ...
}).catech(consle.error);