Golang 实现 WebAssembly VM
背景
由于 WebAssembly Interface Types 还处于草案阶段,目前 WASM 只支持四种基本类型,本质上来说没什么大用。
在实际的开发中,数据结构肯定是远比 i32,i64,f32,f64
这四种基本类型来得复杂的,至少可能是一个复杂的结构体。比如,我需要把一组 key/value 存起来,然后可以通过 key 查询到对应的 value。按照目前的 WASM 的规范,这个实现都是比较麻烦的。key/value 可能是字符串或者其他结构,但是不管什么类型,在内存都是按照字节序的方式存储,所以只需要把类型编码成字节序放到内存中,这样这个结构就可以转化成内存的起始地址(指针)和数据长度,这两个值就是 i32/i64
,并且都是 WASM 支持的类型。
函数声明
对外 API
为了对调用者友好,调用这不需要处理
slice
的地址
1 | // 把 key/value 写入存储 |
- 内部接口
在虚拟机中注入这两个 host function
来实现具体的逻辑
1 | extern "C" { |
WASM 二进制
Rust 实现
invoke
作为对外的入口点,调用此函数,就可以实现把 key/val 存储起来,然后通过 key 查询对应的 val
Cargo.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[package]
name = "example01"
version = "0.1.0"
authors = ["gythialy <gythialy.koo+github@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
[profile.release]
lto = true
opt-level = 's'lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83mod env {
extern "C" {
pub fn qlcchain_storage_read(
key: *const u8,
klen: u32,
val: *mut u8,
vlen: u32,
offset: u32,
) -> u32;
pub fn qlcchain_storage_write(key: *const u8, klen: u32, val: *const u8, vlen: u32);
pub fn qlcchain_storage_delete(key: *const u8, klen: u32);
pub fn qlcchain_debug(val: *const u8, len: u32);
}
}
pub fn storage_write(key: &[u8], val: &[u8]) {
unsafe {
env::qlcchain_storage_write(
key.as_ptr(),
key.len() as u32,
val.as_ptr(),
val.len() as u32,
);
}
}
pub fn storage_delete(key: &[u8]) {
unsafe {
env::qlcchain_storage_delete(key.as_ptr(), key.len() as u32);
}
}
pub fn storage_read(key: &[u8]) -> Option<Vec<u8>> {
const INITIAL: usize = 32;
let mut val = vec![0; INITIAL];
let size = unsafe {
env::qlcchain_storage_read(
key.as_ptr(),
key.len() as u32,
val.as_mut_ptr(),
val.len() as u32,
0,
)
};
if size == core::u32::MAX {
return None;
}
let size = size as usize;
val.resize(size, 0);
if size > INITIAL {
let value = &mut val[INITIAL..];
debug_assert!(value.len() == size - INITIAL);
unsafe {
env::qlcchain_storage_read(
key.as_ptr(),
key.len() as u32,
value.as_mut_ptr(),
value.len() as u32,
INITIAL as u32,
)
};
}
Some(val)
}
pub fn debug(val: &[u8]) {
unsafe {
env::qlcchain_debug(val.as_ptr(), val.len() as u32);
}
}
pub fn invoke() {
let key = "key01";
let val = "val01";
storage_write(key.as_bytes(), val.as_bytes());
if let Some(val) = storage_read(key.as_bytes()) {
debug(val.as_slice());
}
}编译
1
RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target wasm32-unknown-unknown
wat
文件
WAT 文件
VM 实现
gasm 作为 WASM v1 的最小实现,支持解析执行 WASM 二进制文件和注入 host function
。做为 WASM VM MVP 在此基础上做一些简单的封装。
- 根据刚才 Rust 中的定义的函数名注入
host function
,比如qlcchain_storage_write
对应qlcSetDataFun
- 根据回调中的 key/value 的起始地址和长度,获取响应的字节序,就可以解码出对应的数据结构
- 也可以根据 key/value 的内存地址,修改相应的数据
完整的测试代码
1 | package main |
结语
- WASM v1 支持的内容有限,想实现更高级的功能需要做不少额外的工作,但也不是不可能
- 现有大多数的 WASM VM 的实现都不支持或者不完整支持 JIT/AOP,执行效率有限
- 未来
WebAssembly Interface Types
等规范发布的话,更容易支持复杂业务 - wasm-bindgen 已经支持 Rust 生态的
WebAssembly Interface Types
参考链接
- ontology-wasm-cdt-rust
---EOF---