概述

概念

WebAssembly 编码了一种低级、类似汇编的编程语言。这种语言围绕以下概念构建。

WebAssembly 只提供四种基本数字类型。它们分别是整数和IEEE 754 数字,每种类型分别有 32 位和 64 位。32 位整数还用作布尔值和内存地址。这些类型上的常用操作都可用,包括它们之间完整的转换矩阵。没有区分有符号和无符号整数类型。相反,整数由各自的操作解释为二进制补码表示中的无符号或有符号。

除了这些基本数字类型之外,还有一个代表不同类型打包数据的 128 位宽向量类型。支持的表示形式是 4 个 32 位或 2 个 64 位IEEE 754 数字,或不同宽度的打包整数值,具体来说是 2 个 64 位整数、4 个 32 位整数、8 个 16 位整数或 16 个 8 位整数。

最后,值可以由代表指向不同类型实体的指针的不透明引用组成。与其他类型不同,它们的大小或表示形式不可观察。

指令

WebAssembly 的计算模型基于栈机。代码由按顺序执行的指令序列组成。指令操作隐式操作数栈[1] 上的值,并分为两大类。简单指令对数据执行基本操作。它们从操作数栈弹出参数并将结果推回到栈上。控制指令改变控制流。控制流是结构化的,这意味着它使用嵌套良好的结构来表达,例如块、循环和条件语句。分支只能指向此类结构。

陷阱

在某些情况下,某些指令可能会产生陷阱,立即终止执行。陷阱无法由 WebAssembly 代码处理,但会报告到外部环境,在外部环境中通常可以捕获它们。

函数

代码被组织成单独的函数。每个函数接受一系列值作为参数并返回一系列值作为结果。函数可以相互调用,包括递归调用,从而导致一个无法直接访问的隐式调用栈。函数还可以声明可变的局部变量,这些变量可用作虚拟寄存器。

是一个包含特定元素类型的不透明值的数组。它允许程序通过动态索引操作数间接选择这些值。目前,唯一可用的元素类型是无类型函数引用或对外部主机值的引用。因此,程序可以通过对表的动态索引间接调用函数。例如,这允许通过表索引模拟函数指针。

线性内存

线性内存是原始字节的连续可变数组。此类内存的创建具有初始大小,但可以动态增长。程序可以在任何字节地址(包括未对齐的地址)从线性内存加载和存储值。整数加载和存储可以指定一个存储大小,该大小小于相应值类型的尺寸。如果访问不在当前内存大小范围内,则会发生陷阱。

模块

WebAssembly 二进制文件采用模块的形式,其中包含函数、表和线性内存的定义,以及可变或不可变的全局变量。定义也可以导入,指定一个模块/名称对和一个合适的类型。每个定义都可以选择性地在多个名称下导出。除了定义之外,模块还可以为其内存或表定义初始化数据,这些数据采用的形式复制到给定的偏移量。它们还可以定义一个自动执行的启动函数

嵌入器

WebAssembly 实现通常会嵌入主机环境中。此环境定义如何启动模块加载、如何提供导入(包括主机端定义)以及如何访问导出。但是,任何特定嵌入的细节超出了本规范的范围,并将由补充的、环境特定的 API 定义提供。

语义阶段

从概念上讲,WebAssembly 的语义分为三个阶段。对于语言的每个部分,规范都指定了它们中的每一个。

解码

WebAssembly 模块以二进制格式分发。解码处理该格式并将其转换为模块的内部表示形式。在本规范中,这种表示形式由抽象语法建模,但实际实现可以将其直接编译为机器代码。

验证

解码后的模块必须是有效的。验证检查许多良好格式条件,以确保模块有意义且安全。特别是,它执行函数和其主体中指令序列的类型检查,例如确保操作数栈的使用一致。

执行

最后,有效的模块可以执行。执行可以进一步分为两个阶段

实例化。模块实例是模块的动态表示,包含其自身的 state 和执行栈。实例化在给定其所有导入的定义的情况下,执行模块主体本身。它初始化全局变量、内存和表,如果定义了,则调用模块的启动函数。它返回模块导出的实例。

调用。一旦实例化,可以通过对模块实例调用导出的函数来启动进一步的 WebAssembly 计算。给定所需的 arguments,它执行相应的函数并返回其结果。

实例化和调用是嵌入环境中的操作。