WebAssembly规范精读(第三篇)——验证

307 阅读5分钟

序言

出于对成为编译器工程师的向往,我开始深入挖掘各项编译技术的细节。作为一名前端工程师,我决定首先从 WebAssembly 技术开始学习。本系列文章记录了我阅读 WebAssembly 规范的重点笔记。

你可以在此链接查阅完整的 WebAssembly 规范:WebAssembly Spec 。

约定

验证阶段检查 WebAssembly 模块格式的有效性。只有有效的模块才能被实例化。

有效性是通过模块及其内容的抽象语法上的类型系统来定义的。对于每段抽象语法,都有一个类型规则来指定适用于它的约束条件。所有规则都以两种等效形式给出:

  • 在描述性符号中,以直观的形式描述规则。
  • 在形式化符号中,以数学形式描述规则。

描述性和形式化规则是等价的,因此阅读本规范不需要理解形式化符号。形式化符号提供了更简洁的描述,广泛用于编程语言语义学,并且很容易进行数学证明。

上下文

规则的有效性是相对于上下文决定的,上下文收集周围模块和作用域内定义的相关信息:

  • TypesTypes:当前模块中定义的类型列表。
  • FunctionsFunctions:当前模块中声明的函数列表,由其函数类型表示。
  • TablesTables:当前模块中声明的表列表,由其表类型表示。
  • MemoriesMemories:当前模块中声明的内存列表,由其内存类型表示。
  • GlobalsGlobals:当前模块中声明的全局变量列表,由它们的全局类型表示。
  • ElementSegementsElement Segements:当前模块中声明的元素段列表,由其元素类型表示。
  • DataSegementsData Segements:当前模块中声明的数据段列表,每个段由一个 ok 条目表示。
  • LocalsLocals:当前函数中声明的局部变量列表(包括参数),由它们值的类型表示。
  • LabelsLabels:当前位置可访问的标签栈,由它们的结果类型表示。
  • ReturnReturn:当前函数的返回类型,为可选的结果类型,当不允许返回时不存在,如在独立表达式中。
  • ReferencesReferences:模块外部函数的函数索引列表,可以通过引用使用它们。

换句话说,上下文包含每个索引空间的类型列表,描述该空间中定义的每个条目。局部变量、标签和返回类型仅用于验证函数体中的指令,在其他地方为空。标签栈是上下文中唯一随指令序列验证而更改的部分。

非形式化符号

验证由每个抽象语法相关部分的规则定义。这些规则不仅定义了短语有效性的约束条件,还将其定义为一种类型。在描述这些规则时采用以下约定。

  • 当且仅当满足相应规则的所有约束时,短语 AA 称为 “对类型 TT 有效”。TT 的形式取决于 AA 是什么。

例如,如果 AA 是 function,则 TT 是 function type;如果 AA 是 global,则 TT 是 global type。

  • 规则隐式假设了一个给定的上下文 CC
  • 在某些地方,这个上下文被扩展为带有额外信息的上下文 CC'。采用“在上下文 CC',...语句...”这个表述,来表示以下语句必须适用于扩展上下文所体现的假设。

形式化符号

一个短语 AA 有相应类型的 TT,该命题写为:A:TA:T。然而,通常情况下,类型依赖于上下文 CC。为了明确表达这一点,完整形式是一个判断:CA:TC \vdash A:T,它表示假定上下文为 CCA:TA:T 成立。

正式的类型规则使用标准方法来定义类型系统,使用演绎规则表示它们。每个规则都具有以下一般形式:

Deduction rules

这样的规则被称为大含意:如果所有前提都成立,则结论成立。有些规则没有前提;它们是公理,其结论无条件成立。结论总是一个判断 CA:TC \vdash A:T,每个抽象语法结构都有一个相应的规则。

类型

Limit

Limit 必须具有有意义的边界,且在给定范围内。

Limit

  • nn 不能大于 kk
  • 如果 mm 不为空,则:
    • 它不能大于 kk
    • 它不能小于 nn
  • 则 limit 在 kk 范围内有效。

块类型(Block Types)

块类型可以用两种形式表示,这两种形式都将按照以下规则转换为普通函数类型。

typeidxtypeidx

  • 类型 C.types[typeidx]C.types[typeidx] 必须在上下文中定义。
  • 则块类型 C.types[typeidx]C.types[typeidx] 有效,表示函数类型。

Block Types typeidx

[valtype?][valtype^?]

  • 块类型有效,表示函数类型 [][valtype?][]\to[valtype^?]

Block Types valtype

函数类型(Function Types)

函数类型总是有效。

Function Types

表类型

Table Types

内存类型

Memory Types

全局类型(Global Types)

Global Types

外部类型(External Types)

func functype\textsf{func}~functype

External Types

table tabletype\textsf{table}~tabletype

mem memtype\textsf{mem}~memtype

global globaltype\textsf{global}~globaltype

导入子类型(Import Subtyping)

指令

指令通过栈类型(stack types)[t1][t2][t_1^*]→[t_2^*] 进行定义,描述指令如何处理操作数栈。

t1t_1^* 表示输入的类型,即从栈中弹出的数据。t2t_2^* 表示输出的类型,及推入到栈中的数据。栈类型类似于函数类型,除了它们允许将单个操作数分类为(底部),这表明该类型不受约束。作为辅助概念,操作数类型1匹配另一个操作数类型2,如果1是或等于2。这以逐点方式扩展到堆栈类型。

数值指令

模块

函数

内存

全局

元素段

数据段

导出

导入(Imports)

导入 importimport 和导入的描述 import descimport~desc 使用外部类型定义。

{module name1,name name2,desc importdesc}\{\textsf{module}~name_1,\textsf{name}~name_2,\textsf{desc}~importdesc\}
  • 导入描述 importdescimportdesc 必须是有效的 externtypeexterntype
  • 然后导入是有效的,类型为 externtypeexterntype

Valid imports

模块

模块根据导入的外部类型到导出类型的映射进行分类。

一个模块是完全封闭的,也就是说,它的组件只能引用出现在模块本身中的定义。因此,不需要初始上下文。相反,模块内容的验证上下文是从模块中的定义构建的。

  • modulemodule 为被验证的模块。
  • CC 为上下文,其中:
    • C.typesC.types 等于 module.typesmodule.types,
    • C.funcsC.funcs
    • C.tablesC.tables
    • C.memsC.mems
    • C.globalsC.globals
    • C.elemsC.elems
    • C.datasC.datas
    • C.localsC.locals 为空,
    • C.labelsC.labels 为空,
    • C.returnC.return 为空,
    • C.refsC.refs
  • CC^\prime 为上下文,其中:
    • C.globalsC^\prime .globals
    • C.funcsC^\prime .funcs
    • C.refsC^\prime .refs
    • 其他字段为空。

Modules