Skip to main content

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

观察上述信息:

  1. Page 初始值为 16, 即至少分配 16 * 64KB(即 1 MB)虚拟线性栈空间.
  2. 只有一个 function table
  3. 可以看到 add_one 函数被 export 了

被 export 的函数作为该 WebAssembly 的对外接口, 这一点和典型的 FFI 一样.

而如果要调用 host 侧的函数, 也和典型 FFI 做法一样, 将 wasm 的两侧使用 "C" 调用约定进行即可:

extern "C" {
fn some_kind_of_callback_on_host(val: i32);
}

下一步就是让 JS 和 wasm module 交互, 总的流程是:

  1. JS 中 fetch 加载 .wasm 二进制文件
    fetch('./hello.wasm').then(response) => {
    response.arrayBuffer()
    }
  2. 返回的 arrayBuffer(即字节数组) 通过 WebAssembly.instantiate 初始化, 而在初始化时的 env 成员中就可以指定 host 向 module 暴露的回调接口(默认情况下是 env 名空间下).
    .then(bytes => WebAssembly.instantiate(bytes, {
    env: {
    some_callback_func1: (val) => {
    //...
    },
    some_callback_func2: //...
    }
    }))
  3. 通过初始化返回的结果, 就可以获取到 WebAssembly 在 host 侧的示例表示:
    .then(results => {
    instance = results.instance;
    // 从 instance 中就可以获取到 wasm 对外 export 的接口:
    let res = instance.exports.add_one(1, 2);
    // ...
    }).catech(consle.error);