序言
出于对成为编译器工程师的向往,我开始深入挖掘各项编译技术的细节。作为一名前端工程师,我决定首先从 WebAssembly 技术开始学习。本系列文章记录了我阅读 WebAssembly 规范的重点笔记。
你可以在此链接查阅完整的 WebAssembly 规范:WebAssembly Spec 。
约定
验证阶段检查 WebAssembly 模块格式的有效性。只有有效的模块才能被实例化。
有效性是通过模块及其内容的抽象语法上的类型系统来定义的。对于每段抽象语法,都有一个类型规则来指定适用于它的约束条件。所有规则都以两种等效形式给出:
- 在描述性符号中,以直观的形式描述规则。
- 在形式化符号中,以数学形式描述规则。
描述性和形式化规则是等价的,因此阅读本规范不需要理解形式化符号。形式化符号提供了更简洁的描述,广泛用于编程语言语义学,并且很容易进行数学证明。
上下文
规则的有效性是相对于上下文决定的,上下文收集周围模块和作用域内定义的相关信息:
- Types:当前模块中定义的类型列表。
- Functions:当前模块中声明的函数列表,由其函数类型表示。
- Tables:当前模块中声明的表列表,由其表类型表示。
- Memories:当前模块中声明的内存列表,由其内存类型表示。
- Globals:当前模块中声明的全局变量列表,由它们的全局类型表示。
- ElementSegements:当前模块中声明的元素段列表,由其元素类型表示。
- DataSegements:当前模块中声明的数据段列表,每个段由一个 ok 条目表示。
- Locals:当前函数中声明的局部变量列表(包括参数),由它们值的类型表示。
- Labels:当前位置可访问的标签栈,由它们的结果类型表示。
- Return:当前函数的返回类型,为可选的结果类型,当不允许返回时不存在,如在独立表达式中。
- References:模块外部函数的函数索引列表,可以通过引用使用它们。
换句话说,上下文包含每个索引空间的类型列表,描述该空间中定义的每个条目。局部变量、标签和返回类型仅用于验证函数体中的指令,在其他地方为空。标签栈是上下文中唯一随指令序列验证而更改的部分。
非形式化符号
验证由每个抽象语法相关部分的规则定义。这些规则不仅定义了短语有效性的约束条件,还将其定义为一种类型。在描述这些规则时采用以下约定。
- 当且仅当满足相应规则的所有约束时,短语 A 称为 "对类型 T 有效"。 T 的形式取决于 A 是什么。
例如,如果 A 是 function,则 T 是 function type;如果 A 是 global,则 T 是 global type。
- 规则隐式假设了一个给定的上下文 C。
- 在某些地方,这个上下文被扩展为带有额外信息的上下文 C′。采用"在上下文 C′,...语句..."这个表述,来表示以下语句必须适用于扩展上下文所体现的假设。
形式化符号
一个短语 A 有相应类型的 T,该命题写为: A:T。然而,通常情况下,类型依赖于上下文 C。为了明确表达这一点,完整形式是一个判断: C⊢A:T,它表示假定上下文为 C, A:T 成立。
正式的类型规则使用标准方法来定义类型系统,使用演绎规则表示它们。每个规则都具有以下一般形式:

这样的规则被称为大含意:如果所有前提都成立,则结论成立。有些规则没有前提;它们是公理,其结论无条件成立。结论总是一个判断 C⊢A:T,每个抽象语法结构都有一个相应的规则。
类型
Limit
Limit 必须具有有意义的边界,且在给定范围内。

- n 不能大于 k
- 如果 m 不为空,则:
- 它不能大于 k
- 它不能小于 n
- 则 limit 在 k 范围内有效。
块类型(Block Types)
块类型可以用两种形式表示,这两种形式都将按照以下规则转换为普通函数类型。
typeidx
- 类型 C.typestypeidx 必须在上下文中定义。
- 则块类型 C.typestypeidx 有效,表示函数类型。

valtype?
- 块类型有效,表示函数类型 \[\]→valtype?。

函数类型(Function Types)
函数类型总是有效。

表类型

内存类型

全局类型(Global Types)

外部类型(External Types)
func functype

table tabletype
mem memtype
global globaltype
导入子类型(Import Subtyping)
指令
指令通过栈类型(stack types) t1∗→t2∗ 进行定义,描述指令如何处理操作数栈。
t1∗ 表示输入的类型,即从栈中弹出的数据。 t2∗ 表示输出的类型,及推入到栈中的数据。栈类型类似于函数类型,除了它们允许将单个操作数分类为(底部),这表明该类型不受约束。作为辅助概念,操作数类型1匹配另一个操作数类型2,如果1是或等于2。这以逐点方式扩展到堆栈类型。
数值指令
模块
函数
表
内存
全局
元素段
数据段
导出
导入(Imports)
导入 import 和导入的描述 import desc 使用外部类型定义。
{module name1,name name2,desc importdesc}
- 导入描述 importdesc 必须是有效的 externtype 。
- 然后导入是有效的,类型为 externtype。

模块
模块根据导入的外部类型到导出类型的映射进行分类。
一个模块是完全封闭的,也就是说,它的组件只能引用出现在模块本身中的定义。因此,不需要初始上下文。相反,模块内容的验证上下文是从模块中的定义构建的。
- 令 module 为被验证的模块。
- 令 C 为上下文,其中:
- C.types 等于 module.types,
- C.funcs
- C.tables
- C.mems
- C.globals
- C.elems
- C.datas
- C.locals 为空,
- C.labels 为空,
- C.return 为空,
- C.refs
- 令 C′ 为上下文,其中:
- C′.globals
- C′.funcs
- C′.refs
- 其他字段为空。
