本节摘要:Wasm 的设计目标可以压缩成一句话——给字节发一张全球通行的签证。本节先立起模块、导入导出、线性内存、确定性执行四组核心概念,再逐项对账性能、安全、可移植三张特性清单,最后用三组对照(对 JavaScript、对容器、对原生代码)划清它的能力边界。
上一节我们沿时间线看到了 Wasm 的出身,本节负责把出身转化为理解力:后续章节反复出现的术语——模块、实例、导入、导出、线性内存、验证——都在这里正式定义。读完本节再进入第二章的格式拆解,你会始终知道每个字节在为哪条设计目标服务。
误读其一:Wasm 是"更快的 JavaScript"。错位在于层次——Wasm 不解释 JavaScript,也不替代它的生态位,两者是同页共事的邻居而非竞争者;Wasm 快,是因为它压根不是动态语言,而是一份静态类型的可验证指令集。误读其二:Wasm 是浏览器专用的字节码。规范的字里行间没有任何一处依赖浏览器 API——HTTP 加载、DOM 操作统统是宿主注入的能力,模块本体对运行环境一无所知。理解了这两点,下面四组概念才不会被读歪。
**模块(Module)与实例(Instance)**的区分是第一道门槛。模块是编译后的字节集合,处于"已验证、未运行"状态——像通过了验关、还停在堆场的集装箱;实例是模块加载进内存、导入已接通、可以调用的运行态——集装箱被吊上船、通了电。一个模块可以实例化多次,彼此独立。这个区分贯穿全书:验证规则作用于模块,运行语义作用于实例。
**导入(Import)与导出(Export)**定义了模块与外界交互的全部通道。Wasm 模块不能偷偷调系统函数、不能主动开网络连接——它需要什么能力,必须在模块头部如实申报;宿主决定给不给、给多少。下面的 WAT 片段展示了一份最小申报单:
(module ;; 申报:需要宿主提供一个名为 env.log 的函数,签名是吃一个 i32 (import "env" "log" (func $log (param i32))) ;; 本地定义:计算平方 (func $square (param $x i32) (result i32) local.get $x local.get $x i32.mul) ;; 申报出口:把 square 函数以 "square" 之名交出去 (export "square" (func $square)) (export "run" (func $run)) (func $run i32.const 42 call $log)) ;; 调用宿主注入的日志函数
模块只承诺"run 被调用时会拿常量去调 log",至于 log 是浏览器的控制台、是服务端的日志文件、还是测试桩,模块既不知道也不关心。这种"申报-授予"模型是 Wasm 安全性的根,第六章讲 WASI 时会把它扩展到文件与网络。
**线性内存(Linear Memory)**是模块唯一可自由读写的字节区域——一块连续、有界、从零开始的地址空间。所有访问都经过边界检查,越界直接触发陷阱(trap)而不是读到邻居家。宿主可以创建内存并把它交给多个模块共享,这是第五章数据传递与第六章多线程的物理基础。
确定性执行意味着:同一模块在合规引擎上以相同输入运行,结果与副作用序列完全一致,除了时间测量这类被显式标注为非确定的能力。这一点常被低估——它是区块链虚拟机选中 Wasm 的首要理由,也是服务端灰度发布与故障复现时"字节级可复算"的底气。
把"关键特性"摊开对账,性能、安全、可移植各有一本账。
性能这本账要分清两类收益。启动快:模块是紧凑二进制,解析与编译远快于等体积的 JavaScript 文本,AOT 编译型运行时更能做到近乎零预热;执行稳:JIT 不再猜类型,热点路径的行为可预测,长任务不会因反优化(deopt)突然掉速。但"接近原生"不等于"等同原生"——边界检查、跨语言调用开销、GC 暂停(若宿主有 GC 交互)都是要计入的成本,第七章会给出测量方法。
安全这本账的核心词是"纵深":类型验证挡住畸形模块,控制流结构化挡住任意跳转,线性内存边界挡住越界读写,能力导入挡住越权系统调用。四层各管一段,没有哪层单独扛全部风险,第八章安全节会逐层拆。
可移植这本账最容易被一句"跨平台"带过,值得展开说:可移植性不只是"能在多平台运行",而是"在同一套验证语义下运行"——x86 服务器、ARM 手机、RISC-V 开发板上的引擎对同一模块给出相同的接受/拒绝判定、相同的执行结果。软件供应链因此多了一种硬承诺:同一份字节,处处同行为。
| 对照对象 | 相同之处 | 分界线在哪里 |
|---|---|---|
| JavaScript | 同页运行、共享内存、互调无障碍 | Wasm 管计算密度,JS 管 DOM 与胶水编排 |
| Docker 容器 | 都做隔离与分发 | 容器隔离整个进程与文件系统视图,Wasm 隔离到单个模块的指令与内存 |
| 原生二进制 | 都是编译产物、都能 AOT | 原生代码绑定指令集与 ABI,Wasm 字节与硬件解耦、验证前置 |
对照表之外,还有三句值得刻在脑边的否定句。它不是通用虚拟机哲学的复刻——JVM/.NET CLR 面向带运行时的托管语言,字节码里就有对象与异常;Wasm MVP 刻意只保留最小类型,把高级语义留给编译器与后续提案。它也不是操作系统——没有进程、线程(原生线程长期缺席,提案仍在推进)、文件系统,一切资源靠宿主注入。它更不是反 JavaScript 的政治宣言——主流框架的路线恰恰是"JS 做壳、Wasm 做核",两者按成本分工。
概念立好了,下一节去看这些概念分别停靠在哪些真实的码头上。