News Hacker|极客洞察

🤔WASM不是纯栈机:线性验证、local.tee 与 multi-value
都靠类型注解了,还非要叫栈机吗?

🎯 讨论背景

围绕一篇题为《WASM is not quite a stack machine》的文章,评论在争论 WebAssembly(WASM,一种浏览器与嵌入式场景常用的可移植字节码格式)到底是不是传统 stack machine。许多评论把重点放在它的 linear-time、single-pass 验证器上:通过 block/loop/if 的类型签名、`local.tee`、multi-value 等设计,让验证器几乎不需要做控制流汇合处的数据流求解。讨论还频繁对比 JVM(Java 虚拟机)和它后来补上的 StackMapTable(用于给验证器提供分支目标类型信息),以及 WAT(WebAssembly Text Format,WebAssembly 的文本格式)、Binaryen(WASM 工具链/优化器)和 WABT(WebAssembly Binary Toolkit)在文本语法上的差异。后半段则转向现实工程:有人在用 WASM 编译到 C 或 Go,也有人借题发挥,讨论 WASM 早期 hype 是否已经退潮、是否更适合当作稳定的 porting target。

📌 讨论焦点

WASM更像结构化IR而非传统栈机

评论认为,WASM 的核心不是“栈”本身,而是一个带类型约束的结构化中间表示。真正决定形态的是 linear-time、single-pass 的验证器:它只做局部检查,不需要像 JVM 那样在控制流汇合处做复杂的数据流合并。block、loop、if 都带函数式签名,入口和出口类型必须匹配,这让验证既快又可预测。于是所谓的 postfix/stack 只是编码层面的结果,而不是运行模型的本质。

[来源1] [来源2] [来源3] [来源4] [来源5]

类型系统把复杂性推给生产端

另一条线强调 WASM 把复杂性前移到类型系统和代码生成端。`local.tee`、block 返回值、multi-value、typed function references、GC 里的显式子类型和异常 tag,都是把更多语义写进类型注解,让验证器仍然保持线性。评论把这视为一种模式:生产端多写一点信息,运行时就少做昂贵推导。JVM 的 StackMapTable 也被拿来类比,说明这类设计并不是妥协,而是有意选择。

[来源1] [来源2] [来源3]

编译器实现与 stack-to-locals 转换

有评论从编译器实现角度看待这种设计,认为把 WASM 编到 C 或 Go 反而更顺手。由于栈行为局限在函数内部,可以用一个表达式栈,或者把栈值映射到 locals,很多代码生成都能稳定地落在单个函数帧里。问题随之转到 liveness、循环索引、参数和跨块优化是否必须整函数一起做,以及多值返回如何影响转换策略。这里的讨论更像是在说:WASM 适合作为 porting target,但编译器要接受它的控制流和局部变量模型。

[来源1] [来源2] [来源3] [来源4] [来源5] [来源6] [来源7]

WAT文本语法与 binary 表示的争议

另一组评论在争论 WAT 文本格式到底是不是“栈式语言”。有人认为它只是 binary format 的语法糖,LISP-like 的括号只是更方便地写表达式树。也有人指出,工具链对 folded/unfolded 写法的限制、`wat2wasm` 和 Binaryen 的差异,会让“理论上等价”的语法在实践里并不完全等价。多值返回的例子尤其暴露出:某些顺序写法在栈式文本里很自然,但套进 prefix 形式就会变得很别扭。

[来源1] [来源2] [来源3] [来源4] [来源5] [来源6]

`dup`缺失与 `local.tee` 的替代

关于 `dup` 缺失的抱怨主要来自代码生成,希望更紧凑、更少局部变量操作。回复里普遍的观点是,`local.tee` 已经覆盖了大多数“复制后继续使用”的需求:它相当于把值写入 local 但不弹栈,因此能比 `set`+`get` 少一条指令。反驳者也承认,只有在初始化后还要继续使用同一个对象引用时,`dup` 才显得直观;而在很多场景里,locals 本来就是复用值的标准方式。这个争论再次说明,WASM 更偏向 typed locals,而不是增加更多显式栈操作。

[来源1] [来源2] [来源3] [来源4] [来源5] [来源6] [来源7]

WASM的成熟与失望情绪

最后一组评论把话题拉回 WASM 的现实表现:有人觉得早年的“替代 JavaScript、统一浏览器生态、让 Electron 飙速”的承诺并没有兑现。反驳者则举出 Rust 游戏引擎、SSH2 in browser、Google Sheets、Microsoft Office 和 Figma 这类实际应用,认为 WASM 只是从 hype 走向常态。也有人担心 spec 继续加入 GC、tail calls、multi-memory 等可选特性会让平台越来越臃肿,甚至有人觉得当初不如直接选 RISC-V。整体情绪是失望与辩护并存,但共识是 WASM 已经不是“新奇玩具”,而是一个实用的 porting target。

[来源1] [来源2] [来源3] [来源4] [来源5] [来源6] [来源7] [来源8] [来源9] [来源10] [来源11]

📚 术语解释

单遍线性验证器: WASM 的类型验证机制,只需一次线性扫描就能完成检查,不需要全图数据流求解。

WAT: WebAssembly 的文本格式,通常用 S-expression 来写指令序列。

multi-value: WASM 的多值返回/多值块签名扩展,用来表达多个结果并保持验证简单。

local.tee: WASM 指令:把值写入 local,同时保留该值在栈顶,常被用来模拟 `dup`。

Binaryen: 一个 WebAssembly 工具链/优化器项目,常用于转换、优化和生成 WASM 代码。