Wasmtime 中的垃圾回收与异常处理
摘要
Wasmtime 47 默认启用 Wasm 垃圾回收与异常提案,使具有垃圾回收和异常处理机制的高级语言能够更高效地编译到 WebAssembly。
暂无内容
查看缓存全文
缓存时间: 2026/07/25 05:06
# Wasmtime 中的GC与异常支持
来源:https://bytecodealliance.org/articles/wasmtime-gc
Wasm GC 和异常处理提案在今天发布的 [Wasmtime 47](https://github.com/bytecodealliance/wasmtime/releases/tag/v47.0.0) 版本中默认启用!我们很高兴能够帮助更多语言支持 WebAssembly,并让它们能在 Wasmtime 运行的任何地方发挥作用。这一里程碑的实现离不开 Wasmtime 的重大变更,也凝聚了多年的工程努力。
[Wasmtime](https://wasmtime.dev/) 是一个 [快速](https://bytecodealliance.org/articles/inliner)、[安全](https://bytecodealliance.org/articles/security-and-correctness-in-wasmtime) 且 [可移植](https://bytecodealliance.org/articles/wasmtime-portability) 的 WebAssembly 运行时。它独立、轻量,易于 [嵌入](https://docs.wasmtime.dev/lang.html)。Wasmtime 的维护者 [致力于](https://bytecodealliance.org/about) 开放标准,并积极参与 Wasm 标准化工作。
## Wasm GC
最初,在 WebAssembly 的早期版本中,采用“对象与引用”数据模型(而非“原始指针与内存”数据模型)的高级语言,必须在其 `.wasm` 二进制文件中嵌入自己的垃圾收集器(GC)。这导致 `.wasm` 二进制文件膨胀,并且许多原生代码中常用的垃圾收集技术(如使用 [堆栈映射](https://fitzgen.com/2024/09/10/new-stack-maps-for-wasmtime.html) 和堆栈遍历来识别 GC 根)都无法使用。不幸的是,许多语言都属于这一类。
[Wasm GC](https://github.com/WebAssembly/gc) 提案改善了这种情况,为 WebAssembly 增加了对这些高级语言的高效支持。¹ ([脚注1](https://bytecodealliance.org/articles/wasmtime-gc#fn:outdated)) 它扩展了 WebAssembly 语言,允许 Wasm 程序定义自己的 `struct` 和 `array` 类型以及子类型关系。Wasm 程序无需担心管理这些类型实例的生命周期或手动释放它们;这些都由运行时处理。因此,无需嵌入自己的垃圾收集器,这些工具链可以利用 WebAssembly 运行时的收集器。这为更多语言轻松且更高效地编译到 WebAssembly 打开了大门。
例如,下面是一个 Wasm 程序如何为二叉树定义一个节点类型 [的示例](https://github.com/bytecodealliance/sightglass/blob/98ff8a598b4b4f6eab0d8f391118411990efde96/benchmarks/splay/splay.wat#L15-L22):
```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 $node` [创建](https://github.com/bytecodealliance/sightglass/blob/98ff8a598b4b4f6eab0d8f391118411990efde96/benchmarks/splay/splay.wat#L310),而字段通过例如 `struct.get $node $key` [指令](https://github.com/bytecodealliance/sightglass/blob/98ff8a598b4b4f6eab0d8f391118411990efde96/benchmarks/splay/splay.wat#L327) 或 `struct.set $node $left` [指令](https://github.com/bytecodealliance/sightglass/blob/98ff8a598b4b4f6eab0d8f391118411990efde96/benchmarks/splay/splay.wat#L332) 访问。
## Wasm 异常
Wasm 的 [异常提案](https://github.com/WebAssembly/exception-handling) 对于使用异常的语言有着与 Wasm GC 提案对于面向对象/引用语言相似的目标:它旨在为 WebAssembly 上的异常提供高效支持,使 WebAssembly 成为带有异常的语言的更好编译目标。² ([脚注2](https://bytecodealliance.org/articles/wasmtime-gc#fn:outdated2)) 没有这个提案,工具链需要实现自定义的调用约定,不仅返回函数结果,还要返回函数是正常返回还是抛出异常。每个调用点都必须检查这个条件并适当分支,这增加了 `.wasm` 二进制文件的体积和常见正常返回路径的运行时开销。而有了异常提案,这一切都消失了,取而代之的是 `throw` 和 `try`/`catch` 风格的构造。WebAssembly 运行时可以自由地使用经典的回退(unwinding)方法来实现这些,对常见的正常返回调用路径不增加任何开销,从而实现更快的执行和更小的 `.wasm` 二进制文件。
## Wasmtime 的 GC 实现
Wasmtime 使用一个简单的 [Cheney 风格半空间复制收集器](https://en.wikipedia.org/wiki/Cheney%27s_algorithm)。GC 堆被分成两半:“活动”半空间,用于分配新对象;“空闲”半空间。在收集期间,存活对象从空闲空间(即之前的活动空间)复制到新的活动空间,所有 GC 根(例如 Wasm 栈帧中的活动引用)都会更新为指向新位置。分配只是在活动半空间内进行简单的指针碰撞(bump pointer),并且收集器不需要任何读或写屏障。
我们在底层重用 WebAssembly 线性内存来实现并对 GC 堆进行沙箱化。对 GC 对象的引用不是原生指针,而是指向 GC 堆底层线性内存的 32 位索引。WebAssembly 承诺快速、安全、可移植,但运行时必须在实现中真正承担这些责任,而重用线性内存用于 GC 堆在这三个方面都有好处。最明显的是深度防御的安全性:即使收集器出现破坏 GC 堆的错误,恶意的 Wasm 程序也无法逃逸沙箱访问主机内存。在速度方面,它允许我们像对待线性内存一样使用虚拟内存保护页来省略显式的边界检查;我们与池化实例分配器紧密集成,确保维持 5 微秒的实例化时间;在 64 位机器上,32 位的 GC 引用比 64 位指针更紧凑,能更高效地利用 CPU 缓存。最后,在许多平台(包括裸机!)上快速分配、释放和重置大块内存,每个平台都有微妙的不同能力,需要进行大量特殊处理。我们现有的线性内存实现已经可以在这些平台上移植并已经做了这些特殊处理,因此,通过在线性内存之上构建 GC 堆,我们也能“免费”获得可移植的 GC 堆。
为了进一步提高对收集器正确性的信心,我们扩展了模糊测试基础设施来重点测试 Wasm GC。首先,我们扩展了 [`wasm-smith`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-smith) 以支持 GC 提案。理论上,它可以生成几乎任何使用 GC 的 Wasm 程序,只要给 [足够的时间](https://fitzgen.com/2026/06/01/structure-aware-fuzzing-experiment.html)³ ([脚注3](https://bytecodealliance.org/articles/wasmtime-gc#fn:wasm-smith-gc))。但这可能需要**非常长**的时间,因此我们用另外两个模糊测试器进行了补充:
1. 一个用于 [生成](https://github.com/bytecodealliance/wasmtime/issues/10327) 有趣且任意的对象图、类型引用和子类型关系。
2. 另一个用于 [检测](https://github.com/bytecodealliance/wasmtime/blob/86b242fcc4e508a2b4f7cd698063cad80d44f249/crates/fuzzing/src/generators/gc_access.rs) 由收集器错误或编译器错误优化导致的堆破坏。
最后,简要说明一下性能预期:到目前为止,我们主要将工程精力集中在收集器的正确性上,而不是性能上。它是全新的,还没有像 [V8](https://v8.dev/) 和 [SpiderMonkey](https://spidermonkey.dev/) 中的收集器那样受益于数十年的性能工程。我们收集器的吞吐量和延迟目前无法与之匹敌。此外,我们主要的设计权衡是针对 Wasmtime 在生产中最常用场景:创建许多小的、一次性使用的 Wasm 实例,每个实例处理少量任务后,其实例和 GC 堆将被丢弃。系统首先设计为跨多个实例水平扩展,而不是专注于单一实例性能。这种场景不同于例如一个长期运行的单服务器进程(生命周期不确定);为它调优收集器也会有所不同。
## 下一步计划
总有很多 [性能工作](https://github.com/bytecodealliance/wasmtime/issues?q=is%3Aissue%20state%3Aopen%20gc%20label%3Awasm-proposal%3Agc%20label%3Aperformance) 要做。例如,我们目前正在扩展编译器的别名分析优化(如存储到加载转发、冗余加载消除),加入 GC 类型信息。当我们知道两个类型**永远不会**别名(即它们不能占用相同的内存位置)时,我们可以更积极地应用这些优化。
功能方面的下一个重要里程碑是在 [惰性值降级](https://github.com/WebAssembly/component-model/issues/383) 之上,原型化 [GC 与组件模型的集成](https://github.com/WebAssembly/component-model/issues/525)。这项工作将使垃圾收集语言成为组件生态系统中的一等公民,因为它们在跨组件传递数据时不再需要原本不必要的线性内存。
## 结论
我们很高兴达到了这个里程碑!请 [试用 Wasmtime 新启用的 GC 和异常支持](https://wasmtime.dev/),并 [告诉我们](https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime) 效果如何。
相似文章
在 Chrome DevTools 中调试 WASM
关于使用 Chrome DevTools 调试 WebAssembly 代码的指南,包括设置断点和捕获异常。
最好的WebAssembly运行时可能仍然是根本没有运行时
本文使用支持宽算术的wasm2c对WebAssembly-to-C方法进行了基准测试,证明其在速度和内存使用方面仍然与专门的WebAssembly运行时(如Wasmer和Wasmtime)具有竞争力。
2026 年 WebAssembly 运行时性能
本文使用 libsodium 加密库对多种 WebAssembly 运行时(WAVM、WasmEdge、WAMR、wasm2c、Wasmer、Wasmtime、Wazero、Node、Bun)进行了性能基准测试,比较了 2024、2025 和 2026 年的版本。结果显示,WAVM、WasmEdge(AOT)、WAMR(AOT)、wasm2c、Wasmer 和 Wasmtime 在 CPU 密集型加密任务中实现了接近原生的性能,而 wide_arithmetic 指令对加密代码有益。
WATaBoy:将Game Boy指令即时编译为Wasm,性能超越原生解释器
本文介绍了WATaBoy,一个Game Boy模拟器,它使用即时编译到WebAssembly的方式,实现了超越原生解释器的性能,是JIT到Wasm在模拟领域的一个概念验证。
Gossamer:一种具有真实goroutines和无暂停内存的Rust风格语言
Gossamer是一种受Rust启发的新编程语言,具有真实goroutines、基于引用计数和区域的无暂停确定性内存管理,以及配备LLVM编译的字节码虚拟机。它旨在提供富有表现力的语法,无需借用检查器或垃圾回收暂停。