本节摘要:Wasm 并非凭空出现的银弹,而是浏览器性能困局、插件安全灾难与跨平台交付需求三股力量挤压出的产物。本节沿着 asm.js 实验、四大引擎标准化、WASI 出港、组件模型拼箱的完整时间线,讲清每个阶段要解决的核心矛盾,为全书后续的规范细节提供历史坐标。
本章是全书的起点,本节又是本章的地基:后面所有规范设计——类型系统、验证规则、内存模型——都能在这条时间线上找到动机。不了解来路,规范就只是条文;了解了来路,每条规范都是针对具体伤口的缝合。
阅读完本节,你应当能够:
早期 Web 平台的处境,像座只修了条跑道的机场:所有航班——文档、表格、游戏、视频编辑器——都被迫用同一种螺旋桨机型(JavaScript)起降。语言本身设计于九天内定稿的年代,服务的是表单校验,却被推着去跑桌面级应用。动态类型、解释执行、垃圾回收暂停,每一条都在拖重型计算的后腿。
各家的应对思路值得细看,因为它们直接塑造了 Wasm 的形态。Google 走过 Native Client(NaCl)路线:把 x86 原生代码关进沙箱,用静态分析限制内存访问。性能极好,但绑定硬件架构,"为 ARM 编译的模块在 x86 上无法加载",可移植性一败涂地;后来的 Portable Native Client(PNaCl)改用 LLVM 中间表示,方向对了,却始终没能在其他浏览器阵营获得支持。Mozilla 则押注 asm.js:不求浏览器改格式,只用一个极守纪律的 JavaScript 子集换取提前优化。
asm.js 的做法今天看依然精巧。它要求所有值都标注类型,内存访问全部经过一个巨大的类型化数组:
// asm.js 风格的代码片段:全部操作落在类型标注上 function asm_add(stdlib, foreign, heap) { "use asm"; var heap32 = new stdlib.Int32Array(heap); function add(x, y) { x = x | 0; // 声明 x 为 32 位整数 y = y | 0; // 声明 y 为 32 位整数 return (x + y) | 0; // 结果同样标注为 32 位整数 } return { add: add }; }
| 0 这种看似多余的位运算,实际是类型宣言:引擎识别出 "use asm" 指令后,可以把整个函数编译成高度优化的机器码,跳过运行时的类型推测。C++ 编译器(Emscripten 的早期版本)能把大型工程整体转译成这种代码,Firefox 上跑出的性能距离原生只差一截。但它的问题同样清晰:代码是文本,体积膨胀数倍、解析缓慢;类型标注靠约定而非强制,验证只能"尽力而为";每个浏览器引擎的优化程度不一,性能承诺没有统一底线。
asm.js 验证了"类型化低级代码可以跑得快",也暴露了"借道 JavaScript 终非长久之计"。于是 2015 年前后,一场罕见的跨阵营合作启动:Mozilla、Google、Microsoft、Apple 的引擎团队坐到一起,起草后来被称为 WebAssembly MVP 的规范。谈判中最关键的决定,回头看有三条。
其一,选栈式虚拟机而非寄存器机。栈机指令紧凑,且验证算法与生成算法都更简单,这对"浏览器要在毫秒级完成验证"的诉求至关重要。其二,强制静态验证。任何模块在实例化之前必须通过类型检查,验证不过就拒绝执行——安全不是运行时兜底,而是入关前置条件。这条决定把 NaCl 系方案的教训制度化。其三,文本与二进制双格式。二进制供网络传输,文本格式(WAT)供人类阅读调试,两者严格等价。
2017 年秋,四大浏览器在几乎相同的窗口期宣布默认支持,MVP 随即进入 W3C 推荐标准轨道。对开发者而言,这一天之后"编译到 Web"才真正摆脱了 polyfill 与降级方案:一份字节,四家引擎,行为一致。Figma 把渲染内核搬进 Wasm,AutoCAD Web 版复现桌面级几何计算,Unity 与 Unreal 把 Wasm 列为 Web 导出的一级后端——重型应用上 Web 的闸门就此打开。
如果故事停在 2017 年,Wasm 就只是前端优化的一个章节。转折发生在 2019 年:Mozilla 牵头的 WASI(WebAssembly System Interface)项目公布,目标是给浏览器之外的 Wasm 定义一套可移植的系统接口。旧思路是模仿 POSIX,但 POSIX 携带大量历史包袱——全局文件系统视图、进程模型、信号机制,每一条都和沙箱理念冲突。WASI 换了轴心:一切系统能力都通过显式导入授予,模块没申报的能力一概不可见。这套"能力导向"设计让 Wasm 有资格成为服务端与边缘场景的沙箱运行时,Fastly 的边缘计算平台、众多插件系统随后把它当作执行底座。
同一时期,核心规范也在快速增舱:引用类型与批量内存操作让宿主与模块之间的数据交换不再依赖笨重的表格搬运;多线程提案引入共享线性内存与原子操作;SIMD 提案带来 128 位向量指令;异常处理、尾调用、垃圾回收相继定稿或进入实现阶段。2020 年代中后期最值得下注的方向是组件模型:它定义了语言无关的接口描述语言(WIT)与二进制组件格式,让 Rust 写的加密库、Go 写的网络客户端、TypeScript 写的胶水层能按接口契约直接拼装,跨语言调用不再依赖手工写绑定。
下表把这条时间线收拢成一张速查单:
| 阶段 | 时期 | 标志成果 | 解决的核心矛盾 |
|---|---|---|---|
| 前史实验 | 2013 前后 | asm.js、NaCl | JS 太慢与原生代码不可移植的对立 |
| MVP 定稿 | 2015–2017 | 栈机、静态验证、双格式 | 字节码的可移植性与可验证性 |
| 标准化 | 2017 | W3C 推荐标准 | 四家引擎行为一致性 |
| 出港 | 2019 起 | WASI 能力接口 | 浏览器之外缺系统抽象 |
| 增舱 | 2019–2023 | 线程、SIMD、引用类型 | 单线程与数值性能天花板 |
| 拼箱 | 进行中 | 组件模型、GC、异常处理 | 跨语言互操作与托管语言接入 |

下一节我们把镜头从时间线切到设计图纸:Wasm 的核心概念与设计目标,看那些"针对伤口的缝合"具体缝在哪里。