- 引言 — 6 月 11 日实际发生了什么
- WASI 0.2 真正的问题 — 三明治问题
- 三种新的原语
- wasi:io 去哪儿了
- 按接口逐一看实际的变化
- 运行时准备好了吗 — Wasmtime 46 到底做了什么
- 说句实话 — 还没做完的部分
- 关于「快六个数量级」这个说法
- Component Model 1.0 与 WASI 1.0 — 没有日期
- 那么,现在该不该迁移
- 结语
- 参考资料
引言 — 6 月 11 日实际发生了什么
2026 年 6 月 11 日,WASI 子工作组投票批准了 WASI 0.3.0。标题是「WebAssembly 组件原生迎来了 async」。
这句话很容易听起来像炒作,而 WASM 阵营在过去几年里确实多次重复过「马上就到」。所以本文想把两件事分开来看 — 真正落地到规范和运行时里的东西,以及明明还没做完、却很容易被读成已经做完的东西。
先说结论。这次发布是真的。只不过「真」的含义,和大多数人期待的不一样。这不是一次性能发布,而是一次可组合性(composability)发布。而单看性能的话,此刻其实还有一段是在倒退的。这一点我也会在下文说清楚。
WASI 0.2 真正的问题 — 三明治问题
WASI 0.2 里其实也能做 async。wasi:io 包提供了 pollable 资源、input-stream / output-stream,以及 poll 函数,用它们就能表达非阻塞 I/O。
问题在组合组件的时候爆发。wasi.dev 的 0.3 概览文档举的例子很清楚。设想组件 A 调用 B,B 再调用宿主的情形。
A → B → Host
这里的 pollable 是绑定在单个组件实例上的资源。所以 B 没有办法把从宿主那里拿到的 pollable 的唤醒信号原样传给 A。B 能做的只是自己不停轮询来中继就绪信号,用文档的说法,实际当中那条唤醒链就这么断掉了。这就叫三明治问题(sandwich problem)— WASI 0.2 能表达 async,却无法跨组件边界组合它。
Bytecode Alliance 的发布文章把这一点说得更直白。在 WASI 0.2 里,每个组件都需要自己的事件循环或者 async 运行时,所以单个组件可以在宿主上运行,但这些事件循环彼此之间没有办法协调。结果就是,组件一旦使用了流式处理或 async API,这个组件就无法再和其他组件组合了。
为什么这会伤筋动骨,只要想想组件模型存在的理由就很清楚了。组件模型的卖点是「不分语言、可以拼装的二进制」,而实际代码几乎都要做 I/O,做 I/O 就得用 async,用了 async 就拼不起来。也就是说,最重要的功能,恰恰在最常见的场景里失效了。
WASI 0.3 的解法是把 async 下沉到组件模型的规范 ABI 里。现在事件循环只由宿主管理一份,所有组件共享它。调度和唤醒传播的所有权归运行时所有,所以不管生产者和消费者之间夹了多少个组件,async 都能正常工作。
三种新的原语
作为一等公民加入规范 ABI 的一共有三样 — async func,以及流(stream)和 future 类型。
// 将函数本身声明为 async
handle: async func(request: request) -> result<response, error-code>;
// stream<T>: 带类型的异步数据通道
// future<T>: 单个值的异步完成
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
有几处设计决策值得细看。
所有权。 流和 future 的行为像资源类型 — 各自是一个被拥有的句柄,跨组件边界传递时,所有权会从调用者转移给被调用者。但和资源不同的是,它们不能被借用(不支持 borrow)。0.2 里的 input-stream 是绑定在实例上的资源、传不出去,现在正好相反,流成了可以传递的值。三明治问题正是在这一点上被解开的。
基于完成(completion-based)的模型。 这不是基于就绪(readiness-based)的模型。发布文章把它比作 Linux 的 io_uring、Windows 的 IOCP/IoRing,并明确指出,如果需要 epoll 或 kqueue 那种基于就绪的 API,可以在这之上做模拟。这个选择不是口味问题,而是直接关系到可组合性 — 因为就绪信号必须中继传递,而完成信号只需要运行时直接唤醒就够了。
解决流状态问题。 在 0.2 里,流的终止错误是搭在每次 read 调用上内联返回的。所以调用方只有读到最后才知道结果,一旦中途停止读取,就没法区分正常结束和出错。0.3 给流额外附带了一个 future,让结果的确定与消费了多少流无关、单独敲定。上面代码里把流和 future 打包成元组一起返回的写法,正是这个答案。
wasi:io 去哪儿了
wasi:io 包被整个删除了。不存在 WASI 0.3 版本的 wasi:io。它原来干的活儿,全都被吸收进了组件模型的原生原语里。
发布说明给出的映射表,概括了这次发布的一半内容。
WASI 0.2 (wasi:io) → WASI 0.3 (Component Model)
--------------------------------------------------------------
resource pollable → future<T>
resource input-stream → stream<u8>
resource output-stream → stream<u8> (写入方向)
poll(list<pollable>) → await future(由运行时处理)
subscribe() on resource → 调用返回 future<...>
start-foo / finish-foo → foo: async func(...)
最后一行在实践中感受最明显。0.2 里 start-bind / finish-bind、start-connect / finish-connect 那种两段式的舞步,以及驱动它们的 subscribe(),统统消失,折叠成一个 async func。
按接口逐一看实际的变化
发布说明说,大多数变化都是机械式的。0.2 为了假装能做 async 而耍的那些花活儿不再需要了,同样的事现在能写得简洁得多。不过有几处并不是机械式的。
wasi:http — 变化幅度最大。 只有这里不是把轮询接口机械转换成 async,而是重新组织了 world 和核心抽象。incoming-request、outgoing-request、incoming-response、outgoing-response、incoming-body、outgoing-body、future-trailers、future-incoming-response、response-outparam — 这些资源全部消失,整合成了统一的 request / response 资源,其中 body 是流,trailer 是 future。而且 world 一分为二。
// service world:发起 HTTP 调用,处理传入的请求
world service {
import client;
export handler;
}
// middleware world:service 的超集
world middleware {
include service; // service 能做的事全都能做
import handler; // 也可以把传入的请求转给另一个 handler
}
0.2 时代的 proxy world 被 service 取代,middleware world 则把请求路径上的中间件表达为一等公民。
wasi:sockets — 接口从 7 个减到 2 个。 network、instance-network、tcp、tcp-create-socket、udp、udp-create-socket 合并成一个统一的 types 接口,里面装着 tcp-socket 和 udp-socket 资源,DNS 则交给单独的 ip-name-lookup 负责。而且network资源没了——0.2 把网络访问建模成一个随着每次 bind/connect/lookup 调用一起传递的能力(capability)资源,0.3 则取消了它,改在 world 导入这一层级授予权限。
wasi:clocks — 大部分是改名。 wall-clock 改成了 system-clock,datetime 改成了 instant。原因是一致性 — 「wall clock」和「datetime」这两个词,POSIX 里没有,Rust 的 std::time 里也没有,是 WASI 独有的说法。instant 记录里的秒字段从 u64 改成了 s64,这样就能表示纪元之前的时间戳了。下游影响大体上是查找替换级别的,但发布说明特意把这一点标出来,是因为这种改动波及了整个测试套件。
wasi:random — 名字背后藏着语义变化。 get-random-bytes 的参数从 len 改成了 max-len,这不是单纯改名。实现现在可以返回比请求更少的字节数。也就是说,调用方得循环调用,直到攒够想要的字节数为止。要是以为这只是机械转换而一带而过,这里就很容易被咬一口。
运行时准备好了吗 — Wasmtime 46 到底做了什么
这里正是要把「承诺」和「出货」区分开的地方。
发布文章(6 月 11 日)预告说,Wasmtime 45 当时正在跑最新的候选发布版,而Wasmtime 46 将会把组件模型 async 默认打开,借此出货 WASI 0.3.0。这是将来时。
那么实际核实一下 — Wasmtime 46.0.0 于 2026 年 6 月 22 日发布,其发布说明里有这样一句话:Wasmtime 现在默认支持 WASI 0.3.0,component-model-async 这项 wasm 特性默认启用(#13612)。预告在 11 天后真正兑现。运行时这一侧是真的。
有一处文档不一致值得指出。wasi.dev 路线图写的是「WASI 0.3 支持从 Wasmtime 43+ 开始可用」,而发布文章说的是 45/46。看概览文档就能解释这个差异 — 从 Wasmtime v44 起,只要给 wasmtime serve 传 -Sp3 -W component-model-async=y 标志,就能服务 0.3 组件了(而且不导出 0.3 service world 的组件会自动回退到 0.2 的 wasi:http/proxy world),v44 还加入了 wasi:tls@0.3.0-draft 的初步支持。换句话说,藏在标志后面的支持来得更早,而默认打开是在 46。不同文档说出不同数字,是因为没有把这两者区分开。
一致性由共用的 wasi-testsuite 来验证,WASI 0.3 的覆盖测试在 Wasmtime 和 jco 上,跑在 Linux、macOS、Windows 三个平台上。
说句实话 — 还没做完的部分
从这里开始,才是写这篇文章的真正原因。只读发布文章的话,会觉得一切都已经完成了,但把它和 Bytecode Alliance 自己发的另一篇文章放在一起读,画面就会大不相同。
同步调用现在大约慢 3.5 倍。 The Road to Component Model 1.0 里有这样一段:目前的实现是通过宿主调用来管理跨组件边界的 async 任务基础设施的,这给纯同步调用路径也带来了大约 3.5 倍的开销(文中引用为 Nick Fitzgerald 在 Plumbers Summit 上测得的数字)。规范层面已经处理了一部分(P3 开发期间移除了 recursion check),剩下的计划在 WASI P3 出货之后处理 — 方向是重构任务状态,让同步适配器只在栈上分配一个轻量任务,使 Cranelift 能把大部分开销优化掉,目标是找回 async 支持加入之前的性能表现。
这句话要读得精确才行。目标不是「变得更快」,而是「恢复到以前那样快」。也就是说,把 async 原生地引进来的代价,现在正由同步路径在承担,而这笔账单还没还清。
guest 工具链还在跟进中。 规范获得批准,和你能在自己的语言里用上它,是两码事。发布文章写道,包括 Python、JavaScript、C#、C 在内的多种语言,guest 绑定生成器的 async 支持都还在进行中。jco 支持全部 WASI 0.3,但默认启用这个特性的版本「即将」发布;Rust 那边则通过 wit-bindgen 让 async fn 得到自然映射。Go 的情况不太一样,componentize-go 让 goroutine 在 ABI 边界处挂起,等流准备好了再恢复,跑的是有栈协程 — 这之所以可行,是因为组件模型的 async ABI 设计上同时容纳无栈和有栈协程。按发布文章的说法,「接下来的几周到几个月里」各个项目会陆续宣布 0.3 支持。这话的意思是,截至今天,大部分其实还没到。
线程和流拼接没能进入 0.3.0。 两者都被推到了后续的 0.3.x 版本。协作式线程是在组件模型这一层实现的(在 WASI 之下),Rust 组件只要用了 std::thread::spawn,不用在 WIT 里做任何事就能获得支持。进展方面,wasi-libc 的 pthreads 支持已经接近完成,一个 LLVM 补丁刚刚落地,Wasmtime 也有一个藏在特性开关后面的实现在跑。顺序是先协作式、后抢占式。流拼接(不经过中间拷贝就把一条流接到另一条流上)对流式性能很重要,但按文档的说法,只是因为太接近截止日期,没能赶上 P3 本身。
LLVM 可能是耗时最长的一环。 通向 1.0 的 ABI 工作,牵涉到 C ABI 级别的 multivalue 返回值,而 LLVM 目前在 C ABI 层面还不支持 multivalue。确切的 ABI 提案已经有了,但文章自己也承认,把它推上游(upstream)可能是这项工作里耗时最长的部分。顺带一提,LLVM 每六个月发布一次,而把东西合入 Rust 一路走到 stable 大约要九周。这整条流水线,Bytecode Alliance 并不能完全掌控。
目前还没有从 0.2 迁移的文档。 一份带有实际示例的详细 0.2 → 0.3 迁移指南,在组件模型文档里还标着「准备中」。
关于「快六个数量级」这个说法
发布文章里有这样一句话。得益于 middleware world 所支持的服务链式组合,那些彼此频繁通信的微服务组件,可以不经过网络、直接在同一进程内组合起来,于是有了这样的说法:「对大多数微服务而言,调用另一个微服务的耗时将从毫秒级降到纳秒级,缩短六个数量级(six orders of magnitude)」。
这句话应该这样来读。
- 这是一个预测,不是实测。原文用的是将来时(「will reduce」),既没给出基准测试,也没给出测量条件。
- 比较对象本身就不公平。拿「一次网络跳转」和「同一进程内的一次函数调用」相比,数量级自然就拉开了。这个数字不是因为 WASI 0.3 快,而是因为不走网络了。按同样的逻辑,把两个微服务合并成一个二进制文件,同样能拿到六个数量级的提升。
- 而且正如前一节所见,同一个组织的另一篇文章,现在写的是同步组件之间的调用有 3.5 倍开销。这两句话并不矛盾(一个是去掉网络带来的收益,一个是调用约定的开销)——但只有把它们并排放在一起看,才算是平衡的判断。
浏览器那边的数字,也最好用同样的标准来读。Mozilla 的 Ryan Hunt 做过一次实验,在 DOM 变动很多的 WASM VDOM 协调循环中,绕开 JavaScript 胶水层、直接调用浏览器 API,能拿到接近 2 倍的速度提升。文章本身也加了一句但书:「实际收益会因工作负载而异」。这是特定工作负载下的上限,不是一般性的预期。
WASM 阵营长期以来困扰的,正是这一点。技术本身确实很好,但宣传的说法总是快半拍。这次发布真正的成果不是纳秒级的速度,而是用上 async 的同时,组件依然能够组合起来,光是这一点,就已经是足够重大的消息了。
Component Model 1.0 与 WASI 1.0 — 没有日期
这一点经常被搞混,所以先说清楚:Component Model 1.0 和 WASI 1.0 是两个独立的里程碑。而且有先后顺序 — 只有组件模型先到达 1.0,WASI 1.0 才会随之而来。
Luke Wagner 在 Plumbers Summit 上打的一个比方,很好地说明了这层关系。组件模型是始终存在的微内核——它提供能在任何宿主上运行的基础原语。WASI 是搭在其上的操作系统服务(网络、存储、图形),像微内核之上的进程那样运行,不同设备上可能有、也可能没有。想想浏览器就容易理解了——浏览器对「应该存在哪些 I/O API」有非常强烈的立场,所以 WASI 接口在浏览器里是以 polyfill 的形式跑的。而组件模型本身只提供计算原语,因此可以原生地实现在核心 WASM 旁边。
但这里有一道不小的关卡。除非至少在两个浏览器引擎中都实现了原生支持,组件模型不能正式到达 1.0。 目前的状态是这样的 — Mozilla 团队最近在 WebAssembly CG 的线下会议上展示了性能结果,Chrome V8 团队也开了一个 issue 来评估组件模型的实现。文章自己的措辞很准确:这是有意义的信号,而不是承诺。
所以争取 1.0 的策略很有意思,用的正是 asm.js 时代那一套打法 — asm.js 当年埋了一个 no-op 字符串 "use asm",浏览器引擎能通过功能使用遥测检测到它,靠这些数据积累出优化的依据,最终促成了 WebAssembly 本身的诞生。jco 现在做的是同一件事。jco 生成的 JavaScript 胶水代码会吐出一个 "use components" 标识符(在 jco 1.16.8 中从 "use jco" 改名而来)。这么算下来,只要经 jco 转译的组件的真实使用量积累起来,就能成为原生实现的依据。
总结一下,没有任何一手资料给出过 WASI 1.0 的日期。 路线图页面上没有,讲述通往 1.0 之路的文章里也没有。路线图文档自己在第一行就打了预防针 — 这是一份持续更新的文档,目标和展望都是暂定的、可能会修改。(网上流传的「2026 年底/2027 年初为目标」这类数字,我在几个地方看到过,但一手资料里没有确认,所以这里不予采用。)
唯一确定的是节奏。WASI 的小版本发布,按照发布列车(release-train)模型,每两个月一次,在当月的第一个星期四发布 — 意思是不管什么东西「准备好上车」,都会按固定周期发车。0.3.0 之后,向后兼容的 0.3.x 版本都会搭这趟车。已排上日程的货物包括:融入语言惯用法的取消(cancellation)、流的特化与优化、扩大零拷贝范围的调用方提供缓冲区,以及线程。
那么,现在该不该迁移
先说最重要的事实。迁移不是强制的。 不需要为了用上支持 0.3 的运行时而迁到 0.3。wasmtime serve 用同一个二进制文件,按组件分别派发,0.3 组件和 0.2 组件都能跑。路线图也明确说明,实现可以并行支持 0.2 和 0.3,或者在 0.3 之上对 0.2 做 polyfill(虚拟化)。P1 模块照样能跑,P2 组件也照样能跑 — 这个团队从 P1 时代起,就靠语义化版本、并行实现、WASM-to-WASM 适配器来维持稳定性,这个故事在 1.0 之后也会继续下去。
现在迁移就值得的情况
- 你在真正地组合组件,而这条链路里牵涉到 async 或流式处理。如果你已经被三明治问题折腾过,这正是对症的药。
- 你想把 HTTP 中间件表达成组件——0.2 的
proxyworld 表达不了的东西,现在靠middlewareworld 成了一等公民。 - 有消费者会中途中断流,而你需要区分正常结束和出错。
- 能把运行时锁定在 Wasmtime 46+,而且 guest 语言是 Rust。
还是再等等比较好的情况
- 同步调用路径是你的热路径。3.5 倍开销的修复目前还只在规划阶段。
- guest 语言是 Python、C#、C 这类绑定的 async 支持还在进行中的语言。
- 你需要线程或流拼接——这得等 0.3.x。
- 你只是在宿主上跑单个组件。不做组合的话,0.2 就够了,0.3 解决的问题不是你的问题。
如果决定要迁移,有一个实务上的陷阱。把工具链版本一致地锁定住。 组件模型定义了规范的接口名,理论上兼容版本之间是可以链接的,但并不是所有工具都已经支持这种版本感知的链接。在那之前,Wasmtime 和绑定生成器(Rust 用 wit-bindgen,JavaScript 用 jco)必须瞄准同一个 WIT 版本 0.3.0。一旦对不上,就会在实例化那一刻,冒出一个很难看懂的 wrong type 错误。这类错误往往要花上大半天才能查到根源,提前知道这一点很值。
剩下的迁移步骤大体是机械式的 — 按上面的映射表替换 wasi:io 类型,选对合适的 world(CLI 用 wasi:cli/command,HTTP 服务器用 wasi:http/service,中间件用 wasi:http/middleware),把 start-foo / finish-foo 的调用点改成 async func。只要别在机械转换中漏掉前面提到的 wasi:random 的 max-len 这类语义变化就行。
结语
总结一下:WASI 0.3.0 于 2026 年 6 月 11 日获得批准,把 async 从 wasi:io 包下沉到了组件模型的规范 ABI 里。wasi:io 被删除,pollable / input-stream / output-stream / poll 被 async func 以及流、future 这两种原语取代。Wasmtime 46 在 6 月 22 日默认打开了这一切。
这次发布的价值不在速度,而在可组合性。在 0.2 里,组件一旦用了 async 就无法再组合 — 这是一个正面侵蚀组件模型存在理由的缺陷 — 0.3 把事件循环的所有权移交给宿主,修好了这个问题。光是这一点,就已经是足够重大的消息。纳秒级的营销说辞,本来就不需要。
同时说句实话:同步调用路径目前背负着大约 3.5 倍的开销,修复还只是个计划;guest 工具链在各个语言里都还在推进;线程和拼接要等下一趟车;Component Model 1.0 还在等两家浏览器引擎的原生实现;没有人承诺过 WASI 1.0 的日期。
所以实务上的判断很简单。如果你正在组合组件,而这条链路里有 async,现在就该看看它。 如果不是,0.2 会继续运行,polyfill 也在,下一趟车两个月后就来。这个项目里最值得信赖的,不是路线图上的日期,而是发布列车的节奏——而这一次,承诺的东西,真的在 11 天后就到了。
参考资料
- WASI 0.3 Launched — Bytecode Alliance 发布文章 (Bailey Hayes, Yosh Wuyts, 2026-06-11)
- WASI v0.3.0 发布说明 — 按接口列出的详细变化
- WASI 0.3 概览 — wasi.dev(三明治问题、迁移、运行时支持)
- WASI 路线图 — 发布列车与 0.3.x 计划项
- The Road to Component Model 1.0 (Eric Gregory, 2026-06-08) — 3.5 倍开销、浏览器路径、ABI 工作
- Wasmtime 46.0.0 发布说明 — WASI 0.3 默认启用 (2026-06-22)
- wasi-testsuite — 运行时一致性验证
- 浏览器之外的 WebAssembly — WASI 与组件模型概览(相关文章)
현재 단락 (1/95)
2026 年 6 月 11 日,WASI 子工作组投票[批准了 WASI 0.3.0](https://github.com/WebAssembly/WASI/releases/tag/v0.3.0)...