Wasmtime 47 默认开启 Wasm GC 和异常支持,高级语言编译到 WASM 不用自己 GC 了

0 条回复
9 次浏览

之前用 Java、Kotlin、Dart、C# 这类带 GC 的语言编译到 WASM 的时候,得把自己的垃圾回收器整个塞进 .wasm 二进制里。结果就是产物又大又慢,而且很多原生 GC 实现里常用的优化手段(stack map、stack walking 找 GC root 之类的)在 WASM 里根本用不了。

Wasm GC 提案就是来解决这个问题的——直接在 WASM 层面支持 structarray 类型定义和子类型关系,对象生命周期交给运行时管,工具链不用再自己实现 GC 了。

举个例子,WAT 里定义一个二叉树节点长这样:

复制
(rec
  (type $node (struct
    (field $key (mut f64))
    (field $left (mut (ref null $node)))
    (field $right (mut (ref null $node)))
    (field $value (mut (ref null $payload)))
  ))
)

struct.new 创建,struct.get / struct.set 读写字段,非常直观。

异常提案也一起开了

以前没有原生异常支持,工具链得自己搞一套 calling convention,每次函数调用都要额外返回"是正常返回还是抛异常了",每个 call site 都要判断分支。又臃肿又慢。

现在有了 throwtry/catch 原语,运行时可以用经典的 unwinding 实现,正常路径零开销。

Wasmtime 的 GC 实现细节(这部分比较硬核)

用的是 Cheney 半空间拷贝回收器,GC 堆分两半,活跃空间 bump pointer 分配,回收时把活对象拷到另一半,更新所有 GC root。不需要 read/write barrier,实现很简洁。

比较有意思的设计:GC 堆底层复用了 WASM linear memory。对象引用不是原生指针,而是 32-bit 的堆内索引。好处是:

  • 安全性:就算 collector 有 bug 把堆搞坏了,恶意 WASM 程序也逃不出沙箱,碰不到宿主内存
  • 性能:可以用虚拟内存 guard page 省掉显式边界检查;32-bit 引用比 64-bit 指针更省 cache;跟 pooling allocator 集成后实例化时间还是保持在 5μs
  • 可移植性:linear memory 那套跨平台(包括 bare metal)的内存管理逻辑直接复用,不用重新造轮子

测试方面,除了扩展 wasm-smith 支持 GC 提案做模糊测试,还额外写了两个 fuzzer:一个专门生成各种复杂的对象图和子类型关系,另一个专门检测 collector bug 或编译器误优化导致的堆损坏。

性能方面先打个预防针

官方说了,目前主要精力放在正确性上,性能还没怎么优化。跟 V8、SpiderMonkey 那些打磨了几十年的 GC 比,吞吐和延迟肯定还有差距。而且 Wasmtime 的设计目标场景是大量短生命周期的小实例(每个实例处理几个任务就扔掉),不是那种单个长驻服务进程的场景。

后续计划

  • 编译器层面:把 GC 类型信息接入 alias analysis,做更激进的 store-to-load forwarding 和冗余 load 消除
  • 功能层面:GC + Component Model 集成,让带 GC 的语言在组件生态里成为一等公民,不用再为了跨组件传数据而维护一个没用的 linear memory

原文: https://bytecodealliance.org/articles/wasmtime-gc

发表一个评论

R保持