WebAssembly 核心规范

编辑草案

有关此文档的更多详细信息
此版本
https://github.webassembly.net.cn/spec/core/bikeshed/
最新发布版本
https://www.w3.org/TR/wasm-core-2/
实施报告
https://webassembly.net.cn/features/
反馈
GitHub
编辑
Andreas Rossberg
问题跟踪
GitHub 问题

摘要

本文档描述了核心 WebAssembly 标准的 2.0 版,这是一种安全、可移植、低级代码格式,专为高效执行和紧凑表示而设计。

这是相关文档集的一部分:WebAssembly 核心规范WebAssembly JS 接口WebAssembly Web API

本文档的状态

这是一个公开的编辑草案副本。它仅供讨论,随时可能更改。它在此发布并不意味着 W3C 认可其内容。不要将此文档引用为除正在进行的工作之外的任何其他内容。

GitHub 问题 是讨论此规范的首选方式。所有问题和评论都被 存档

本文档由 WebAssembly 工作组 制作。

本文档由一个在 W3C 专利政策 下运作的组制作。W3C 维护一个 与该组交付成果相关的任何专利披露的公开列表;该页面还包括披露专利的说明。任何实际了解某个专利的人,如果他认为该专利包含 基本权利要求,则必须根据 W3C 专利政策第 6 节 披露相关信息。

本文档受 2023 年 11 月 3 日 W3C 流程文档 约束。

1. 简介

1.1. 简介

WebAssembly(缩写为 Wasm [1])是一种为高效执行和紧凑表示而设计的安全、可移植、低级代码格式。它的主要目标是在 Web 上启用高性能应用程序,但它不做出任何特定于 Web 的假设或提供特定于 Web 的功能,因此它也可以在其他环境中使用。

WebAssembly 是一种由 W3C 社区小组 开发的开放标准。

本文档描述了 WebAssembly 核心标准的 2.0 版(2024-09-13 草案)。它旨在被未来的增量版本取代,这些版本将包含更多新功能。

1.1.1. 设计目标

WebAssembly 的设计目标如下

  • 快速、安全、可移植的语义

    • 快速:以接近原生代码的性能执行,利用所有当代硬件通用的功能。

    • 安全:代码在内存安全的 [2]、沙盒环境中执行,以防止数据损坏或安全漏洞。

    • 定义明确:以易于非正式和正式推理的方式完整准确地定义有效程序及其行为。

    • 硬件无关:可以在所有现代架构上编译,包括台式机或移动设备和嵌入式系统。

    • 语言无关:不偏袒任何特定语言、编程模型或对象模型。

    • 平台无关:可以嵌入到浏览器中,作为独立的 VM 运行,或集成到其他环境中。

    • 开放:程序可以通过简单通用的方式与其环境进行交互。

  • 高效、可移植的表示

    • 紧凑:具有二进制格式,该格式速度快,因为比典型的文本或原生代码格式更小。

    • 模块化:程序可以分成更小的部分,这些部分可以分别传输、缓存和使用。

    • 高效:可以在快速单次传递中进行解码、验证和编译,无论是即时 (JIT) 还是提前 (AOT) 编译。

    • 可流式传输:允许在看到所有数据之前尽早开始解码、验证和编译。

    • 可并行化:允许将解码、验证和编译分成许多独立的并行任务。

    • 可移植:不做出现代硬件普遍支持的架构假设。

WebAssembly 代码还旨在易于检查和调试,尤其是在像 Web 浏览器这样的环境中,但这些功能超出了本规范的范围。

1.1.2. 范围

本质上,WebAssembly 是一种虚拟指令集架构(虚拟 ISA)。因此,它有许多用例,可以嵌入到许多不同的环境中。为了涵盖它们的各种用途并实现最大程度的重复使用,WebAssembly 规范被分成多个文档并分层。

本文档关注 WebAssembly 的核心 ISA 层。它定义了指令集、二进制编码、验证和执行语义,以及文本表示。但是,它没有定义 WebAssembly 程序如何与其执行的特定环境交互,也没有定义如何从这样的环境中调用它们。

相反,本规范由其他文档补充,这些文档定义了与特定嵌入环境(例如 Web)的接口。这些将分别定义适合给定环境的 WebAssembly 应用程序编程接口(API)

1.1.3. 安全注意事项

WebAssembly 并没有提供访问代码执行环境的任何环境访问权限。任何与环境的交互,例如 I/O、访问资源或操作系统调用,只能通过调用由 嵌入器 提供并导入到 WebAssembly 模块函数 来执行。嵌入器可以通过控制或限制其提供导入的函数功能来建立适合相应环境的安全策略。这些考虑因素是嵌入器的责任,也是特定环境 API 定义 的主题。

由于 WebAssembly 旨在被翻译成直接在主机硬件上运行的机器代码,因此它可能容易受到硬件级别侧信道攻击。在存在此问题的环境中,嵌入器可能需要采取适当的缓解措施来隔离 WebAssembly 计算。

1.1.4. 依赖项

WebAssembly 依赖于两个现有标准

但是,为了使本规范自包含,上述标准的相关方面在本规范中定义并形式化,例如 二进制表示舍入 浮点值,以及 值范围UTF-8 编码 Unicode 字符。

注意

上述标准是所有相应定义的权威来源。本规范中给出的形式化旨在与这些定义相匹配。描述的语法或语义中存在的任何差异应视为错误。

1.2. 概述

1.2.1. 概念

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

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

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

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

指令

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

陷阱

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

函数

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

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

线性内存

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

模块

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

嵌入器

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

1.2.2. 语义阶段

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

解码

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

验证

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

执行

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

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

调用。实例化后,可以通过调用模块实例上的导出函数来启动进一步的 WebAssembly 计算。给定必需的参数,这将执行相应函数并返回其结果。

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

2. 结构

2.1. 约定

WebAssembly 是一种编程语言,具有多种具体表示形式(其 二进制格式文本格式)。两者都映射到一个通用结构。为了简洁起见,这种结构以抽象语法的形式描述。本规范的所有部分都根据此抽象语法定义。

2.1.1. 语法表示法

在定义抽象语法的语法规则时,采用以下约定。

  • 终结符(原子)以无衬线字体或符号形式编写: .

  • 非终结符以斜体形式编写: .

  • 的序列。

  • 是一个可能是空的 迭代序列。 (这是 的简写,在 不相关的情况下使用。)

  • 是一个非空的 迭代序列。 (这是 的简写,其中 。)

  • 的可选出现。 (这是 的简写,其中 。)

  • 产生式写作 .

  • 大型产生式可以拆分为多个定义,用显式省略号 () 结束第一个定义,并用省略号 () 开始后续定义。

  • 有些产生式用圆括号 (“”) 附加了边条件,用于简化将产生式组合扩展为多个独立情况的写法。

  • 如果同一个元变量或非终结符在产生式中多次出现,则所有这些出现必须具有相同的实例化。 (这是对多个不同变量相等的要求的简写。)

2.1.2. 辅助符号

在处理语法结构时,还会使用以下符号

  • 表示空序列。

  • 表示序列 的长度。

  • 表示序列 的第 个元素,从 开始。

  • 表示序列 的子序列。

  • 表示与 相同的序列,但将第 个元素替换为

  • 表示与 相同的序列,除了子序列 被替换为 .

  • 表示通过连接所有序列 (在 中)而形成的扁平序列。

此外,采用以下约定。

  • 符号 ,其中 是一个非终结符,被视为一个元变量,它遍历 的相应序列(类似于 )。

  • 当给定一个序列 时,则假设序列 中的 的出现与 呈逐点对应关系(类似于 )。这隐式地表达了一种在序列上映射语法结构的形式。

以下形式的产生式被解释为记录,它们将一组固定的字段 分别映射到“值” .

采用以下符号来操作这些记录。

  • 表示 组件在 中的内容。

  • 表示与 相同的记录,只是 组件的内容被替换为 .

  • 表示两个具有相同字段序列的记录的组合,通过逐点追加每个序列来完成。

  • 表示对一个记录序列进行组合,按顺序进行;如果序列为空,则结果记录的所有字段都为空。

序列和记录的更新符号递归地推广到通过“路径”访问的嵌套组件

  • 的简写形式,

  • 的简写形式,

其中 简化为

2.1.3. 向量

向量 是形式为 (或 )的有界序列,其中 可以是值或复杂结构。一个向量最多可以有 个元素。

2.2.

WebAssembly 程序操作原始数值。此外,在程序的定义中,值的不变序列用来表示更复杂的数据,例如文本字符串或其他向量。

2.2.1. 字节

最简单的值形式是原始的未解释字节。在抽象语法中,它们用十六进制文字表示。

2.2.1.1. 约定
  • 元变量 代表字节。

  • 字节有时被解释为自然数

2.2.2. 整数

不同类别的整数具有不同的值范围,它们由它们的位宽 以及它们是无符号还是有符号来区分。

定义了未解释的整数,其有符号解释可能根据上下文而有所不同。在抽象语法中,它们表示为无符号值。但是,某些操作会 转换 它们为基于二进制补码解释的有符号值。

注意

本规范中出现的主要整数类型是 。但是,其他大小作为辅助结构出现,例如,在 浮点数 的定义中。

2.2.2.1. 约定
  • 元变量 表示整数。

  • 数字可以用简单的算术表示,如上面的语法所示。为了区分 这样的算术运算和 这样的序列,后者用括号来区分。

2.2.3. 浮点数

浮点数数据表示与 [IEEE-754-2019] 标准(第 3.3 节)中相应的二进制格式相对应的 32 位或 64 位值。

每个值都有一个符号和一个大小。 大小可以表示为形式为 , 其中 是指数,尾数,其最高有效位 ,或者表示为非规格化数,其中指数固定为最小可能值,; 非规格化数中包括正零和负零值。 由于尾数是二进制值,因此规格化数表示为 , 其中 的位宽; 非规格化数也是如此。

可能的大小还包括特殊值 (无穷大)和 NaN,非数字)。 NaN 值具有一个有效载荷,它描述了底层 二进制表示 中的尾数位。 信号 NaN 和安静 NaN 之间没有区别。

其中

规范 NaN 是一个浮点值 ,其中 是一个有效载荷,其最高有效位为 ,而其他所有位都为

算术 NaN 是一个浮点值 ,其中 ,使得最高有效位为 ,而其他所有位都为任意值。

注意

在抽象语法中,次正规数通过尾数的开头 0 来区分。次正规数的指数与正规数的最小可能指数相同。仅在 二进制表示 中,次正规数的指数以与任何正规数的指数不同的方式编码。

这里定义的规范 NaN 概念与 [IEEE-754-2019] 标准(第 3.5.2 节)为十进制交换格式定义的规范 NaN 概念无关。

2.2.3.1. 约定
  • 元变量 涵盖浮点值,其含义从上下文中清楚。

2.2.4. 向量

数值向量 是 128 位值,由向量指令(也称为 SIMD 指令,单指令多数据)处理。它们在抽象语法中使用 表示。通道类型(整数浮点数)和通道大小的解释由对它们进行操作的特定指令确定。

2.2.5. 名称

名称字符的序列,字符是 [UNICODE](第 2.4 节)中定义的标量值

由于 二进制格式 的限制,名称的长度受其 UTF-8 编码长度的限制。

2.2.5.1. 约定
  • 字符(Unicode 标量值)有时与自然数 可互换使用。

2.3. 类型

WebAssembly 中的各种实体按类型分类。类型在 验证实例化 以及可能 执行 期间进行检查。

2.3.1. 数值类型

数值类型 用于对数值进行分类。

类型 分别对应于 32 位和 64 位整数。整数本身并非有符号或无符号,它们的解释由各个操作决定。

类型 分别对应于 32 位和 64 位浮点数。它们分别对应于二进制浮点数表示,也称为单精度双精度,如 [IEEE-754-2019] 标准(第 3.3 节)中所定义。

数值类型是透明的,这意味着它们的位模式是可以观察到的。数值类型的值可以存储在 内存 中。

2.3.1.1. 约定
  • 符号 表示数值类型 位宽。也就是说,.

2.3.2. 向量类型

向量类型 用于对由向量指令处理的 数值 向量进行分类(也称为SIMD 指令,单指令多数据)。

类型 对应一个包含128位整型或浮点型数据的打包向量。打包数据可以解释为有符号或无符号整型,单精度或双精度浮点型值,或单个128位类型。解释方式由各个操作确定。

向量类型,如同 数字类型,是透明的,这意味着它们的位模式是可以观察到的。向量类型的值可以存储在 内存 中。

2.3.2.1. 约定
  • 符号 用于表示 位宽,它也适用于向量类型,也就是说,.

2.3.3. 引用类型

引用类型对运行时 存储 中对象的的一级引用进行分类。

类型 表示所有对 函数 的引用的无限并集,无论它们的 函数类型 是什么。

类型 表示所有由 嵌入器 拥有并可以以这种类型传递到 WebAssembly 中的引用的无限并集。

引用类型是不透明的,这意味着它们的尺寸或位模式都无法观察到。引用类型的值可以存储在 表格 中。

2.3.4. 值类型

值类型对 WebAssembly 代码可以计算的值和变量接受的值进行分类。它们要么是 数字类型,要么是 向量类型,要么是 引用类型

2.3.4.1. 约定
  • 元变量 在上下文中明确的情况下,表示值类型或其子类。

2.3.5. 结果类型

结果类型执行 指令函数 的结果进行分类,结果是一个值序列,用方括号表示。

2.3.6. 函数类型

函数类型函数 的签名进行分类,将参数向量映射到结果向量。它们也用于对 指令 的输入和输出进行分类。

2.3.7. 限制

限制 用于对与 内存类型表格类型 相关的可调整大小的存储的大小范围进行分类。

如果没有指定最大值,则相应的存储可以增长到任何大小。

2.3.8. 内存类型

内存类型 用于对线性 内存 及其大小范围进行分类。

这些限制会约束内存的最小大小以及可选的最大大小。限制以 页面大小 为单位。

2.3.9. 表格类型

表格类型 用于对在给定大小范围内包含 引用类型 元素的 表格 进行分类。

与内存类似,表格也会受到限制,用于约束其最小大小和可选的最大大小。限制以条目数为单位。

注意

在 WebAssembly 的未来版本中,可能会引入其他元素类型。

2.3.10. 全局类型

全局类型 用于对 全局变量 进行分类,这些变量会保存一个值,并且可以是可变的,也可以是不可变的。

2.3.11. 外部类型

外部类型 用于对 导入外部值 及其相应的类型进行分类。

2.3.11.1. 约定

以下辅助符号用于外部类型序列。它以保留顺序的方式筛选出特定类型的条目。

2.4. 指令

WebAssembly 代码由一系列指令组成。其计算模型基于堆栈机,指令在隐式操作数堆栈上操作值,消耗(弹出)参数值并生成或返回(压入)结果值。

除了来自堆栈的动态操作数之外,一些指令还具有静态立即参数,通常是索引或类型注释,它们是指令本身的一部分。

一些指令在结构上是结构化的,因为它们对嵌套的指令序列进行括号。

以下部分将指令分为多个不同的类别。

2.4.1. 数值指令

数值指令提供对特定类型的数值的基本操作。这些操作与硬件中可用的相应操作密切匹配。

数值指令按数值类型划分。对于每种类型,可以区分几个子类别

  • 常量:返回一个静态常量。

  • 一元运算:消耗一个操作数并生成一个相应类型的结果。

  • 二元运算:消耗两个操作数并生成一个相应类型的结果。

  • 测试:消耗一个相应类型的操作数并生成一个布尔整型结果。

  • 比较:消耗两个相应类型的操作数并生成一个布尔整型结果。

  • 转换:消耗一个类型的值并生成另一个类型的结果(转换的源类型是“”之后的那个)。

一些整型指令有两种形式,其中一个符号位注释区分操作数是作为解释无符号还是有符号整数。对于其他整型指令,使用二进制补码进行有符号解释意味着它们的行为相同,无论符号位如何。

2.4.1.1. 约定

有时,根据以下语法简写将运算符分组在一起会很方便。

2.4.2. 向量指令

向量指令(也称为SIMD 指令,即单指令多数据)提供了对 的基本操作向量类型.

向量指令的命名约定包括一个前缀,该前缀决定了操作数的解释方式。该前缀描述了操作数的 *形状*,写成 ,由一个打包的 数值类型 和该类型的 *通道* 数 组成。操作在每个通道的值上逐点执行。

注意

例如,形状 将操作数解释为四个 值,打包成一个 。数值类型 的位宽乘以 始终为 128。

为前缀的指令不涉及特定解释,并将 视为 值或 128 个独立位的向量。

向量指令可以分组到几个子类别中

  • 常量:返回一个静态常量。

  • 一元运算:消耗一个 操作数并生成一个 结果。

  • 二元运算:消耗两个 操作数并生成一个 结果。

  • 三元运算:消耗三个 操作数并生成一个 结果。

  • 测试:消耗一个 操作数并生成一个布尔整型结果。

  • 移位:消耗一个 操作数和一个 操作数,生成一个 结果。

  • 扩展:消耗一个数值类型的值并生成一个指定形状的 结果。

  • 提取通道:消耗一个 操作数并返回给定通道中的数值。

  • 替换通道:消耗一个 操作数和给定通道的数值,并生成一个 结果。

一些向量指令有一个有符号性注释 ,它区分操作数中的元素是 解释无符号 还是 有符号 整数。对于其他向量指令,使用二进制补码进行有符号解释意味着它们的行为相同,无论有符号性如何。

2.4.2.1. 约定

有时,根据以下语法简写将运算符分组在一起会很方便。

2.4.3. 引用指令

此组指令与访问引用相关。

这些指令分别生成空值、检查空值或生成对给定函数的引用。

2.4.4. 参数指令

此组指令可以在任何值类型的操作数上进行操作。

The instruction simply throws away a single operand.

The instruction selects one of its first two operands based on whether its third operand is zero or not. It may include a value type determining the type of these operands. If missing, the operands must be of numeric type.

注意

In future versions of WebAssembly, the type annotation on may allow for more than a single value being selected at the same time.

2.4.5. 变量指令

变量指令与访问 局部变量全局变量 相关。

这些指令分别获取或设置变量的值。The instruction is like but also returns its argument.

2.4.6. 表指令

这组指令与表 table 相关。

指令 分别用于加载或存储表中的元素。

指令 返回表的当前大小。指令 按给定的增量扩展表,并返回之前的大小,如果无法分配足够的空间,则返回 。它还接受新分配的条目的初始化值。

指令 将指定范围内所有条目设置为给定值。

指令 将元素从源表区域复制到可能重叠的目标区域;第一个索引表示目标。指令 将元素从 被动元素段 复制到表中。指令 防止进一步使用被动元素段。此指令旨在用作优化提示。在元素段被丢弃后,其元素将无法再被检索,因此此段使用的内存可能会被释放。

另一个访问表的指令是 控制指令

2.4.7. 内存指令

这组指令涉及线性 内存

使用 指令访问内存,这些指令对应不同的 数字类型。它们都接受一个 *内存立即数* ,其中包含地址 *偏移量* 和预期 *对齐方式*(用 2 的幂的指数表示)。整数加载和存储可以选择指定一个比相应值类型 位宽 更小的 *存储大小*。在加载的情况下,需要一个符号扩展模式 来选择适当的行为。

向量加载可以指定一个大小为 位宽 一半的形状。每个车道的大小是其正常大小的一半,符号扩展模式 随后指定如何将较小的车道扩展到较大的车道。或者,向量加载可以执行 *splat*,这样只加载指定存储大小的单个车道,并将结果复制到所有车道。

静态地址偏移量加到动态地址操作数,产生一个 33 位 *有效地址*,该地址是访问内存的零基索引。所有值都以 小端 字节顺序读取和写入。如果任何被访问的内存字节位于内存当前大小暗示的地址范围之外,则会导致 陷阱

注意

WebAssembly 的未来版本可能会提供具有 64 位地址范围的内存指令。

指令 返回内存的当前大小。指令 通过给定的增量扩展内存并返回之前的大小,如果无法分配足够的内存,则返回 。这两个指令都以 页大小 为单位。

指令 将区域中的所有值设置为给定的字节。指令 将数据从源内存区域复制到可能重叠的目标区域。指令 将数据从 被动数据段 复制到内存中。指令 阻止进一步使用被动数据段。此指令旨在用作优化提示。在数据段被丢弃后,其数据将无法再检索,因此此段使用的内存可能会被释放。

注意

在 WebAssembly 的当前版本中,所有内存指令都隐式地对 内存 索引 进行操作。此限制可能在未来版本中被解除。

2.4.8. 控制指令

此组中的指令影响控制流。

指令 什么也不做。

指令会导致无条件的 陷阱

指令是结构化指令。它们将嵌套的指令序列(称为)括起来,并以 伪指令终止或分隔。根据语法规定,它们必须是嵌套良好的。

结构化指令可以根据其注释的块类型在操作数栈上消耗输入并产生输出。它可以作为一个 类型索引,该索引引用一个合适的 函数类型,或者作为一个可选的 值类型 内联,这是一种函数类型 的简写。

每个结构化控制指令都引入一个隐式的标签。标签是分支指令的目标,这些分支指令使用 标签索引 引用它们。与其他 索引空间 不同,标签的索引是按嵌套深度相对的,也就是说,标签 指的是包围引用分支指令的最内层结构化控制指令,而增加的索引指的是更外层的那些指令。因此,标签只能从内部关联的结构化控制指令中引用。这也意味着分支只能被定向到外部,“中断”它们所针对的控制结构的块。确切的效果取决于该控制结构。在 的情况下,它是一个向前跳转,在匹配的 之后恢复执行。在 的情况下,它是一个向后跳转到循环的开头。

注意

这强制执行结构化控制流。直观地,一个针对 的分支的行为类似于大多数类似 C 的语言中的 语句,而一个针对 的分支的行为类似于 语句。

分支指令有几种形式: 执行无条件分支, 执行条件分支, 通过操作数索引到标签向量执行间接分支,该标签向量是指令的立即数,或者如果操作数越界,则索引到默认目标。 指令是针对最外层块的无条件分支的快捷方式,该块隐式地是当前函数的主体。执行分支会展开操作数栈,直到到达目标结构化控制指令被输入的高度。但是,分支本身可能会额外消耗操作数,它们会在展开后将操作数压回操作数栈。向前分支需要根据目标块类型的输出来提供操作数,即代表已终止块所产生的值。向后分支需要根据目标块类型的输入来提供操作数,即代表已重新启动块所消耗的值。

指令调用另一个 函数,从栈中消耗必要的参数,并返回调用的结果值。 指令通过操作数索引到一个 表格 间接调用函数,该表格由一个 表格索引 表示,并且必须具有类型 。由于它可能包含不同类型的函数,因此被调用者会动态地与指令第二个立即数所索引的 函数类型 进行检查,如果它们不匹配,则调用会被中止并产生一个 陷阱

2.4.9. 表达式

函数 主体、全局变量 的初始化值、元素 段的元素和偏移量以及 数据 段的偏移量都以表达式给出,表达式是 指令 的序列,以一个 标记终止。

在某些情况下,验证限制表达式为常量,这限制了允许指令集。

2.5. 模块

WebAssembly 程序被组织成模块,它们是部署、加载和编译的单元。模块收集类型函数内存全局变量的定义。此外,它可以声明导入导出,并以数据元素段的形式提供初始化,或一个开始函数

每个向量(以及整个模块)都可能是空的。

2.5.1. 索引

定义使用从零开始的索引进行引用。每类定义都有自己的索引空间,由以下类别区分。

函数、表格、内存和全局变量的索引空间包括同一模块中声明的相应导入。这些导入的索引位于同一索引空间中其他定义的索引之前。

元素索引引用元素段,数据索引引用数据段。

局部变量的索引空间仅在函数内部可访问,并且包括该函数的参数,参数位于局部变量之前。

标签索引引用指令序列内的结构化控制指令。

2.5.1.1. 约定
  • 元变量 指代标签索引。

  • 元变量 指代任何其他索引空间中的索引。

  • 符号 表示在 索引空间中自由出现的索引集。有时该集合被重新解释为其元素的向量

注意

例如,如果 , 那么 , 或等效地,向量 .

2.5.2. 类型

模块的 组件定义了一个函数类型的向量。

模块中使用的所有函数类型都必须在此组件中定义。它们由类型索引引用。

注意

WebAssembly 的未来版本可能会添加其他形式的类型定义。

2.5.3. 函数

模块的 组件定义了一个具有以下结构的函数向量

函数的 通过引用模块中定义的 类型 来声明其签名。 函数的参数通过函数体中以 0 为基的 局部索引 来引用;它们是可变的。

The 声明一个可变局部变量及其类型的向量。 这些变量通过函数体中的 局部索引 来引用。 第一个局部的索引是未引用参数的最小索引。

The 是一个 指令 序列,该序列在终止时必须生成与函数类型的 结果类型 匹配的堆栈。

Functions are referenced through function indices, starting with the smallest index not referencing a function import.

2.5.4.

模块的 组件定义了一个由其 表类型 描述的表向量。

表是特定 引用类型 的不透明值的向量。 表类型中的 大小指定了该表的初始大小,而其 (如果存在)限制了其以后可以增长的尺寸。

Tables can be initialized through element segments.

Tables are referenced through table indices, starting with the smallest index not referencing a table import. Most constructs implicitly reference table index .

2.5.5. 内存

模块的 组件定义了一个由其 内存类型 描述的线性内存(或简称内存)向量。

内存是原始未解释字节的向量。 内存类型中的 大小指定了该内存的初始大小,而其 (如果存在)限制了其以后可以增长的尺寸。 两者都以 页面大小 为单位。

Memories can be initialized through data segments.

Memories are referenced through memory indices, starting with the smallest index not referencing a memory import. Most constructs implicitly reference memory index .

注意

在 WebAssembly 的当前版本中,单个模块中最多可以定义或导入一个内存,并且所有结构都隐式地引用了这个内存 。 此限制可能会在将来的版本中解除。

2.5.6. 全局变量

模块的 组件定义了一个全局变量(或简称全局变量)向量。

每个全局变量存储给定全局类型的单个值。其还指定全局变量是不可变还是可变的。此外,每个全局变量都使用值初始化,该值由常量初始化器表达式给出。

全局变量通过全局索引引用,从不引用全局导入的最小索引开始。

2.5.7. 元素段

表的初始内容未初始化。元素段可用于从静态向量元素中初始化表的子范围。

模块的组件定义元素段向量。每个元素段定义一个引用类型以及相应的常量元素表达式列表。

元素段具有一个模式,用于将它们标识为被动主动声明式。被动元素段的元素可以使用指令复制到表中。主动元素段在实例化期间将元素复制到表中,如表索引和定义表中偏移量的常量表达式指定。声明式元素段在运行时不可用,但仅用于向前声明在代码中使用等指令形成的引用。

常量表达式给出。

元素段通过元素索引引用。

2.5.8. 数据段

内存的初始内容为零字节。数据段可用于从静态向量字节中初始化内存范围。

模块的组件定义数据段向量。

与元素段类似,数据段具有一个模式,用于将它们标识为被动主动。被动数据段的内容可以使用指令复制到内存中。主动数据段在实例化期间将内容复制到内存中,如内存索引和定义内存中偏移量的常量表达式指定。

数据段通过 数据索引 引用。

注意

在当前版本的 WebAssembly 中,模块中最多允许一个内存。因此,唯一有效的

2.5.9. 开始函数

模块的 组件声明一个 开始函数函数索引,该函数在模块被 实例化 时自动调用,在 表格内存 初始化之后。

注意

开始函数用于初始化模块的状态。在此初始化完成之前,模块及其导出无法从外部访问。

2.5.10. 导出

模块的 组件定义了一组 导出,这些导出在模块被 实例化 后对主机环境可用。

每个导出都由一个唯一的 名称 标记。可导出定义是 函数表格内存全局变量,它们通过相应的描述符引用。

2.5.10.1. 约定

以下辅助符号定义了导出序列,以按顺序保留的方式筛选出特定类型的索引

2.5.11. 导入

模块的组件定义了一组用于实例化导入

每个导入都由一个两级名称空间标记,包括一个名称和一个,用于该模块中的一个实体。可导入的定义是函数内存全局变量。每个导入都由一个描述符指定,该描述符具有一个相应的类型,实例化期间提供的定义需要与该类型匹配。

每个导入都在相应的索引空间中定义一个索引。在每个索引空间中,导入的索引都位于模块本身包含的任何定义的第一个索引之前。

注意

与导出名称不同,导入名称不一定是唯一的。可以多次导入相同的 / 对;这些导入甚至可能具有不同的类型描述,包括不同类型的实体。具有此类导入的模块仍然可以实例化,具体取决于 嵌入器 如何允许解析和提供导入。但是,嵌入器不需要支持这种重载,并且 WebAssembly 模块本身不能实现重载的名称。

3. 验证

3.1. 约定

验证检查 WebAssembly 模块是否格式正确。只有有效的模块才能 实例化

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

  1. 散文形式描述直观的含义。

  2. 正式符号形式描述规则,以数学形式表示。 [1]

注意

散文和正式规则是等效的,因此不需要理解正式符号就可以阅读本规范。形式主义提供了一种更简洁的描述,它使用的是编程语言语义中广泛使用的符号,并且很容易进行数学证明。

在这两种情况下,规则都是以声明式方式制定的。也就是说,它们只制定约束,并不定义算法。在 附录 中提供了根据本规范对指令序列进行类型检查的健全且完整的算法的框架。

3.1.1. 上下文

单个定义的有效性是相对于上下文指定的,上下文收集了有关周围 模块 和作用域内的定义的相关信息

  • 类型:当前模块中定义的类型列表。

  • 函数:当前模块中声明的函数列表,用它们的函数类型表示。

  • :当前模块中声明的表列表,用它们的表类型表示。

  • 内存:当前模块中声明的内存列表,用它们的内存类型表示。

  • 全局变量:当前模块中声明的全局变量列表,用它们的全局变量类型表示。

  • 元素段:当前模块中声明的元素段列表,用它们的元素类型表示。

  • 数据段:当前模块中声明的数据段列表,每个数据段都用 条目表示。

  • 局部变量:当前函数(包括参数)中声明的局部变量列表,用它们的值类型表示。

  • 标签:从当前位置可以访问的标签堆栈,用它们的返回值类型表示。

  • 返回值:当前函数的返回值类型,表示为可选的返回值类型,当不允许返回值时(如在独立表达式中)不存在。

  • 引用:模块中函数外部出现的 函数索引 列表,因此可以在函数内部使用它们来形成引用。

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

更具体地说,上下文被定义为 记录 ,其抽象语法为

除了以 形式编写的字段访问之外,以下符号用于操作上下文。

  • 当拼写出上下文时,空字段会被省略。

  • 表示与 相同的上下文,但元素 被预先添加到其 组件序列中。

注意

索引符号,如 用于在上下文中各自的 索引空间 中查找索引。上下文扩展符号 主要用于局部扩展 *相对* 索引空间,如 标签索引。因此,符号被定义为附加到相应序列的 *前面*,引入一个新的相对索引 并移动现有的索引。

3.1.2. 散文符号

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

  • 如果且仅当某个词组 满足了相应规则所表达的所有约束,那么该词组就被称为“在类型 下有效”。 的形式取决于 是什么。

    注意

    例如,如果 是一个 函数,那么 是一个 函数类型;对于一个 是一个 全局变量 是一个 全局变量类型;等等。

  • 这些规则隐含地假设了给定的 上下文

  • 在某些地方,该上下文被局部扩展为上下文 ,其中包含了额外的条目。 “在上下文 下,… 语句 …” 这种表述用于表达以下语句必须在扩展上下文所蕴含的假设下适用。

3.1.3. 形式符号

注意

本节简要介绍了用于正式指定类型规则的符号。 对于感兴趣的读者,可以在相应的教科书中找到更全面的介绍。 [2]

一个词组 具有相应的类型 的命题写成 。 然而,一般来说,类型取决于上下文 。 为了明确表达这一点,完整形式是 判断 ,它表明 中编码的假设下成立。

形式类型规则采用了一种标准方法来指定类型系统,将其呈现为 推导规则。 每个规则都有以下一般形式

这种规则被解读为一个大的蕴涵:如果所有前提都成立,那么结论就成立。 有些规则没有前提;它们是结论无条件成立的 公理。 结论始终是一个判断 ,并且每个相关的抽象语法结构 都对应一个规则。

注意

例如, 指令的类型规则可以作为一个公理给出

该指令始终在类型 下有效(表明它消耗两个 值并生成一个),与任何辅助条件无关。

这样的指令可以这样类型化

这里,前提要求立即的 局部索引 存在于上下文中。指令会产生一个与其类型 相对应的值(并且不会消耗任何值)。如果 不存在,则前提不成立,指令类型错误。

最后,一个 结构化 指令需要一个递归规则,其中前提本身就是一个类型判断。

一个 指令只有在它主体中的指令序列有效时才有效。此外,结果类型必须与块的注释 匹配。如果是这样,则 指令具有与主体相同的类型。在主体内部,有一个额外的对应结果类型的标签可用,这可以通过将上下文 扩展到前提中包含额外的标签信息来表达。

3.2. 类型

大多数 类型 都是普遍有效的。但是,对 限制 的限制适用,这些限制必须在验证期间进行检查。此外, 块类型 被转换为普通 函数类型 以便于处理。

3.2.1. 限制

限制 必须具有有意义的边界,这些边界必须在给定范围内。

3.2.1.1.
  • 的值不能大于 .

  • 如果最大值 非空,则

    • 它的值不能大于 .

    • 它的值不能小于 .

  • 那么这个限制在 范围内是有效的。

3.2.2. 块类型

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

3.2.2.1.
3.2.2.2.

3.2.3. 函数类型

函数类型 始终有效。

3.2.3.1.
  • 函数类型是有效的。

3.2.4. 表类型

3.2.4.1.
  • 限制 必须在范围 有效

  • 然后表类型有效。

3.2.5. 内存类型

3.2.5.1.
  • 限制 必须在范围 有效

  • 然后内存类型有效。

3.2.6. 全局类型

3.2.6.1.
  • 全局类型有效。

3.2.7. 外部类型

3.2.7.1.
3.2.7.2.
3.2.7.3.
  • 内存类型 必须 有效

  • 然后外部类型有效。

3.2.7.4.

3.2.8. 导入子类型

实例化 模块时,外部值 必须提供,其 类型 与每个导入所分类的相应 外部类型 相匹配。 在某些情况下,这允许使用简单的子类型形式(正式写为“”),如这里所定义。

3.2.8.1. 限制

限制 匹配限制 当且仅当

  • 大于或等于 .

  • 要么

    • 为空。

  • 或者

    • 两者 均不为空。

    • 小于或等于 .

3.2.8.2. 函数

一个 外部类型 匹配 当且仅当

3.2.8.3.

一个外部类型 匹配,当且仅当

3.2.8.4. 内存

一个外部类型 匹配,当且仅当

3.2.8.5. 全局变量

一个 外部类型 相匹配当且仅当

3.3. 指令

指令 通过堆栈类型 进行分类,这些分类描述了指令如何操作 操作数堆栈.

这些类型描述了指令弹出所需的输入栈,其中包含操作数类型 ,以及提供的输出栈,其中包含类型为 的结果值,它会将这些值压回栈中。栈类型类似于 函数类型,但它们允许将单个操作数分类为 (底部),表示该类型不受约束。作为辅助概念,操作数类型 与另一个操作数类型 匹配,如果 或等于 。这以逐点方式扩展到栈类型。

注意

例如,指令 的类型为 ,它消耗两个 值并产生一个值。

输入扩展到 指令序列 . 这样的序列具有 栈类型 ,如果执行指令的累积效果是从操作数栈中消耗类型为 的值,并将类型为 的新值压入栈。

对于某些指令,类型规则并不能完全约束类型,因此允许多种类型。这些指令被称为多态。可以区分两种程度的多态性

在这两种情况下,只要满足程序周围部分施加的约束,就可以任意选择不受约束的类型或类型序列。

注意

例如, 指令在类型 下有效,对于任何可能的 数字类型 。因此,这两个指令序列

是有效的,其中 的类型中分别实例化为 .

指令 在任何可能的 操作数类型 序列 下都是有效的,类型为 。因此,

假设指令 的类型为 是有效的。相反,

是无效的,因为没有可以为指令 选择的类型可以使序列类型正确。

附录中描述了一种类型检查 算法,它可以有效地实现这里给出的规则所规定的指令序列验证。

3.3.1. 数值指令

3.3.1.1.
  • 该指令的类型为 是有效的。

3.3.1.2.
  • 该指令的类型为 是有效的。

3.3.1.3.
  • 该指令的类型为 是有效的。

3.3.1.4.
  • 该指令的类型为 是有效的。

3.3.1.5.
  • 该指令在类型 下有效。

3.3.1.6.
  • 该指令在类型 下有效。

3.3.2. 引用指令

3.3.2.1.
  • 该指令的类型为 是有效的。

注意

在未来的 WebAssembly 版本中,可能存在不允许空引用的引用类型。

3.3.2.2.
3.3.2.3.
  • 函数 必须在上下文中定义。

  • 函数索引 必须包含在 中。

  • 该指令的类型有效为 .

3.3.3. 向量指令

向量指令可以带有前缀来描述操作数的 形状。打包数值类型,, 不是 值类型。辅助函数将这种打包类型形状映射到值类型

以下辅助函数表示向量形状的通道数,即其 *维度*

3.3.3.1.
  • 该指令的类型有效为 .

3.3.3.2.
3.3.3.3.
3.3.3.4.
3.3.3.5.
3.3.3.6.
3.3.3.7.
3.3.3.8.
3.3.3.9.
3.3.3.10.
3.3.3.11.
3.3.3.12.
3.3.3.13.
3.3.3.14.
3.3.3.15.
3.3.3.16.
3.3.3.17.
3.3.3.19.
3.3.3.20.
3.3.3.21.

3.3.4. 参数指令

3.3.4.1.

注意

都是没有注释的 值多态 指令。

3.3.4.2.
  • 如果 存在,那么

    • 的长度必须为

    • 然后该指令在类型 下有效。

  • 否则

注意

在 WebAssembly 的未来版本中, 可能允许每个选择有多个值。

3.3.5. 变量指令

3.3.5.1.
  • 局部变量 必须在上下文中定义。

  • 值类型 .

  • 那么该指令在类型为 时有效。

3.3.5.2.
  • 局部变量 必须在上下文中定义。

  • 值类型 .

  • 那么该指令在类型为 时有效。

3.3.5.3.
  • 局部变量 必须在上下文中定义。

  • 值类型 .

  • 那么该指令在类型为 时有效。

3.3.5.4.
  • 全局变量 必须在上下文环境中定义。

  • 为全局类型 global type .

  • 那么该指令在类型为 时有效。

3.3.5.5.
  • 全局变量 必须在上下文环境中定义。

  • 为全局类型 global type .

  • 可变性 必须为 .

  • 那么该指令在类型为 时有效。

3.3.6. 表格指令

3.3.6.1.
3.3.6.2.
3.3.6.3.
  • 表格 必须在上下文环境中定义。

  • 然后指令类型为 有效。

3.3.6.4.
3.3.6.5.
3.3.6.6.
3.3.6.7.
3.3.6.8.
  • 元素段 必须在上下文中定义。

  • 那么该指令在类型 下是有效的。

3.3.7. 内存指令

3.3.7.1.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 位宽 除以 .

  • 然后该指令的类型为 .

3.3.7.2.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 .

  • 然后该指令的类型为 .

3.3.7.3.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 位宽 除以 .

  • 然后该指令的类型为 .

3.3.7.4.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 .

  • 然后该指令的类型为 .

3.3.7.5.
  • 内存 必须在上下文中定义。

  • 对齐方式 不能大于 .

  • 然后指令类型有效 .

3.3.7.6.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 .

  • 然后指令类型有效 .

3.3.7.7.
  • 内存 必须在上下文中定义。

  • 对齐 不能大于 .

  • 然后指令类型有效 .

3.3.7.8.
3.3.7.9.
3.3.7.10.
  • 内存 必须在上下文中定义。

  • 然后指令类型为 有效。

3.3.7.11.
  • 内存 必须在上下文中定义。

  • 然后这条指令是有效的,类型为 .

3.3.7.12.
  • 内存 必须在上下文中定义。

  • 那么该指令在类型 下是有效的。

3.3.7.13.
  • 内存 必须在上下文中定义。

  • 那么该指令在类型 下是有效的。

3.3.7.14.
  • 内存 必须在上下文中定义。

  • 数据段 必须在上下文中定义。

  • 那么该指令在类型 下是有效的。

3.3.7.15.
  • 数据段 必须在上下文中定义。

  • 那么该指令在类型 下是有效的。

3.3.8. 控制指令

3.3.8.1.
  • 该指令类型有效 .

3.3.8.2.
  • 该指令类型有效 , 对于任何 操作数类型 序列 .

注意

指令是 栈多态的

3.3.8.3.

注意

符号 将新的标签类型插入到索引为 的位置,并向后移动所有其他标签类型。

3.3.8.4.
3.3.8.5.

注意

符号 将新的标签类型插入到索引为 的位置,并向后移动所有其他标签类型。

3.3.8.6.

注意

上下文标签索引空间中,最近的标签位于最前面,这样如预期那样执行相对查找。

指令堆栈多态的

3.3.8.7.

注意

上下文标签索引空间中,最近的标签位于最前面,这样如预期那样执行相对查找。

3.3.8.8.
  • 标签必须在上下文中定义。

  • 对于每个 标签 中,标签 必须在上下文中定义。

  • 必须存在一个 操作数类型 序列 ,使得

    • 序列 的长度与序列 的长度相同。

    • 对于每个 操作数类型 中,以及在 中对应的类型 匹配 .

    • 对于每个 标签

      • 序列 的长度与序列 的长度相同。

      • 对于每个 操作数类型 中,以及在 中对应的类型 匹配 .

  • 然后该指令在类型 下有效,对于任何 操作数类型 序列

注意

上下文 中的 标签索引 空间包含最新的标签,所以 执行一个相对查找,如预期。

指令是 堆栈多态 的。

3.3.8.9.
  • 返回类型 在上下文 中不能是缺失的。

  • 结果类型

  • 然后指令在类型 下有效,对于任何操作数类型序列.

注意

指令 栈多态 的。

在验证一个非函数体的 表达式 时,是缺失的(设置为 )。这与将其设置为空结果类型 () 不同,后者是针对不返回任何内容的函数的。

3.3.8.10.
  • 函数 必须在上下文中定义。

  • 然后指令在类型 下是有效的。

3.3.8.11.

3.3.9. 指令序列

指令序列的类型递归定义。

3.3.9.1. 空指令序列:
  • 空指令序列对于任何操作数类型序列 都是有效的,且类型为 .

3.3.9.2. 非空指令序列:
  • 指令序列 必须是有效的,且类型为 ,对于一些操作数类型序列 .

  • 指令 必须是有效的,类型为 , 对于某些 操作数类型 的序列。

  • 必须有一个 操作数类型 序列 , 使得 , 其中类型序列 的长度与 相同。

  • 对于每个 操作数类型 中,以及在 中对应的类型 , 匹配 .

  • 那么组合的指令序列将是有效的,类型为 .

3.3.10. 表达式

表达式 通过 结果类型 进行分类,形式为 .

3.3.10.1.
3.3.10.2. 常量表达式

注意

目前,出现在 globalselementdata 段的常量表达式受到进一步约束,因为其中包含的 指令只能引用*导入的*全局变量。这在 模块验证规则 中通过相应地约束上下文 来强制执行。

常量表达式的定义可能会在 WebAssembly 的未来版本中扩展。

3.4. 模块

模块 在它们包含的所有组件都有效时才有效。此外,大多数定义本身都用合适的类型进行分类。

3.4.1. 函数

函数 通过形式为 函数类型 进行分类。

3.4.1.1.
  • 类型 必须在上下文中定义。

  • 函数类型 .

  • 相同的 上下文,但

  • 在上下文 中,表达式 必须是有效的,类型为 .

  • 然后函数定义是有效的,类型为 .

3.4.2.

是按 表类型 分类。

3.4.2.1.

3.4.3. 内存

内存 是通过 内存类型 进行分类的。

3.4.3.1.

3.4.5. Element Segments

Element segments are classified by the reference type of their elements.

3.4.5.1.
3.4.5.2.
3.4.5.3.
3.4.5.4.

3.4.6. 数据段

数据段 不按任何类型分类,只检查其是否格式正确。

3.4.6.1.
  • 数据模式 必须有效。

  • 然后数据段就有效。

3.4.6.2.
  • 数据模式有效。

3.4.6.3.

3.4.7. 起始函数

起始函数声明 不按任何类型分类。

3.4.7.1.
  • 函数 必须在上下文中定义。

  • 的类型 必须是 .

  • 然后起始函数就有效。

3.4.8. 导出

导出 和导出描述 按其 外部类型 进行分类。

3.4.8.1.
3.4.8.2.
3.4.8.3.
3.4.8.4.
3.4.8.5.

3.4.9. 导入

导入 和导入描述 外部类型 分类。

3.4.9.1.
3.4.9.2.
  • 函数 必须在上下文中定义。

  • 函数类型 .

  • 那么,该导入描述是有效的,类型为 .

3.4.9.3.
3.4.9.4.
3.4.9.5.

3.4.10. 模块

模块通过其 外部类型导入导出 的映射来分类。

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

注意

模块中的大多数定义——特别是函数——是相互递归的。因此,此规则中 上下文 的定义是递归的:它依赖于模块中包含的函数、表格、内存和全局定义的验证结果,而这些验证结果本身又依赖于 。然而,这种递归仅仅是一种规范手段。构建 所需的所有类型都可以很容易地从模块上的一次简单的预处理中确定,该预处理不会执行任何实际的验证。

但是,全局变量不是递归的,并且在它们被局部定义时,不能在 常量表达式 中访问。定义有限上下文 用于验证某些定义的效果是,它们只能访问函数和导入的全局变量,而不能访问其他任何内容。

注意

WebAssembly 的未来版本可能会取消对内存数量的限制。

4. 执行

4.1. 约定

实例化 模块或 调用 结果模块 实例 上的 导出 函数时,WebAssembly 代码就会被执行

执行行为是在模拟程序状态抽象机的术语中定义的。它包含一个堆栈,用于记录操作数的值和控制结构,以及一个包含全局状态的抽象存储

对于每条指令,都有一个规则指定其执行对程序状态的影响。此外,还有一些规则描述了模块的实例化。与 验证 一样,所有规则都以两种等效形式给出

  1. 散文形式描述执行,以直观的方式。

  2. 形式化符号形式描述规则,以数学形式。 [1]

注意

与验证一样,散文和形式化规则是等效的,因此不需要理解形式化符号来阅读本规范。形式化符号以编程语言语义中广泛使用的符号提供了更简洁的描述,并且易于进行数学证明。

4.1.1. 散文符号

执行由针对 指令 的每种 抽象语法 的风格化、逐步规则来指定。在陈述这些规则时,采用了以下约定。

  • 执行规则隐含地假设给定了一个 存储 .

  • 执行规则还假设存在一个隐式堆栈,它通过压入弹出标签来修改。

  • 某些规则要求堆栈至少包含一个帧。最近的帧被称为当前帧。

  • 存储和当前帧都通过替换其某些组件来进行修改。这种替换被假定为全局适用。

  • 指令的执行可能陷入,在这种情况下,整个计算将被中止,并且它不会对存储进行进一步的修改。(之后仍然可以启动其他计算。)

  • 指令的执行也可能以跳转到指定的目标结束,该目标定义了要执行的下一条指令。

  • 执行可以进入退出指令序列,这些序列形成了

  • 指令序列隐式地按顺序执行,除非发生陷阱或跳转。

  • 在各个地方,规则包含断言,表达了关于程序状态的关键不变式。

4.1.2. 形式符号

注意

本节简要解释了正式指定执行的符号。对于感兴趣的读者,可以在相应的教科书中找到更深入的介绍。 [2]

正式执行规则使用一种标准方法来指定操作语义,将它们呈现为规约规则。每个规则都有以下一般形式

配置是对程序状态的句法描述。每个规则指定执行的一步。只要最多有一个规约规则适用于给定的配置,规约(从而执行)就是确定性的。WebAssembly 只有极少数例外,这些例外在本规范中明确指出。

对于 WebAssembly,配置通常是一个元组,它由当前存储 、当前函数的调用帧 以及要执行的指令 序列组成。(更精确的定义稍后给出。)

为了避免不必要的混乱,存储 和帧 从不涉及它们的规约规则中省略。

没有堆栈 的独立表示。相反,它很方便地表示为配置的指令序列的一部分。特别是, 被定义为与 指令一致,并且 指令的序列可以解释为一个向右增长的操作数“堆栈”。

注意

例如,规约规则 用于 指令可以给出如下:

根据此规则,两个 指令以及 指令本身从指令流中删除,并用一个新的 指令替换。这可以解释为从堆栈中弹出两个值并将结果压入。

当没有产生结果时,指令简化为空序列

标签 类似地定义 为指令序列的一部分。

规约的顺序由适当的求值上下文 的定义决定。

当没有更多规约规则适用时,规约终止。WebAssembly 类型系统健壮性 保证这仅在原始指令序列已简化为 指令序列(可以解释为结果操作数堆栈的)或发生陷阱 时才会发生。

注意

例如,以下指令序列:

在三个步骤后终止

其中 以及 .

4.2. 运行时结构

存储器以及其他构成 WebAssembly 抽象机的运行时结构,例如模块实例,在额外的辅助语法中被精确定义。

4.2.1.

WebAssembly 计算操作四种基本数值类型的值,即整数浮点数(分别为 32 位和 64 位),或向量(128 位),或引用类型

在语义的大部分地方,不同类型的值可能出现。为了避免歧义,因此使用抽象语法表示值,该语法明确了值的类型。方便起见,可以使用与 指令 相同的符号来表示它们。

非空引用由附加的管理指令表示。它们要么是函数引用,指向特定的函数地址,要么是外部引用,指向外部地址的未解释形式,可由嵌入器定义以表示其自己的对象。

注意

WebAssembly 的未来版本可能会添加其他形式的引用。

每个 值类型 都有一个关联的默认值;对于 数值类型,它分别是 ;对于 向量类型,它是 ;对于 引用类型,它是 null。

4.2.1.1. 约定
  • 元变量 在上下文中清晰的情况下,表示引用值。

4.2.2. 结果

结果是计算的结果。它要么是一系列,要么是一个陷阱

4.2.3. 存储

存储表示 WebAssembly 程序可以操作的所有全局状态。它包含所有函数表格内存全局变量元素段以及数据段的运行时表示,这些实例在抽象机器的生命周期中已被分配[1]

语义的一个不变式是,没有任何元素或数据实例从除拥有模块实例以外的任何地方被寻址

从语法上讲,存储被定义为一个记录,列出每种类型的现有实例。

4.2.3.1. 约定
  • 元变量在上下文中明确时,表示存储。

4.2.4. 地址

函数实例表格实例内存实例全局变量实例元素实例以及数据实例存储中用抽象的地址引用。这些地址只是对各自存储组件的索引。此外,嵌入器可以提供一组未解释的主机地址

一个 嵌入器 可以为与它们的地址相对应的 导出 的存储对象分配标识,即使此标识在 WebAssembly 代码本身内部不可观察(例如,对于 函数实例 或不可变的 全局变量)。

注意

地址是动态的,全局唯一的对运行时对象的引用,与 索引 形成对比,后者是静态的,对它们原始定义的模块本地引用。内存地址 表示存储中内存实例的抽象地址,而不是内存实例内部的偏移量。

存储对象分配数量没有具体限制,因此逻辑地址可以是任意大的自然数。

4.2.5. 模块实例

模块实例模块 的运行时表示。它是通过 实例化 模块创建的,它收集了由模块导入、定义或导出的所有实体的运行时表示。

每个组件引用其原始模块中相应声明(无论是导入还是定义)的运行时实例,按照其静态索引的顺序。 函数实例表实例内存实例全局实例通过它们各自在存储区中的地址进行间接引用。

语义的不变性要求给定模块实例中的所有导出实例具有不同的名称

4.2.6. 函数实例

函数实例函数的运行时表示。它实际上是对其原始函数的运行时模块实例(来自其原始模块)的闭包。模块实例用于在执行函数期间解析对其他定义的引用。

宿主函数是在 WebAssembly 外部表达的函数,但作为导入传递给模块。宿主函数的定义和行为超出了本规范的范围。为了本规范的目的,假设当调用时,宿主函数的行为是不确定的,但在某些约束内,以确保运行时的完整性。

注意

函数实例是不可变的,并且它们的标识不能被 WebAssembly 代码观察到。但是,嵌入器可能会提供隐式或显式的方法来区分它们的地址

4.2.7. 表实例

表实例的运行时表示。它记录了它的类型,并包含一个引用值向量。

表元素可以通过表指令、活动元素段的执行或由嵌入器提供的外部手段进行修改。

语义的不变性要求所有表元素的类型都等于的元素类型。它也是一个不变性,即元素向量的长度永远不会超过的最大大小(如果存在)。

4.2.8. 内存实例

内存实例是线性内存的运行时表示。它记录了它的类型,并包含一个字节向量。

向量长度始终是 WebAssembly *页面大小* 的倍数,页面大小定义为常数 ,简写为 .

字节可以通过 内存指令、活动 数据段 的执行或由 嵌入器 提供的外部手段进行修改。

语义的不变性在于,字节向量长度除以页面大小,永远不会超过 的最大大小(如果存在)。

4.2.9. 全局实例

全局实例全局 变量的运行时表示。它记录了其 类型 并保存一个单独的

可变全局变量的值可以通过 变量指令 或由 嵌入器 提供的外部手段进行修改。

语义的不变性在于,该值具有与 值类型 相同的类型。

4.2.10. 元素实例

元素实例元素段 的运行时表示。它保存引用向量及其共同的 类型

4.2.11. 数据实例

数据实例数据段 的运行时表示。它保存一个 字节 向量。

4.2.12. 导出实例

导出实例导出 的运行时表示。它定义了导出的 名称 和关联的 外部值

4.2.13. 外部值

外部值 是可以导入或导出的实体的运行时表示。它是一个 地址,表示共享 存储 中的 函数实例表格实例内存实例全局实例

4.2.13.1. 约定

以下定义了用于外部值序列的辅助符号。它以保留顺序的方式过滤掉特定类型的条目

4.2.14. 堆栈

除了存储之外,大多数指令与隐式堆栈进行交互。堆栈包含三种类型的条目

  • :指令的操作数

  • 标签:活动结构化控制指令,分支可以将其作为目标。

  • 激活:活动函数调用的调用帧

在程序执行期间,这些条目可以以任何顺序出现在堆栈中。堆栈条目由以下抽象语法描述。

注意

可以使用单独的操作数、控制结构和调用堆栈来对 WebAssembly 语义进行建模。但是,由于堆栈是相互依赖的,因此需要有关关联堆栈高度的额外簿记。为了便于本规范的目的,交错表示更简单。

4.2.14.1.

值由自身表示。

4.2.14.2. 标签

标签带有参数元数,以及它们关联的分支目标,在语法上表示为指令序列

直观地说,是分支被执行时要执行的延续,它将替换原始控制结构。

注意

例如,循环标签的形式为

当执行到该标签的分支时,会执行该循环,实际上是从头开始重新执行循环。相反,一个简单的块标签的形式为

当分支时,空延续会结束目标块,使得执行可以继续执行后续指令。

4.2.14.3. 激活帧

激活帧承载着相应函数的返回值元数,保存其局部变量(包括参数)的值,顺序对应于它们的静态局部变量索引,以及对该函数自身模块实例的引用。

局部变量的值由各自的变量指令修改。

4.2.14.4. 约定
  • 元变量表示标签,从上下文中可以清楚地识别。

  • 元变量表示帧状态,从上下文中可以清楚地识别。

  • 以下辅助定义接受一个块类型,并在当前帧中查找它所表示的函数类型

4.2.15. 管理指令

注意

本节仅与正式符号相关。

为了表达陷阱调用控制指令的规约,指令的语法扩展到包含以下管理指令

指令表示发生陷阱。陷阱会通过嵌套的指令序列冒泡,最终将整个程序规约为单个指令,表示程序突然终止。

指令表示函数引用值。类似地,表示外部引用.

指令表示即将调用一个函数实例,由其地址标识。它统一了不同形式的调用处理。

指令中,标签 “在堆栈上” 建模。此外,管理语法维护了原始 结构化控制指令函数体 及其 指令序列 的嵌套结构,并使用 标记。这样,当内部指令序列是外部序列的一部分时,便可以知道内部指令序列的结束位置。

注意

例如,规约规则

这将块替换为一个标签指令,可以解释为将标签“压入”堆栈。当遇到 时,即内部指令序列已规约为空序列 - 或者更确切地说,是一系列 指令,代表生成的数值 - 然后 指令将根据其自身的 规约规则 被消除。

这可以解释为从堆栈中移除标签,只保留本地累积的操作数。

4.2.15.1. 块上下文

为了指定 分支 的规约,定义了以下 块上下文 语法,以包围 空洞 的标签数量 进行索引,该空洞标记着计算的下一步将要进行的位置。

此定义允许索引围绕 分支返回 指令的活动标签。

注意

例如,简单分支的 规约 可以定义如下:

在此,上下文中的空洞 由分支指令实例化。当发生分支时,此规则将用标签的延续替换目标标签和关联的指令序列。选定的标签通过 标签索引 进行识别,该索引对应于必须跳过的周围 指令的数量 - 这恰好是块上下文索引中编码的计数。

4.2.15.2. 配置

配置由当前的 存储 和正在执行的 线程 组成。

线程是在 指令 上的计算,它相对于当前 的状态进行操作,该帧引用了计算运行的 模块实例,即当前函数源自的位置。

注意

当前版本的 WebAssembly 是单线程的,但在未来可能会支持多线程的配置。

4.2.15.3. 评估上下文

最后,评估上下文 的以下定义和相关的结构规则允许在指令序列和管理形式内部进行简化,以及陷阱的传播

当线程的指令序列简化为 结果 时,简化将终止,即为 序列或为 .

注意

对评估上下文的限制排除了像 这样的上下文,对于这些上下文, .

以下指令序列是评估上下文下简化示例。

可以将其分解为 ,其中

此外,这是唯一可能的评估上下文,其中孔的内容与缩减规则的左侧匹配。

4.3. 数值

数值基元以通用方式定义,通过按位宽索引的运算符 进行定义。

一些运算符是非确定性的,因为它们可以返回几种可能结果之一(例如不同的 NaN 值)。从技术上讲,每个运算符因此返回一组允许的值。为了方便起见,确定性结果表示为普通值,这些值被假定为与相应的单元素集一致。

一些运算符是偏函数的,因为它们在某些输入上没有定义。从技术上讲,对于这些输入,返回一个空的结果集。

在形式化表示中,每个运算符都由等式子句定义,这些子句按优先级降序应用。也就是说,第一个适用于给定参数的子句定义了结果。在某些情况下,类似的子句使用 符号组合成一个。当多个这样的占位符出现在单个子句中时,它们必须一致地解析:要么为所有占位符选择上符号,要么选择下符号。

注意

例如, 运算符定义如下

此定义应被视为每个子句扩展为两个独立子句的简写形式。

数值运算符通过对输入序列逐元素应用运算符来提升到输入序列,返回结果序列。当有多个输入时,它们必须具有相同的长度。

注意

例如,一元运算符 ,当给定一个浮点数序列时,会返回一个浮点数结果序列。

二元运算符 ,当给定两个长度相同的整数序列 时,会返回一个整数结果序列。

约定

  • 元变量 用于遍历单个位。

  • 元变量 用于遍历浮点数的(无符号)幅度,包括

  • 元变量 用于遍历(无符号)有理数幅度,不包括

  • 符号 表示双射函数 的逆。

  • 有理数的截断写成 ,具有通常的数学定义。

  • 整数的饱和写成 。这两个函数的参数范围是任意有符号整数。

    • 无符号饱和, 限制在 之间。

    • 有符号饱和, 限制在 之间。

4.3.1. 表示

数字和数值向量具有作为比特序列的底层二进制表示。

这些函数中的每一个都是双射,因此它们是可逆的。

4.3.1.1. 整数

整数 被表示为二进制无符号数。

布尔运算符,如 ,通过逐位应用于长度相等的比特序列来提升。

4.3.1.2. 浮点数

浮点值[IEEE-754-2019] (第 3.4 节) 定义的相应二进制格式中表示。

其中 .

4.3.1.3. 向量

类型为 的数值向量具有与 相同的底层表示。它们也可以解释为打包到 中的数值序列,具有特定的 ,前提是 .

此函数是关于的双射,因此它是可逆的。

4.3.1.4. 存储

当一个数字存储到内存中时,它将被转换为小端序的字节序列。

这些函数同样是可逆的双射。

4.3.2. 整数运算

4.3.2.1. 符号解释

整数运算符定义在值上。使用有符号解释的运算符使用以下定义来转换值,当值位于值范围的上半部分时(即其最高有效位为),它采用二进制补码。

此函数是双射的,因此是可逆的。

4.3.2.2. 布尔解释

谓词的整数结果 - 即 测试关系 运算符 - 使用以下辅助函数定义,该函数根据条件生成值

4.3.2.3.
  • 返回 的结果。

4.3.2.4.
  • 返回从 中减去 的结果。

4.3.2.5.
  • 返回 的乘积的结果。

4.3.2.6.
  • 如果 , 则结果未定义。

  • 否则,返回将 除以 的结果,向零取整。

注意

此运算符是 部分 的。

4.3.2.7.
  • 有符号解释

  • 有符号解释

  • 如果 , 则结果未定义。

  • 否则,如果 除以 的结果为 , 则结果未定义。

  • 否则,返回将 除以 的结果,向零取整。

注意

此运算符是 部分 的。除了被 除外,结果 不能表示为 位带符号整数。

4.3.2.8.
  • 如果 , 则结果未定义。

  • 否则,返回将 除以 的余数。

注意

此运算符是 部分 的。

只要两个操作数都已定义,则有 .

4.3.2.9.
  • 有符号解释

  • 有符号解释

  • 如果 , 则结果未定义。

  • 否则,返回将 除以 的余数,并保留被除数 的符号。

注意

此运算符是 部分 的。

只要两个操作数都被定义,则有 .

4.3.2.10.
  • 返回 的按位取反。

4.3.2.11.
  • 返回 的按位与。

4.3.2.12.
  • 返回 按位取反后的按位与。

4.3.2.13.
  • 返回 的按位或运算结果。

4.3.2.14.
  • 返回 的按位异或运算结果。

4.3.2.15.
  • 的结果。

  • 返回将 左移 位的结果,模 .

4.3.2.16.
  • 的结果。

  • 返回将 右移 位的结果,并用 位扩展。

4.3.2.17.
  • 的结果。

  • 返回将 右移 位的结果,并使用原始值的最高有效位进行扩展。

4.3.2.18.
  • 的结果。

  • 返回将 左移 位的结果。

4.3.2.19.
  • 的结果。

  • 返回将 右移 位的结果。

4.3.2.20.
  • 返回 中前导零位的数量;如果 ,则所有位都算作前导零位。

4.3.2.21.
  • 返回 中尾随零位的数量;如果 ,则所有位都算作尾随零位。

4.3.2.22.
  • 返回 中非零位的数量。

4.3.2.23.
  • 如果 为零,则返回 ,否则返回

4.3.2.24.
  • 如果 等于 ,则返回 ;否则返回

4.3.2.25.
  • 如果 不等于 ,则返回 ;否则返回

4.3.2.26.
  • 如果 小于 ,则返回 ;否则返回

4.3.2.27.
4.3.2.28.
  • 如果 大于 , 则返回 ,否则返回 .

4.3.2.29.
4.3.2.30.
  • 返回 如果 小于或等于 , 否则。

4.3.2.31.
4.3.2.32.
  • 如果 大于或等于 ,则返回 ;否则返回

4.3.2.33.
  • 有符号解释

  • 有符号解释

  • 如果 大于或等于 ,则返回 ;否则返回

4.3.2.34.
  • 为计算 的结果。

  • 返回 .

4.3.2.35.
  • 的按位与运算结果。

  • 的按位取反运算结果。

  • 的按位与运算结果。

  • 返回 的按位或运算的结果。

4.3.2.36.
  • 有符号解释

  • 如果 大于或等于 ,则返回

  • 否则返回 j 的负数,对 取模。

4.3.2.37.
  • 返回对 取反的结果,模 .

4.3.2.38.
  • 如果 ,则返回 ,否则返回 .

4.3.2.39.
  • 返回 如果 , 否则返回 .

4.3.2.40.
  • 返回 如果 , 返回 否则。

4.3.2.41.
  • 如果 , 则返回 , 否则返回 .

4.3.2.42.
  • 相加的结果。

  • 返回 .

4.3.2.43.
  • 的有符号解释。

  • 的有符号解释。

  • 相加的结果。

  • 返回 .

4.3.2.44.
  • 为从 中减去 的结果。

  • 返回 .

4.3.2.45.
  • 的有符号解释。

  • 的有符号解释。

  • 为从 中减去 的结果。

  • 返回 .

4.3.2.46.
  • , , 和 相加的结果。

  • 返回 除以 的结果,向零取整。

4.3.2.47.
  • 返回 的结果。

4.3.3. 浮点运算

浮点运算遵循 [IEEE-754-2019] 标准,并具有以下限定条件

  • 所有运算符使用舍入到最接近的偶数,除非另有说明。不支持非默认定向舍入属性。

  • 遵循从操作数传播 NaN 负载的建议是允许的,但不是必需的。

  • 所有运算符使用“不停”模式,并且浮点异常不会以其他方式被观察到。特别是,不支持备用浮点异常处理属性或状态标志上的运算符。安静的 NaN 和信号 NaN 之间没有可观察到的区别。

注意

WebAssembly 的未来版本可能会解除其中一些限制。

4.3.3.1. 舍入

舍入始终是舍入到最接近的偶数,与 [IEEE-754-2019](第 4.3.1 节)一致。

精确浮点数是指可以被精确表示为给定位宽 浮点数 的有理数。

给定浮点位宽 限制数是指其大小为不能被精确表示为位宽为 的浮点数的 的最小幂的正数或负数(该大小为 对于 对于 )。

候选数是指给定位宽 的精确浮点数或正数或负数限制数。

候选对是指候选数对 ,其中不存在位于两者之间的候选数。

实数 转换为位宽为 的浮点值如下

  • 如果 等于 ,则返回 .

  • 否则,如果 是一个精确的浮点数,则返回 .

  • 否则,如果 大于或等于正极限,则返回 .

  • 否则,如果 小于或等于负极限,则返回 .

  • 否则,如果 是一个候选对,使得 ,则

    • 如果 ,则设 .

    • 否则,如果 ,则设 .

    • 否则,如果 尾数 是偶数,则设 .

    • 否则,设 .

  • 如果 等于 ,则

    • 如果 ,则返回 .

    • 否则,返回 .

  • 否则如果 是一个极限数,那么

    • 如果 ,则返回 .

    • 否则,返回 .

  • 否则,返回 .

其中

4.3.3.2. NaN 传播

当浮点运算符(除 , , 或 之外)的结果为 NaN 时,其符号是不确定的,而 有效载荷 则按以下方式计算

  • 如果运算符的所有 NaN 输入的有效载荷都为 规范的(包括没有 NaN 输入的情况),则输出的有效载荷也为规范的。

  • 否则,有效载荷将在所有 算术 NaN 中非确定性地选择;也就是说,其最高有效位为 ,而其他所有位则未指定。

此非确定性结果由以下辅助函数表示,该函数从一组输入生成一组允许的输出

4.3.3.3.
  • 如果 是 NaN,则返回 中的一个元素。

  • 否则,如果 都是符号相反的无穷大,则返回 中的一个元素。

  • 否则,如果 都是符号相同的无穷大,则返回该无穷大。

  • 否则,如果 是无穷大,则返回该无穷大。

  • 否则,如果 都是符号相反的零,则返回正零。

  • 否则,如果 都是符号相同的零,则返回该零。

  • 否则,如果 是零,则返回另一个操作数。

  • 否则,如果 是大小相同但符号相反的值,则返回正零。

  • 否则,返回 相加的结果,舍入到最接近的可表示值。

4.3.3.4.
  • 如果 是 NaN,则返回 中的一个元素。

  • 否则,如果 都是符号相同的无穷大,则返回 .

  • 否则,如果 都是符号相反的无穷大,则返回 .

  • 否则,如果 是无穷大,则返回该无穷大。

  • 否则,如果 是无穷大,则返回该无穷大的负数。

  • 否则,如果 都是符号相同的零,则返回正零。

  • 否则,如果 都是符号相反的零,则返回 .

  • 否则,如果 是零,则返回 .

  • 否则,如果 是零,则返回 的负数。

  • 否则,如果 相同,则返回正零。

  • 否则,返回从 中减去 的结果,四舍五入 到最接近的可表示值。

注意

除了关于 NaN 的非确定性之外,始终满足以下条件:.

4.3.3.5.
  • 如果 是 NaN,则返回 中的一个元素。

  • 否则,如果 之一是零,另一个是无穷大,则返回 的一个元素。

  • 否则,如果 都是符号相同的无穷大,则返回正无穷大。

  • 否则,如果 都是符号相反的无穷大,则返回负无穷大。

  • 否则,如果 是无穷大,而另一个是符号相同的数值,则返回正无穷大。

  • 否则,如果 是无穷大,而另一个是符号相反的数值,则返回负无穷大。

  • 否则,如果 都是符号相同的零,则返回正零。

  • 否则,如果 都是符号相反的零,则返回负零。

  • 否则,返回 相乘的结果,四舍五入 到最近的可表示的值。

4.3.3.6.
  • 如果 是 NaN,则返回 中的一个元素。

  • 否则,如果 都是无穷大,则返回 中的一个元素。

  • 否则,如果 都是零,则返回 .

  • 否则,如果 是无穷大,而 是一个符号相同的数值,则返回正无穷大。

  • 否则,如果 是无穷大,而 是一个符号相反的数值,则返回负无穷大。

  • 否则,如果 是无穷大,而 是一个符号相同的数值,则返回正零。

  • 否则,如果 是无穷大,而 是一个符号相反的数值,则返回负零。

  • 否则,如果 是零,而 是一个符号相同的数值,则返回正零。

  • 否则,如果 是零,而 是一个符号相反的数值,则返回负零。

  • 如果 为零且 为一个带有等号的值,则返回正无穷大。

  • 如果 为零且 为一个带有相反符号的值,则返回负无穷大。

  • 否则返回 除以 的结果,四舍五入 到最接近的可表示值。

4.3.3.7.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 为负无穷大,则返回负无穷大。

  • 如果 为正无穷大,则返回另一个值。

  • 如果 都是带有相反符号的零,则返回负零。

  • 否则返回 中较小的值。

4.3.3.8.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 为正无穷大,则返回正无穷大。

  • 如果 是负无穷大,则返回另一个值。

  • 如果 都是符号相反的零,则返回正零。

  • 否则,返回 中较大的值。

4.3.3.9.
  • 如果 符号相同,则返回 .

  • 否则,返回符号取反后的 .

4.3.3.10.
  • 如果 是 NaN,则返回符号为正的 .

  • 如果 是无穷大,则返回正无穷大。

  • 如果 是零,则返回正零。

  • 如果 是正值,则 .

  • 否则,返回取反后的 .

4.3.3.11.
  • 如果 是 NaN,则返回符号取反后的

  • 否则,如果 是无穷大,则返回符号取反后的无穷大。

  • 否则,如果 是零,则返回符号取反后的零。

  • 否则,返回取反后的 .

4.3.3.12.
  • 如果 是 NaN,则返回 中的一个元素。

  • 否则,如果 是负无穷大,则返回 中的一个元素。

  • 如果 是正无穷大,则返回正无穷大。

  • 如果 是零,则返回该零。

  • 如果 有负号,则返回 中的元素。

  • 否则,返回 的平方根。

4.3.3.13.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 是无穷大,则返回 .

  • 如果 是零,则返回 .

  • 如果 小于 但大于 , 则返回负零。

  • 否则,返回不小于 的最小整数。

4.3.3.14.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 是无穷大,则返回 .

  • 如果 是零,则返回 .

  • 如果 大于 但小于 ,则返回正零。

  • 否则返回不大于 的最大整数值。

4.3.3.15.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 是无穷大,则返回 .

  • 如果 是零,则返回 .

  • 如果 大于 但小于 ,则返回正零。

  • 如果 小于 但大于 , 则返回负零。

  • 否则返回与 符号相同的整数,且该整数的绝对值是小于或等于 绝对值的最大整数。

4.3.3.16.
  • 如果 是 NaN,则返回 中的一个元素。

  • 如果 是无穷大,则返回 .

  • 如果 是零,则返回 .

  • 如果 大于 但小于或等于 , 则返回正零。

  • 如果 小于 但大于或等于 , 则返回负零。

  • 否则返回最接近 的整数值;如果两个值一样接近,则返回偶数。

4.3.3.18.
  • 如果 是 NaN,则返回 .

  • 否则如果 都是零,则返回 .

  • 否则如果 值相同,则返回 .

  • 否则返回 .

4.3.3.19.
  • 如果 是 NaN,则返回 .

  • 如果 值相同,则返回 .

  • 如果 是正无穷大,则返回 .

  • 如果 是负无穷大,则返回 .

  • 如果 是正无穷大,则返回 .

  • 如果 是负无穷大,则返回 .

  • 否则如果 都是零,则返回 .

  • 如果 小于 , 则返回 .

  • 否则返回 .

4.3.3.23.
  • 如果 小于 ,则返回 .

  • 否则返回 .

4.3.4. 转换

4.3.4.1.
  • 返回 .

注意

在抽象语法中,无符号扩展只是重新解释相同的值。

4.3.4.2.
  • 带符号解释,大小为 .

  • 返回 相对于大小 的二进制补码。

4.3.4.3.
  • 返回 .

4.3.4.4.
  • 如果 是 NaN,则结果未定义。

  • 否则,如果 是无穷大,则结果未定义。

  • 否则,如果 是一个数字,并且 是目标类型范围内的值,则返回该值。

  • 否则,结果未定义。

注意

该运算符是 部分 的。它没有为 NaN、无穷大或结果超出范围的值定义。

4.3.4.5.
  • 如果 是 NaN,则结果未定义。

  • 否则,如果 是无穷大,则结果未定义。

  • 如果 是一个数字,并且 是目标类型范围内的一个值,则返回该值。

  • 否则,结果未定义。

注意

该运算符是 部分 的。它没有为 NaN、无穷大或结果超出范围的值定义。

4.3.4.6.
  • 如果 是 NaN,则返回 .

  • 否则如果 是负无穷大,则返回 .

  • 否则如果 是正无穷大,则返回 .

  • 否则,返回 .

4.3.4.7.
  • 如果 是 NaN,则返回 .

  • 如果 是负无穷大,则返回 .

  • 如果 是正无穷大,则返回 .

  • 否则,返回 .

4.3.4.8.
  • 如果 是一个 规范的 NaN,则返回 的一个元素(即大小为 的规范的 NaN)。

  • 否则如果 是一个 NaN,则返回 的一个元素(即大小为 的任何 算术 NaN)。

  • 否则,返回 .

4.3.4.9.
  • 如果 是一个 规范的 NaN,则返回 的一个元素(即大小为 的规范的 NaN)。

  • 否则,如果 是一个 NaN,则返回 的一个元素(即任何大小为 的 NaN)。

  • 否则,如果 是一个无穷大,则返回该无穷大。

  • 如果 是零,则返回该零。

  • 否则,返回 .

4.3.4.10.
4.3.4.11.
4.3.4.12.
  • 为位序列 .

  • 返回常量 ,对于它而言,.

4.3.4.13.
4.3.4.14.

4.4. 指令

WebAssembly 计算是通过执行单个 指令 来完成的。

4.4.1. 数值指令

数值指令是根据通用 数值运算符 来定义的。数值指令与其底层运算符的映射由以下定义表示

对于转换运算符

当底层运算符为部分运算符时,当结果未定义时,相应的指令将陷入异常。当底层运算符为非确定性运算符时,因为它们可能返回多个可能的NaN 值,相应的指令也是非确定性的。

注意

例如,指令 作用于操作数 的结果调用了 ,它根据上述定义映射到泛型 。类似地, 作用于 调用了 ,它根据上述定义映射到泛型 .

4.4.1.1.
  1. 将值 推入堆栈。

注意

此指令不需要正式的规约规则,因为 指令本身就是

4.4.1.2.
  1. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  2. 从堆栈中弹出值

  3. 如果 有定义,那么

    1. 为计算 的可能结果。

    2. 将值 推入堆栈。

  4. 否则

    1. 陷入陷阱。

4.4.1.3.
  1. 断言:由于 验证,栈顶有两个 值类型 的值。

  2. 从栈中弹出值

  3. 从堆栈中弹出值

  4. 如果 已定义,则

    1. 为计算 的可能结果。

    2. 将值 推入堆栈。

  5. 否则

    1. 陷入陷阱。

4.4.1.4.
  1. 断言:由于 验证值类型 的一个值位于堆栈的顶部。

  2. 从堆栈中弹出值

  3. 为计算 的结果。

  4. 将值 推入堆栈。

4.4.1.5.
  1. 断言:由于 验证,两个 值类型 的值位于堆栈顶部。

  2. 从栈中弹出值

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 将值 推入堆栈。

4.4.1.6.
  1. 断言:由于 验证,一个 值类型 的值位于堆栈顶部。

  2. 从堆栈中弹出值 .

  3. 如果 被定义

    1. 是计算 的一个可能结果。

    2. 将值 推入堆栈。

  4. 否则

    1. 陷入陷阱。

4.4.2. 引用指令

4.4.2.1.
  1. 将值 推入堆栈。

注意

此指令不需要正式的约简规则,因为 指令本身就是

4.4.2.2.
  1. 断言:由于验证,一个引用值位于栈顶。

  2. 从栈中弹出值

  3. 如果,则

    1. 将值压入栈。

  4. 否则

    1. 将值压入栈。

4.4.2.3.
  1. 当前

  2. 断言:由于验证存在。

  3. 函数地址

  4. 将值压入栈。

4.4.3. 向量指令

按位操作的向量指令被视为相应宽度的整数运算。

大多数其他向量指令都是根据给定的 形状 按车道方式应用数值运算符来定义的。

注意

例如,指令 应用于操作数 的结果调用了 , 它映射到 , 其中 分别是调用 所产生的序列。

4.4.3.1.
  1. 将值 压入堆栈。

注意

该指令不需要正式的归约规则,因为 指令与 一致。

4.4.3.2.
  1. 断言:由于 验证,堆栈顶部存在一个 值类型 的值。

  2. 从堆栈中弹出值

  3. 为计算 的结果。

  4. 将值 压入堆栈。

4.4.3.3.
  1. 断言:由于 验证,堆栈顶部有两个 值类型 的值。

  2. 从堆栈中弹出值

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 将值 压入堆栈。

4.4.3.4.
  1. 断言:由于 验证,堆栈顶部有三个 值类型 的值。

  2. 从堆栈中弹出值

  3. 从堆栈中弹出值

  4. 从堆栈中弹出值

  5. 为计算 的结果。

  6. 将值 压入堆栈。

4.4.3.5.
  1. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  2. 从堆栈中弹出值

  3. 为计算 的结果。

  4. 将值 压入堆栈。

4.4.3.6.
  1. 断言:由于 验证,堆栈顶部有两个 值类型 的值。

  2. 从堆栈中弹出值

  3. 为计算 的结果。

  4. 从堆栈中弹出值

  5. 为计算 的结果。

  6. 为序列 的连接。

  7. 为计算 .

  8. 将值 推送到堆栈。

4.4.3.7.
  1. 断言: 由于 验证, 栈顶有两个 值类型 的值。

  2. 断言: 由于 验证, 对于 中的所有值,都满足以下条件: .

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 从堆栈中弹出值

  6. 为计算 的结果。

  7. 为两个序列 的连接。

  8. 为计算 .

  9. 将值 推入堆栈。

4.4.3.8.
  1. 为类型 .

  2. 断言:根据 验证,类型为 值类型 的值位于栈顶。

  3. 从堆栈中弹出值

  4. 为整数 .

  5. 为计算结果 .

  6. 将值 压入堆栈。

4.4.3.9.
  1. 断言:由于 验证.

  2. 断言:由于 验证,一个 值类型 的值位于堆栈顶端。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为类型 .

  6. 为计算 .

  7. 将值 推入堆栈。

4.4.3.10.
  1. 断言:由于 验证.

  2. 为类型 .

  3. 断言:由于 验证,一个类型为 的值位于栈顶。

  4. 从栈顶弹出值 .

  5. 断言:由于 验证,一个类型为 的值位于栈顶。

  6. 从堆栈中弹出值

  7. 为计算 的结果。

  8. 为计算 的结果。

  9. 推入栈中。

4.4.3.11.
  1. 断言:由于 验证,栈顶为一个 值类型 的值。

  2. 从堆栈中弹出值

  3. 为计算 的结果。

  4. 将值 压入堆栈。

4.4.3.12.
  1. 断言:由于 验证,堆栈顶部有两个 值类型 的值。

  2. 从堆栈中弹出值

  3. 从堆栈中弹出值

  4. 如果 被定义

    1. 是计算 的一个可能结果。

    2. 将值 压入堆栈。

  5. 否则

    1. 陷入陷阱。

4.4.3.13.
  1. 断言:由于 验证,栈顶有两个 值类型 的值。

  2. 从堆栈中弹出值

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为计算 的结果。

  6. 为计算 的结果。

  7. 为计算 的结果。

  8. 为计算 的结果。

  9. 将值 压入堆栈。

4.4.3.14.
  1. 断言:由于 验证,一个 值类型 的值位于栈顶。

  2. 从栈顶弹出 值。

  3. 断言:由于 验证,一个 值类型 的值位于栈顶。

  4. 从堆栈中弹出值

  5. 是计算 的结果。

  6. 是计算 的结果。

  7. 为计算 的结果。

  8. 将值 压入堆栈。

4.4.3.15.
  1. 断言: 由于 验证,一个类型为 值类型 的值位于栈顶。

  2. 从栈中弹出 值。

  3. 是计算 的结果。

  4. 是计算 的结果。

  5. 将值 压入堆栈。

4.4.3.16.
  1. 断言:由于 验证,一个 值类型 的值位于堆栈顶部。

  2. 从栈中弹出 值。

  3. 为计算 的结果。

  4. 位宽 值类型

  5. 为计算 的结果。

  6. 为两个序列 的连接。

  7. 为计算 的结果。

  8. 将值 压入堆栈。

4.4.3.17.
  1. 断言:由于 语法.

  2. 断言:由于 验证,堆栈顶部有两个 值类型 的值。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为计算 的结果。

  6. 从堆栈中弹出值

  7. 为计算 的结果。

  8. 为计算 的结果。

  9. 为两个序列 的连接。

  10. 为计算 的结果。

  11. 将值 推入堆栈。

4.4.3.18.
  1. 断言:由于 语法.

  2. 断言:由于 验证,栈顶有一个值为 值类型 的值。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为计算 的结果。

  6. 为计算 的结果。

  7. 将值 推入堆栈。

4.4.3.19.
  1. 断言: 由于 语法, .

  2. 断言:由于 验证,栈顶有一个值为 值类型 的值。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 如果 , 那么

    1. 为序列 .

  6. 否则

    1. 为序列 .

  7. 为计算 .

  8. 为计算 .

  9. 将值 推入堆栈。

其中

4.4.3.20.
  1. 断言:由于 语法.

  2. 断言:由于 验证,栈顶有一个值为 值类型 的值。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为计算 .

  6. 为两个序列 的拼接。

  7. 为计算 .

  8. 将值 推入堆栈。

4.4.3.21.
  1. 断言:由于 验证,堆栈顶部有两个 值类型 的值。

  2. 从堆栈中弹出值

  3. 从堆栈中弹出值

  4. 是计算 的结果。

  5. 为计算 的结果。

  6. 为计算 的结果。

  7. 为计算 的结果。

  8. 为计算 的结果。

  9. 为计算 的结果。

  10. 为计算 的结果。

  11. 将值 推入堆栈。

4.4.3.22.
  1. 断言: 由于 语法, .

  2. 断言:由于 验证,两个 值类型 的值位于堆栈顶部。

  3. 从堆栈中弹出值

  4. 从堆栈中弹出值

  5. 为计算 的结果。

  6. 为计算 的结果。

  7. 如果 , 那么

    1. 为序列

    2. 为序列

  8. 否则

    1. 为序列

    2. 为序列

  9. 为计算 的结果。

  10. 为计算 的结果。

  11. 为计算 的结果。

  12. 为计算 .

  13. 将值 推入堆栈。

其中

4.4.3.23.
  1. 断言: 由于 语法, .

  2. 断言:由于 验证,一个 值类型 的值位于栈顶。

  3. 从堆栈中弹出值

  4. 为计算 的结果。

  5. 为计算 的结果。

  6. 为计算 的结果。

  7. 为计算 .

  8. 将值 压入堆栈。

4.4.4. 参数指令

4.4.4.1.
  1. 断言:由于 验证,栈顶存在一个值。

  2. 从栈中弹出值

4.4.4.2.
  1. 断言:由于 验证,一个 值类型 的值位于栈顶。

  2. 从栈中弹出值

  3. 断言:由于 验证,栈顶还有两个值(具有相同的 值类型)。

  4. 从栈中弹出值

  5. 从栈中弹出值

  6. 如果 不等于 ,则

    1. 将值 压回栈中。

  7. 否则

    1. 将值 压回栈中。

注意

在 WebAssembly 的未来版本中, 可能允许每个选择有多个值。

4.4.5. 变量指令

4.4.5.1.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 为值 .

  4. 将值 推入堆栈。

4.4.5.2.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 断言:由于 验证,堆栈顶部有一个值。

  4. 从栈中弹出值

  5. 用值 替换 .

4.4.5.3.
  1. 断言:由于 验证,堆栈顶部有一个值。

  2. 从栈中弹出值

  3. 将值 推入堆栈。

  4. 将值 推入堆栈。

  5. 执行 指令 .

4.4.5.4.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 全局地址 .

  4. 断言:由于 验证 存在。

  5. 全局实例 .

  6. 为值 .

  7. 将值 推入堆栈。

4.4.5.5.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 全局地址 .

  4. 断言:由于 验证 存在。

  5. 全局实例 .

  6. 断言:由于 验证,堆栈顶部有一个值。

  7. 从栈中弹出值

  8. 用值 替换 .

注意

验证 确保全局变量实际上被标记为可变的。

4.4.6. 表格指令

4.4.6.1.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表格地址 .

  4. 断言:由于 验证 存在。

  5. 表实例 .

  6. 断言:由于 验证,一个 值类型 的值位于栈顶。

  7. 从栈中弹出值 .

  8. 如果 不小于 的长度,那么

    1. 陷入陷阱。

  9. 为值 .

  10. 将值 推入堆栈。

4.4.6.2.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表格地址 .

  4. 断言:由于 验证 存在。

  5. 表实例 .

  6. 断言:由于 验证,一个 引用值 位于栈顶。

  7. 从栈中弹出值

  8. 断言:由于 验证,一个 值类型 的值位于栈顶。

  9. 从栈中弹出值 .

  10. 如果 不小于 的长度,那么

    1. 陷入陷阱。

  11. 将元素 替换为 .

4.4.6.3.
  1. 当前

  2. 断言:根据 验证 存在。

  3. 表格地址 .

  4. 断言:根据 验证 存在。

  5. 表实例 .

  6. 的长度。

  7. 将值 推入栈中。

4.4.6.4.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表格地址 .

  4. 断言:由于 验证 存在。

  5. 表实例 .

  6. 的长度。

  7. 断言:由于 验证,栈顶有一个 值类型 的值。

  8. 从栈顶弹出值

  9. 断言:由于 验证,栈顶有一个 引用值

  10. 从栈中弹出值

  11. ,对于它,.

  12. 要么

  1. 如果 增长 个条目,用初始化值 成功,那么

    1. 将值 推入栈中。

  2. 否则

    1. 将值 推入栈。

  1. 或者

  1. 将值 推入栈。

注意

The instruction is non-deterministic. It may either succeed, returning the old table size , or fail, returning . Failure must occur if the referenced table instance has a maximum size defined that would be exceeded. However, failure can occur in other cases as well. In practice, the choice depends on the resources available to the embedder.

4.4.6.5.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表格地址 .

  4. 断言:由于 验证 存在。

  5. 表格实例 .

  6. 断言:由于 验证,一个 值类型 的值位于栈顶。

  7. 从栈顶弹出值

  8. 断言:由于 验证,栈顶有一个 引用值

  9. 从栈中弹出值

  10. 断言:由于 验证,一个 值类型 的值位于栈顶。

  11. 从栈中弹出值 .

  12. 如果 大于 的长度,则

    1. 陷入陷阱。

  1. 如果 ,则

    1. 返回。

  2. 将值 推入栈。

  3. 将值 推入堆栈。

  4. 执行指令

  5. 将值 推入栈。

  6. 将值 推入堆栈。

  7. 将值 推入栈。

  8. 执行指令

4.4.6.6.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表地址 .

  4. 断言:由于 验证 存在。

  5. 表实例 .

  6. 断言:由于 验证 存在。

  7. 表地址 .

  8. 断言:由于 验证 存在。

  9. 表实例 .

  10. 断言:由于 验证,一个 值类型 的值位于堆栈顶部。

  11. 从栈顶弹出值

  12. 断言:由于 验证,一个 值类型 的值位于堆栈顶部。

  13. 从栈顶弹出 值。

  14. 断言:由于 验证,一个 值类型 的值位于堆栈顶部。

  15. 从堆栈中弹出值 .

  16. 如果 大于 的长度,或 大于 的长度,则

    1. 陷入陷阱。

  17. 如果 ,则

  1. 返回。

  1. 如果 ,则

  1. 将值 压入堆栈。

  2. 将值 压入堆栈。

  3. 执行指令 .

  4. 执行指令

  5. 断言:由于之前对表格大小的检查,.

  6. 将值 推入堆栈。

  7. 断言:由于之前对表格大小的检查,.

  8. 将值 推入堆栈。

  1. 否则

  1. 断言:由于之前对表格大小的检查,.

  2. 将值 推入堆栈。

  3. 断言:由于之前对表格大小的检查,.

  4. 将值 推入堆栈。

  1. 执行指令 .

  1. 执行指令

  2. 将值 压入堆栈。

  3. 将值 压入堆栈。

  1. 将值 推入栈。

  2. 执行指令 .

4.4.6.7.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 表格地址 .

  4. 断言:由于 验证 存在。

  5. 表格实例 .

  6. 断言:由于 验证 存在。

  7. 元素地址 .

  8. 断言:由于 验证 存在。

  9. 元素实例 .

  10. 断言:由于 验证,堆栈顶端有一个值为 值类型 的值。

  11. 从栈顶弹出值

  12. 断言:由于 验证,堆栈顶端有一个值为 值类型 的值。

  13. 从栈顶弹出 值。

  14. 断言:由于 验证,堆栈顶端有一个值为 值类型 的值。

  15. 从堆栈中弹出值 .

  16. 如果 大于 的长度,或者 大于 的长度,则

    1. 陷入陷阱。

  17. 如果 ,则

    1. 返回。

  18. 参考值 .

  19. 将值 压入堆栈。

  20. 将值 推入堆栈。

  21. 执行指令

  22. 断言:由于之前对表格大小的检查,.

  23. 将值 推入堆栈。

  24. 断言:由于之前对段大小的检查,.

  25. 将值 推入堆栈。

  26. 将值 推入栈。

  27. 执行指令 .

4.4.6.8.
  1. 当前

  2. 断言:根据 验证 存在。

  3. 元素地址 .

  4. 断言:根据 验证 存在。

  5. 替换 .

4.4.7. 内存指令

注意

在 load 和 store 指令中,对齐方式 不会影响语义。它指示访问内存的偏移量 应该满足属性 . WebAssembly 实现可以使用此提示来优化预期用途。违反该属性的未对齐访问仍然允许,并且必须成功,无论注释如何。但是,在某些硬件上它可能明显更慢。

4.4.7.1.
  1. 当前

  2. 断言:根据 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,栈顶存在一个 值类型 的值。

  7. 从栈中弹出值 .

  8. 为整数 .

  9. 如果 不属于指令的一部分,那么

    1. 位宽 数值类型 .

  10. 如果 大于 的长度,那么

    1. 陷入陷阱。

  11. 为字节序列 .

  12. 如果 是指令的一部分,那么

    1. 为整数,使得 .

    2. 为计算 的结果。

  13. 否则

    1. 为常量,使得 .

  14. 将值 推入堆栈。

4.4.7.2.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,栈顶存在一个 值类型 的值。

  7. 从栈中弹出值 .

  8. 为整数 .

  9. 如果 大于 的长度,那么

    1. 陷入陷阱。

  10. 为字节序列 .

  11. 为整数,其中 .

  12. 为整数 .

  13. 为计算 的结果。

  14. 为计算 的结果。

  15. 将值 推入栈中。

4.4.7.3.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,栈顶存在一个 值类型 的值。

  7. 从栈中弹出值 .

  8. 为整数 .

  9. 如果 大于 的长度,那么

    1. 陷入陷阱。

  10. 为字节序列 .

  11. 为整数,使得 .

  12. 为整数 .

  13. 为计算 的结果。

  14. 将值 推入栈中。

4.4.7.5.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,一个 值类型 的值位于堆栈顶端。

  7. 从堆栈中弹出值

  8. 断言:由于 验证,栈顶存在一个 值类型 的值。

  9. 从栈中弹出值 .

  10. 为整数 .

  11. 如果 大于 的长度,那么

    1. 陷入陷阱。

  12. 为字节序列 .

  13. 为常数,使得 .

  14. .

  15. 为计算 的结果。

  16. 为计算 .

  17. 将值 推入栈中。

4.4.7.6.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,栈顶存在一个 值类型 的值。

  7. 从栈顶弹出值 .

  8. 断言:由于 验证,栈顶存在一个 值类型 的值。

  9. 从栈中弹出值 .

  10. 为整数 .

  11. 如果 不属于指令的一部分,那么

    1. 位宽 数值类型 .

  12. 如果 大于 的长度,那么

    1. 陷入陷阱。

  13. 如果 是指令的一部分,则

    1. 为计算 的结果。

    2. 为字节序列 .

  14. 否则

    1. 为字节序列 .

  15. 将字节 替换为 .

4.4.7.7.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,一个 值类型 的值位于栈顶。

  7. 从栈中弹出值 .

  8. 断言:由于 验证,栈顶存在一个 值类型 的值。

  9. 从栈中弹出值 .

  10. 为整数 .

  11. 如果 大于 的长度,那么

    1. 陷入陷阱。

  12. .

  13. 为计算 的结果。

  14. 为计算 的结果。

  15. 将字节 替换为 .

4.4.7.8.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 的长度除以 页大小

  7. 将值 推入栈中。

4.4.7.9.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址 .

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 的长度除以 页大小

  7. 断言:由于 验证,一个 值类型 的值位于栈顶。

  8. 从栈顶弹出值

  9. ,对于它,.

  10. 要么

  1. 如果 增长 成功,则

    1. 将值 推入栈中。

  2. 否则

    1. 将值 推入栈。

  1. 或者

  1. 将值 推入栈。

注意

指令 是非确定性的。它可能成功,返回旧的内存大小 ,也可能失败,返回 。如果引用的内存实例定义了最大大小,并且会超过该大小,则失败必须发生。然而,失败也可能在其他情况下发生。在实践中,选择取决于可供嵌入器使用的资源

4.4.7.10.
  1. 当前

  2. 断言:由于验证 存在。

  3. 内存地址

  4. 断言:由于验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,一个 值类型 的值位于栈顶。

  7. 从栈顶弹出值

  8. 断言:由于 验证,一个 值类型 的值位于栈顶。

  9. 从栈中弹出值

  10. 断言:由于 验证,一个 值类型 的值位于栈顶。

  11. 从堆栈中弹出值 .

  12. 如果 大于 的长度,则

    1. 陷入陷阱。

  13. 如果 ,则

    1. 返回。

  14. 将值 压入堆栈。

  15. 将值 推入堆栈。

  16. 执行指令 .

  17. 断言:由于之前对内存大小的检查,.

  18. 将值 推入堆栈。

  19. 将值 推入堆栈。

  20. 将值 推入栈。

  21. 执行指令 .

4.4.7.11.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  7. 从栈顶弹出值

  8. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  9. 从栈顶弹出 值。

  10. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  11. 从堆栈中弹出值 .

  12. 如果 大于 的长度,或者 大于 的长度,则

    1. 陷入陷阱。

  13. 如果 ,则

  1. 返回。

  1. 如果 ,则

  1. 将值 压入堆栈。

  2. 将值 压入堆栈。

  3. 执行指令 .

  4. 执行指令 .

  5. 断言:由于之前对内存大小的检查,.

  6. 将值 推入堆栈。

  7. 断言:由于先前对内存大小的检查,.

  8. 将值 推入堆栈。

  1. 否则

  1. 断言:由于先前对内存大小的检查,.

  2. 将值 推入堆栈。

  3. 断言:由于先前对内存大小的检查,.

  4. 将值 推入堆栈。

  5. 执行指令 .

  6. 执行指令 .

  7. 将值 压入堆栈。

  8. 将值 压入堆栈。

  1. 将值 推入栈。

  2. 执行指令 .

4.4.7.12.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 内存地址

  4. 断言:由于 验证 存在。

  5. 内存实例 .

  6. 断言:由于 验证 存在。

  7. 数据地址 .

  8. 断言:由于 验证 存在。

  9. 数据实例 .

  10. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  11. 从栈顶弹出值

  12. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  13. 从栈顶弹出 值。

  14. 断言:由于 验证,堆栈顶端存在一个 值类型 的值。

  15. 从堆栈中弹出值 .

  16. 如果 大于 的长度,或者 大于 的长度,则

    1. 陷入陷阱。

  17. 如果 ,则

    1. 返回。

  18. 为字节 .

  19. 将值 压入堆栈。

  20. 将值 推入堆栈。

  21. 执行指令 .

  22. 断言:由于之前对内存大小的检查,.

  23. 将值 推入堆栈。

  24. 断言:由于先前对内存大小的检查,.

  25. 将值 推入堆栈。

  26. 将值 推入栈。

  27. 执行指令 .

4.4.7.13.
  1. 当前

  2. 断言:由于 验证 存在。

  3. 数据地址 .

  4. 断言:由于 验证 存在。

  5. 替换为 数据实例 .

4.4.8. 控制指令

4.4.8.1.
  1. 什么也不做。

4.4.8.2.
  1. 陷入陷阱。

4.4.8.3.
  1. 当前

  2. 断言:由于 验证 已定义。

  3. 函数类型 .

  4. 为参数个数为 且延续为块结尾的标签。

  5. 断言:根据 验证,栈顶至少有 个值。

  6. 从栈顶弹出 个值。

  7. 进入,标签为 .

4.4.8.4.
  1. 当前

  2. 断言:由于 验证 已定义。

  3. 函数类型 .

  4. 为参数个数为 且延续为循环开始的标签。

  5. 断言:根据 验证,栈顶至少有 个值。

  6. 从栈顶弹出 个值。

  7. 进入,标签为 .

4.4.8.5.
  1. 断言:由于 验证,一个 值类型 的值在栈顶。

  2. 从栈中弹出值

  3. 如果 不为零,那么

    1. 执行块指令 .

  4. 否则

    1. 执行块指令 .

4.4.8.6.
  1. 断言:由于 验证,栈至少包含 个标签。

  2. 为栈中出现的第 个标签,从栈顶开始,从零开始计数。

  3. 的元数。

  4. 断言:由于 验证,栈顶至少有 个值。

  5. 从栈顶弹出 个值。

  6. 重复

    1. 当栈顶为值时,执行以下操作:

      1. 从栈顶弹出值。

    2. 断言:由于 验证,栈顶现在是一个标签。

    3. 从栈顶弹出标签。

  7. 将值 推入栈。

  8. 跳转到 的延续部分。

4.4.8.7.
  1. 断言:由于 验证,栈顶有一个 值类型 的值。

  2. 从栈中弹出值

  3. 如果 不为零,那么

    1. 执行 指令 .

  4. 否则

    1. 什么也不做。

4.4.8.8.
  1. 断言:由于 验证,栈顶有一个 值类型 的值。

  2. 从栈中弹出值 .

  3. 如果 小于 的长度,则

    1. 为标签 .

    2. 执行 指令 .

  4. 否则

    1. 执行 指令 .

4.4.8.9.
  1. 当前

  2. 的元数。

  3. 断言:由于 验证,栈顶至少有 个值。

  4. 从栈中弹出结果 .

  5. 断言:由于 验证,栈中至少包含一个

  6. 当栈顶不是帧时,执行以下操作

    1. 从栈中弹出栈顶元素。

  7. 断言:栈顶是帧 .

  8. 从栈中弹出帧。

  9. 推入栈。

  10. 跳转到最初推送帧的调用指令后的指令。

4.4.8.10.
  1. 当前

  2. 断言:由于验证 存在。

  3. 函数地址

  4. 调用 地址为 的函数实例。

4.4.8.11.
  1. 当前

  2. 断言:由于验证 存在。

  3. 表格地址 .

  4. 断言:由于验证 存在。

  5. 表格实例 .

  6. 断言:由于验证 存在。

  7. 函数类型 .

  8. 断言:由于验证,栈顶存在一个值类型 的值。

  9. 从栈中弹出值 .

  10. 如果 不小于 的长度,那么

    1. 陷入陷阱。

  11. 引用 .

  12. 如果, 那么

    1. 陷入陷阱。

  13. 断言:由于表格修改的验证 是一个函数引用

  14. 函数引用 .

  15. 断言:由于表格修改的验证 存在。

  16. 函数实例 .

  17. 函数类型 .

  18. 如果 不同,则

    1. 陷入陷阱。

  19. 调用 地址为 的函数实例。

4.4.9.

以下辅助规则定义了执行形成 指令序列 的语义。

4.4.9.1. 进入带标签
  1. 推入堆栈。

  2. 跳转到指令序列 的开头。

注意

进入指令序列不需要正式的规约规则,因为标签 已嵌入到控制指令直接规约到的 管理指令 中。

4.4.9.2. 退出带有标签

当一个块在没有跳转或陷阱中止的情况下结束时,执行以下步骤。

  1. 从堆栈顶部弹出所有值 .

  2. 断言:由于 验证,标签 现在位于堆栈顶部。

  3. 从栈顶弹出标签。

  4. 推回堆栈。

  5. 跳转到与标签 相关的 结构化控制指令 之后的位置。

注意

此语义也适用于包含在 指令中的指令序列。因此,循环的执行会从末尾掉落,除非显式执行反向分支。

4.4.10. 函数调用

以下辅助规则定义了通过一个 调用指令 调用 函数实例 并从其中返回的语义。

4.4.10.1. 调用 函数地址
  1. 断言:由于 验证 存在。

  2. 函数实例.

  3. 函数类型 .

  4. 值类型 列表 .

  5. 表达式 .

  6. 断言:由于 验证,堆栈顶部有 个值。

  7. 从栈顶弹出 个值。

  8. .

  9. 将具有元数 的激活压入栈。

  10. 为元数为 且继续为函数结尾的 标签

  11. 进入 具有标签 的指令序列 .

4.4.10.2. 从函数返回

当函数执行到末尾时,如果没有发生跳转(例如,) 或发生陷阱中断,则执行以下步骤。

  1. 当前

  2. 激活的元数。

  3. 断言:由于 验证,堆栈顶端有 个值。

  4. 从栈中弹出结果 .

  5. 断言:由于 验证,帧 现在位于堆栈顶端。

  6. 从栈中弹出帧。

  7. 推回堆栈。

  8. 跳转到原始调用后的指令。

4.4.10.3. 宿主函数

调用 宿主函数 具有非确定性行为。 它可能以 陷阱 终止,也可能正常返回。 但是,在后一种情况下,它必须根据其 函数类型 在堆栈上消耗和生成正确数量和类型的 WebAssembly

宿主函数也可以修改 存储 。 然而,所有存储修改必须导致原始存储的 扩展 ,即它们只能修改可变内容,并且不能删除实例。 此外,生成的存储必须是 有效的 ,即其中的所有数据和代码都类型正确。

这里, 表示在当前存储 中,使用参数 执行宿主函数 的实现定义方式。它产生一组可能的结果,其中每个元素要么是修改后的存储 和一个 结果 的对,要么是表示发散的特殊值 。如果至少有一个参数,其结果集不是单一的,则宿主函数是非确定性的。

为了使 WebAssembly 实现能够在存在宿主函数的情况下保持 健全性,每个 宿主函数实例 都必须是 有效的,这意味着它必须遵守适当的前置条件和后置条件:在 有效存储 下,给定与指定的参数类型 匹配的参数 , 执行宿主函数必须产生一组非空的可能结果,每个结果要么是发散,要么由一个有效存储 组成,该存储是 扩展,以及一个与指定的返回类型 匹配的结果。所有这些概念在 附录 中进行了精确定义。

注意

宿主函数可以通过 调用模块导出 的函数来回调到 WebAssembly。但是,任何此类调用的影响都被宿主函数允许的非确定性行为所包含。

4.4.11. 表达式

一个 表达式 是相对于 当前 进行求值的,该帧指向其包含的 模块实例

  1. 跳转到表达式的指令序列 的开头。

  2. 执行指令序列。

  3. 断言:由于 验证,栈顶包含一个

  4. 从栈中弹出

是求值的結果。

注意

求值会迭代此归约规则,直到达到一个值。构成 函数 主体的表达式在函数 调用 期间被执行。

4.5. 模块

对于模块,执行语义主要定义了 实例化,它会 分配 模块及其包含的定义的实例,从包含的 元素数据 段初始化 表格内存,如果存在则调用 启动函数。它还包括对导出函数的 调用

实例化依赖于一些辅助概念来进行类型检查导入分配实例。

4.5.1. 外部类型

为了将外部值导入进行比较,这些值由外部类型进行分类。以下辅助类型规则指定了相对于存储 的这种类型关系,其中引用了实例。

4.5.1.1.
4.5.1.2.
4.5.1.3.
4.5.1.4.

4.5.2. 值类型

为了将参数值与导出的函数的参数类型进行比较,值被分类为值类型。以下辅助类型规则指定了此类型关系,相对于一个可能包含引用地址的存储 S。

4.5.2.1. 数值
  • 该值在数值类型 中有效。

4.5.2.2. 空引用
  • 该值在引用类型 中有效。

4.5.2.3. 函数引用
  • 外部值 必须有效。

  • 然后,该值在引用类型 中有效。

4.5.2.4. 外部引用
  • 该值在引用类型 中有效。

4.5.3. 分配

函数、表格、内存和全局变量的新实例在存储 S 中被分配,如下面辅助函数定义。

4.5.3.1. 函数
  1. 为要分配的函数,而 为其模块实例。

  2. 为 S 中第一个空闲函数地址。

  3. 为函数类型 .

  4. 函数实例 .

  5. 附加到 中的 .

  6. 返回 .

4.5.3.2. 宿主函数
  1. 宿主函数 用于分配和 函数类型.

  2. 为 S 中第一个空闲函数地址。

  3. 函数实例 .

  4. 附加到 中的 .

  5. 返回 .

注意

WebAssembly 语义本身永远不会分配宿主函数,但可以由嵌入器分配。

4.5.3.3. 表格
  1. 为要分配的表格类型,而为初始化值。

  2. 表格类型的结构。

  3. 中第一个空闲的表格地址

  4. 表格实例,其中个元素都设置为.

  5. 追加到中,并加入到中。

  6. 返回 .

4.5.3.4. Memories
  1. 为要分配的 内存类型

  2. 内存类型 的结构。

  3. 中第一个空闲的 内存地址

  4. 内存实例 ,它包含 页已归零的 字节

  5. 附加到 中。

  6. 返回 .

4.5.3.5. 全局变量
  1. 为要分配的 全局变量类型,并且 为用来初始化全局变量的值。

  2. 中第一个空闲的 全局变量地址

  3. 全局变量实例 .

  4. 追加到 中。

  5. 返回 .

4.5.3.6. 元素段
  1. 为元素类型,并令 为要分配的 引用 向量。

  2. 中的第一个空闲 元素地址

  3. 元素实例 .

  4. 附加到 中。

  5. 返回 .

4.5.3.7. 数据段
  1. 为要分配的 字节 向量。

  2. 中的第一个空闲 数据地址

  3. 数据实例 .

  4. 附加到 中。

  5. 返回 .

4.5.3.8. 扩展
  1. 假设 是要扩展的 表实例 是要扩展的元素数量, 是初始化值。

  2. 假设 加上 的长度。

  3. 如果 大于或等于 , 则失败。

  4. 假设 表类型 的结构。

  5. 假设 ,其中 更新为 .

  6. 如果 不是 有效的,则失败。

  7. 附加到 .

  8. 设置为 表类型 .

4.5.3.9. 增长 内存
  1. 表示要增长的 内存实例 表示要增长的大小(以 为单位)。

  2. 断言: 的长度可以被 页大小 整除。

  3. 等于 加上 的长度除以 页大小 .

  4. 如果 大于 , 则失败。

  5. 表示 内存类型 的结构。

  6. 假设 ,其中 更新为 .

  7. 如果 不是 有效的,则失败。

  8. 次循环中,将 字节,值设置为 ,追加到 .

  9. 设置为 内存类型 .

4.5.3.10. 模块

用于 模块 的分配函数需要一组合适的 外部值,这些外部值被假定为与模块的 导入 向量 匹配;此外,还需要模块的 全局变量 的初始化 列表,以及模块的 元素段引用 向量列表。

  1. 为要分配的 模块,而 为提供模块导入的 外部值 向量, 为模块的 全局变量 的初始化 ,而 为模块的 元素段引用 向量。

  2. 对于每个在 函数 中的 ,执行以下操作:

    1. 函数地址,该地址是通过 分配 得到的,对于下面定义的 模块实例

  3. 对于每个在 表格 中的 ,执行以下操作:

    1. 表格类型 .

    2. 表格地址,该地址是通过 分配 并使用初始值 得到的。

  4. 对于每个在 内存 中的 ,执行以下操作:

    1. 内存地址,该地址是通过 分配 得到的。

  5. 对于每个在 全局变量 中的 ,执行以下操作:

    1. 全局变量地址,该地址是通过 分配 并使用初始值 得到的。

  6. 对于每个元素段 中,执行以下操作:

    1. 为分配一个 元素实例(类型为 引用类型 )得到的 元素地址,该实例的内容为 .

  7. 对于每个数据段 中,执行以下操作:

    1. 为分配一个内容为 数据实例 得到的 数据地址

  8. 为以索引顺序连接的 函数地址

  9. 为以索引顺序连接的 表格地址

  10. 为以索引顺序连接的 内存地址

  11. 为以索引顺序连接的 全局地址

  12. 为以索引顺序连接的 元素地址

  13. 数据地址 按索引顺序连接的结果。

  14. 为从 中提取的 函数地址 列表,与 连接。

  15. 为从 中提取的 表地址 列表,与 连接。

  16. 为从 中提取的 内存地址 列表,与 连接。

  17. 为从 中提取的 全局地址 列表,与 连接。

  18. 对于每个 导出 中,执行以下操作

    1. 如果 函数索引 的函数导出,则令 外部值 .

    2. 否则,如果 表格索引 的表格导出,则令 外部值 .

    3. 否则,如果 内存索引 的内存导出,则令 外部值 .

    4. 否则,如果 全局索引 的全局导出,则令 外部值 .

    5. 导出实例 .

  19. 导出实例 按索引顺序的串联。

  20. 模块实例 .

  21. 返回 .

其中

这里,符号 是对多个 分配 对象类型 的简写,定义如下

此外,如果省略号 是一个序列 (如全局变量或表格),那么该序列的元素将逐点传递给分配函数。

注意

模块分配的定义与其相关函数的分配相互递归,因为生成的模块实例 被作为参数传递给函数分配器,以形成必要的闭包。在实现中,这种递归可以通过在次要步骤中对其中一个进行修改来轻松地解开。

4.5.4. 实例化

给定一个 存储 ,一个 模块 使用一个外部值列表 实例化,该列表提供所需的导入,如下所示。

实例化检查模块是否 有效 以及提供的导入是否 匹配 声明的类型,否则可能会 *失败* 并出现错误。实例化也可能导致从初始化一个活动段的表格或内存或从执行启动函数中 陷阱。如何报告这些条件取决于 嵌入器

  1. 如果 不是 有效的,那么

    1. 失败。

  2. 断言:有效的,具有 外部类型 对其 导入 进行分类。

  3. 如果 导入 的数量 不等于提供的 外部值 的数量 ,那么

    1. 失败。

  4. 对于每个 外部值 外部类型 中,执行以下操作:

    1. 如果 在存储 中不 有效,对于 外部类型 ,那么

      1. 失败。

    2. 如果 匹配,那么

      1. 失败。

  1. 是一个辅助模块 实例 ,它只包含最终模块实例 中的导入全局变量以及导入和分配的函数,定义如下。

  2. 是一个辅助 .

  3. 将帧 推送到栈中。

  4. 是由 确定的 全局 初始化 的向量。它们可以按如下方式计算。

    1. 对于每个在 全局变量 中的 ,执行以下操作:

      1. 评估 初始化表达式 的结果。

    2. 断言:由于 验证,帧 现在位于堆栈顶部。

    3. 为按索引顺序连接的

  5. 为由 中的 元素段 确定的 引用 向量列表。它们可以按如下方式计算。

    1. 对于 中的每个 元素段 ,以及对于 中的每个元素 表达式 ,执行以下操作:

      1. 求值 初始化表达式 的结果。

    2. 为按索引 顺序连接的函数元素

    3. 为按索引 顺序连接的函数元素向量

  6. 从堆栈中弹出帧

  7. 为一个新的模块实例,它从 在存储 分配,导入为 , 全局初始化值为 , 元素段内容为 , 令 为模块分配产生的扩展存储。

  8. 为辅助 .

  9. 将帧 推入堆栈。

  10. 对于 中每个 元素段 ,其 模式,执行以下操作:

    1. 为向量 的长度。

    2. 执行 指令序列 .

    3. 执行 指令 .

    4. 执行 指令 .

    5. 执行 指令 .

    6. 执行 指令 .

  11. 对于 中每个 元素段 ,其 模式,执行以下操作:

    1. 执行 指令 .

  12. 对于每个 数据段 中,其 模式 形式为 ,执行以下操作:

    1. 断言:.

    2. 为向量 的长度。

    3. 执行 指令序列 .

    4. 执行 指令 .

    5. 执行 指令 .

    6. 执行 指令 .

    7. 执行 指令 .

  13. 如果 开始函数 不为空,则:

    1. 开始函数 .

    2. 执行 指令 .

  14. 断言:由于 验证,帧 现在位于堆栈顶部。

  15. 从堆栈中弹出帧

其中

4.5.5. 调用

一旦一个 模块 已经 实例化,任何导出的函数都可以通过其 函数地址 存储 中以及一个适当的列表 参数从外部 *调用*。

如果参数不符合 函数类型,则调用可能 *失败* 并返回错误。调用也可能导致 陷阱。由 嵌入器 定义如何报告这些情况。

注意

如果 嵌入器 API 在执行调用之前本身执行类型检查,无论是静态的还是动态的,那么除了陷阱之外,不会发生其他错误。

执行以下步骤

  1. 断言: 存在。

  2. 函数实例 .

  3. 函数类型 .

  4. 如果提供的参数值的长度 与预期参数的数量 不同,则

    1. 失败。

  5. 对于 值类型 以及相应的 , 执行

    1. 如果 不是 有效的 值类型 , 那么

      1. 失败。

  6. 为虚拟的 .

  7. 将帧 推入堆栈。

  8. 将值 推入堆栈。

  9. 在地址 调用函数实例。

函数返回后,执行以下步骤。

  1. 断言:由于 验证,堆栈顶部有

  2. 从堆栈中弹出

  3. 断言:由于 验证,帧 现在位于堆栈顶部。

  4. 从堆栈中弹出帧

作为调用的结果返回。

5. 二进制格式

5.1. 约定

WebAssembly 模块 的二进制格式是对其 抽象语法 的密集线性 *编码*。[1]

该格式由一个 *属性语法* 定义,其唯一的终结符是 字节。字节序列是模块的格式良好的编码,当且仅当它由该语法生成。

该语法中的每个产生式都正好有一个综合属性:相应的字节序列编码的抽象语法。因此,属性语法隐式地定义了一个 *解码* 函数(即,二进制格式的解析函数)。

除了少数例外,二进制语法与抽象语法的语法非常相似。

注意

抽象语法中的某些短语在二进制格式中有多种可能的编码。例如,数字可以像具有可选前导零一样进行编码。解码器实现必须支持所有可能的替代方案;编码器实现可以选择任何允许的编码。

包含 WebAssembly 模块的二进制格式文件的推荐扩展名为“”,推荐的 媒体类型 为“”。

5.1.1. 语法

在定义二进制格式的语法规则时,采用以下约定。它们反映了用于 抽象语法 的约定。为了区分二进制语法的符号和抽象语法的符号,采用 字体来表示前者。

  • 终结符是 字节,用十六进制表示法表示:.

  • 非终结符以打字机字体书写:.

  • 的迭代序列。

  • 是一个可能是空的 的迭代序列。(这是一种简写方式,用于 ,其中 不相关。)

  • 的可选出现。(这是一种简写方式,用于 ,其中 。)

  • 表示与非终结符 相同的语言,但同时也为变量 绑定了为 合成的属性。模式也可以用作变量,例如 .

  • 产生式以 , 其中每个 是在给定情况下为 合成的属性,通常从 中绑定的属性变量中合成。

  • 一些产生式通过括号中的边际条件进行补充,这些条件限制了产生式的适用性。它们为将产生式组合扩展为许多独立情况提供了一种简写方式。

  • 如果相同的元变量或非终结符符号在一个产生式中多次出现(在语法或属性中),那么所有这些出现必须具有相同的实例。(这是一种简写方式,用于需要多个不同变量相等的边际条件。)

注意

例如,用于 二进制语法数字类型 如下所示

因此,字节 编码类型 编码类型 ,以此类推。其他字节值不允许作为数字类型的编码。

有关 二进制语法限制 定义如下

也就是说,限制对编码为字节 后跟 值的编码,或者字节 后跟两个这样的编码。变量 指定了各自 非终结符的属性,在本例中,它们是实际的 无符号整数,它们将解码为。然后,完整产生的属性是限制的抽象语法,以之前的值的术语表示。

5.1.2. 辅助符号

在处理二进制编码时,还使用以下符号

  • 表示空字节序列。

  • 是从推导中生成产生式 的字节序列的长度。

5.1.3. 向量

向量 使用其 长度以及其元素序列的编码进行编码。

5.2.

5.2.1. 字节

字节 自己进行编码。

5.2.2. 整数

所有 整数 使用 LEB128 可变长度整数编码进行编码,可以是无符号或有符号变体。

无符号整数 使用 无符号 LEB128 格式进行编码。作为附加约束,对类型为 的值的总字节数不能超过 字节。

带符号整数 使用 有符号 LEB128 格式编码,该格式使用二进制补码表示。作为额外的约束,编码类型为 的值的字节总数不能超过 个字节。

未解释整数 编码为有符号整数。

注意

非终结字节的生成式中的边条件 限制了编码的长度。但是,在这些界限内仍然允许“尾部零”。例如, 都是值 作为 编码的有效编码。类似地, 都是值 作为 编码的有效编码。

对终结字节的值 的边条件进一步强制执行,对于正值,这些字节中任何未使用的位必须为 ,而对于负值,必须为 。例如, 作为 编码是格式错误的。类似地, 作为 编码是格式错误的。

5.2.3. 浮点数

浮点数 值直接由其 [IEEE-754-2019](第 3.4 节)中的位模式在 小端 字节顺序中进行编码

5.2.4. 名称

名称 被编码为一个包含名称字符序列的 [UNICODE](第 3.9 节)UTF-8 编码的 向量 字节。

辅助 函数表示此编码,定义如下

注意

与其他一些格式不同,名称字符串不以 0 结尾。

5.3. 类型

注意

在某些情况下,可能的类型包括类型构造函数或由 类型索引 表示的类型。因此,类型构造函数的二进制格式对应于小负数的编码 值,因此它们可以明确地出现在与(正)类型索引相同的位置。

5.3.1. 数字类型

数字类型 由单个字节编码。

5.3.2. 向量类型

向量类型 也由单个字节编码。

5.3.3. 引用类型

引用类型 也由单个字节编码。

5.3.4. 值类型

值类型 使用其各自的编码作为 数字类型向量类型引用类型 进行编码。

注意

值类型可以出现在允许使用类型索引的上下文中,例如在块类型的情况下。因此,类型的二进制格式对应于小负数的带符号 LEB128 编码,以便它们将来可以与(正)类型索引共存。

5.3.5. 结果类型

结果类型通过各自的值类型向量进行编码。

5.3.6. 函数类型

函数类型通过字节 0x60 后面跟着的各自的参数类型和结果类型向量进行编码。

5.3.7. 限制

限制通过一个前导标志进行编码,该标志指示是否存在最大值。

5.3.8. 内存类型

内存类型 使用它们的 限制 进行编码。

5.3.9. 表类型

表类型 使用它们的 限制 以及它们元素的 引用类型 编码。

5.3.10. 全局类型

全局类型 通过它们的 值类型 和一个表示它们 可变性 的标志进行编码。

5.4. 指令

指令操作码进行编码。每个操作码由一个字节表示,后面跟着指令的立即数参数(如果有)。唯一的例外是 结构化控制指令,它由多个操作码组成,用来括起它们的嵌套指令序列。

注意

用于编码指令的字节码范围内的间隙保留供将来扩展。

5.4.1. 控制指令

控制指令 具有不同的编码。对于结构化指令,构成嵌套块的指令序列以 的显式操作码结尾。

块类型 使用特殊的压缩形式进行编码,可以是字节 (表示空类型),单个 值类型,或者作为以正 有符号整数 编码的 类型索引

注意

指令的编码中, 的操作码可以省略,如果接下来的指令序列为空。

与任何其他出现不同,类型索引块类型中被编码为正的有符号整数,这样它的有符号 LEB128 位模式就不会与值类型或特殊代码的编码冲突,这些编码对应于负整数的 LEB128 编码。为了避免在允许的索引范围内有任何损失,它被视为一个 33 位有符号整数。

5.4.2. 引用指令

引用指令 由单个字节码表示。

5.4.3. 参数化指令

参数化指令 由单个字节码表示,可能后跟类型注释。

5.4.4. 变量指令

变量指令 由字节码表示,后跟相应索引的编码。

5.4.5. 表格指令

表格指令 用单个字节或一个字节前缀后跟一个可变长度的 无符号整数 表示。

5.4.6. 内存指令

每个内存指令变体都使用不同的字节码进行编码。加载和存储指令后面跟着其立即数的编码。

注意

在未来的 WebAssembly 版本中, 指令编码中出现的额外零字节可用于索引其他内存。

5.4.7. 数值指令

所有数值指令变体都由不同的字节码表示。

指令后面跟着相应的字面量。

所有其他数字指令都是没有立即数的普通操作码。

所有饱和截断指令都有一个字节的前缀,而实际的操作码则由一个可变长度的 无符号整数 编码。

5.4.8. 向量指令

向量指令 的所有变体都由独立的字节码表示。它们都有一个字节前缀,而实际的操作码由一个可变长度的 无符号整数 编码。

向量加载和存储后是其 介质的编码。

指令后紧跟 16 个立即字节,这些字节将被转换为 ,字节序为

指令后紧跟 16 个 立即数的编码。

指令后紧跟一个 立即数的编码。

所有其他向量指令都是没有立即数的纯操作码。

5.4.9. 表达式

表达式 由其指令序列编码,并以一个明确的 操作码来终止,表示 .

5.5. 模块

模块的二进制编码被组织成。大多数节对应于一个模块记录的组件,除了函数定义被分成两个节,将它们的类型声明在函数节中与它们在代码节中的主体分离。

注意

这种分离使模块中函数的并行流式编译成为可能。

5.5.1. 索引

所有索引都使用它们的值进行编码。

5.5.2.

每个节包含:

  • 一个字节的节标识符

  • 以字节为单位的内容的大小

  • 实际的内容,其结构取决于节标识符。

每个节都是可选的;省略的节等同于存在且内容为空的节。

以下带参数的语法规则定义了具有标识符的节的通用结构,其内容由语法描述。

对于大多数部分,内容编码一个向量。 在这些情况下,空结果被解释为空向量。

注意

除了未知的自定义部分对于解码不是必需的,但可以在浏览二进制文件时用于跳过部分。 如果大小与二进制内容的长度不匹配,则模块格式错误。

使用以下部分 ID

ID

部分

0

自定义部分

1

类型部分

2

导入部分

3

函数部分

4

表部分

5

内存部分

6

全局部分

7

导出部分

8

开始部分

9

元素部分

10

代码部分

11

数据部分

12

数据计数部分

注意

部分 ID 不总是对应于模块编码中的部分顺序

5.5.3. 自定义部分

自定义部分 的 ID 为 0。 它们旨在用于调试信息或第三方扩展,并且被 WebAssembly 语义忽略。 它们的内容包括一个名称,以进一步标识自定义部分,后面跟着一个用于自定义用途的未解释字节序列。

注意

如果实现解释了自定义部分的数据,则该数据中的错误或部分的放置位置不得使模块失效。

5.5.4. 类型部分

类型部分 的 ID 为 1。 它解码为一个函数类型 向量,表示模块组件。

5.5.5. 导入部分

导入部分 的 id 为 2。它解码为一个 导入 的向量,表示 是一个 模块 的组件。

5.5.6. 函数部分

函数节的 ID 为 3。它解码成一个 类型索引 的向量,表示 函数 中的 字段,这些函数位于 模块 组件中。相应函数的 字段在 代码节 中单独编码。

5.5.7. 表格节

表格节的 ID 为 4。它解码成一个 表格 的向量,表示 模块 组件。

5.5.8. 内存节

内存节的 ID 为 5。它解码成一个 内存 的向量,表示 模块 组件。

5.5.9. 全局段

全局段的 ID 为 6。它解码为一个 全局变量 的向量,表示 模块 部分。

5.5.10. 导出段

导出段的 ID 为 7。它解码为一个 导出 的向量,表示 模块 部分。

5.5.11. 开始部分

开始部分 具有 ID 8。它解码为一个可选的 启动函数,它表示 模块 的组成部分。

5.5.12. 元素部分

元素段 的 id 为 9。它解码为一个 元素段 的向量,这些段表示 组件的一个 模块

注意

初始整数可以解释为一个位域。位 0 区分被动段或声明段和主动段,位 1 表示主动段存在显式表格索引,否则区分被动段和声明段,位 2 表示使用元素类型和元素 表达式 而不是元素种类和元素索引。

将来版本的 WebAssembly 中可能会添加其他元素种类。

5.5.13. 代码段

代码段 的 id 为 10。它解码为一个代码条目向量,这些条目是 值类型 向量和 表达式 的对。它们表示 字段的一个 函数 组件的一个 模块。相应的函数的 字段在 函数段 中单独编码。

每个代码条目的编码包括

  • 函数代码的 大小(以字节为单位),

  • 实际的 函数代码,它依次包含

    • 局部变量 的声明,

    • 作为 表达式 的函数 主体

局部变量声明被压缩成一个向量,其条目包含

表示相同值类型的 计数 个局部变量。

在此, 遍历对 。元函数 连接所有序列 . 任何导致结果序列长度超出 vector 最大尺寸的代码格式错误。

注意

sections 类似,代码 在解码时不需要,但可以用于在二进制文件中导航时跳过函数。如果尺寸与相应函数代码的长度不匹配,则模块格式错误。

5.5.14. 数据段

数据段 的 ID 为 11。它解码为一个 数据段 向量,代表 模块 组件。

注意

初始整数可以解释为位域。位 0 表示被动段,位 1 表示活动段是否存在显式内存索引。

在当前版本的 WebAssembly 中,单个模块最多可以定义或导入一个内存,因此所有有效的 active 数据段的 值为 .

5.5.15. 数据计数段

数据计数段 的 ID 为 12。它解码为一个可选的 u32,表示 数据段数据段 中的数量。如果此计数与数据段向量长度不匹配,则模块格式错误。

注意

数据计数区用于简化单遍验证。由于数据区出现在代码区之后, 指令在读取数据区之前无法检查数据段索引是否有效。数据计数区出现在代码区之前,因此单遍验证器可以使用此计数,而不是延迟验证。

5.5.16. 模块

一个 模块 的编码以一个包含 4 字节魔数(字符串 ) 和一个版本字段的序言开始。WebAssembly 二进制格式的当前版本为 1。

序言之后是一系列 区段自定义区段 可以插入此序列中的任何位置,而其他区段最多只能出现一次,并且必须按照规定的顺序排列。所有区段都可以为空。

由(可能为空的)函数代码 区段产生的向量的长度必须匹配。

同样,可选的数据计数必须与 数据段 向量长度匹配。此外,如果代码区段中出现任何 数据索引,则数据计数必须存在。

其中,对于每个 ,

注意

WebAssembly 二进制格式的版本将来可能会增加,如果需要对格式进行向后不兼容的更改。但是,此类更改预计很少发生,即使有也只会在极少数情况下发生。二进制格式旨在向前兼容,以便将来可以进行扩展而无需增加其版本。

6. 文本格式

6.1. 约定

WebAssembly 模块 的文本格式是其 抽象语法 转换为 S 表达式 的渲染。

二进制格式 一样,文本格式由一个属性语法定义。文本字符串是模块的格式良好的描述,当且仅当它是根据语法生成的。此语法的每个产生式最多有一个综合属性:相应的字符序列表达的抽象语法。因此,属性语法隐式地定义了一个解析函数。一些产生式还将 上下文 作为继承属性,该属性记录绑定 标识符

除了少数例外,文本语法的核心与抽象语法的语法密切相关。但是,它还定义了许多缩写,这些缩写是对核心语法的“语法糖”。

包含文本格式 WebAssembly 模块的文件的推荐扩展名为“”。具有此扩展名的文件被假定为使用 UTF-8 编码,如 [UNICODE](第 2.5 节)所述。

6.1.1. 语法

在定义文本格式的语法规则时采用以下约定。它们反映了用于 抽象语法二进制格式 的约定。为了将文本语法的符号与抽象语法的符号区分开来,采用 字体来表示前者。

  • 终结符可以是引号括起来的文字字符串,也可以用 [UNICODE] 标量值表示:, .(所有直接写出的字符都明确地来自 Unicode 的 7 位 ASCII 子集。)

  • 非终结符以打字机字体书写:.

  • 的迭代序列。

  • 是可能为空的 迭代序列。(这是 的简写形式,在 不相关的情况下使用。)

  • 是一个或多个 迭代的序列。(这是 的简写形式,在 的情况下使用。)

  • 的可选出现。(这是 的简写形式,在 的情况下使用。)

  • 表示与非终结符 相同的语言,但它也将变量 绑定到为 合成的属性。模式也可以用作变量,例如 .

  • 产生式写成 , 其中每个 是在给定情况下为 合成的属性,通常来自在 中绑定的属性变量。

  • 一些产生式通过括号中的边际条件进行补充,这些条件限制了产生式的适用性。它们为将产生式组合扩展为许多独立情况提供了一种简写方式。

  • 如果同一个元变量或非终结符在产生式中多次出现(在语法或属性中),那么所有这些出现都必须具有相同的实例化。

  • 区分词法产生式和语法产生式。对于后者,在语法包含空格的任何地方都允许任意 空格。定义 词法语法 语法的产生式被认为是词法产生式,其他所有产生式都是语法产生式。

注意

例如,文本语法数字类型 给出如下:

对于 文本语法限制 定义如下

变量 分别命名了各自 非终结符的属性,在本例中,它们是实际解析的 无符号整数。完整产生的属性是限制的抽象语法,用前述值表示。

6.1.2. 缩写

除了与 抽象语法 紧密对应的核心语法之外,文本语法还定义了一些缩写,这些缩写可用于方便性和可读性。

缩写由重写规则定义,这些规则指定了它们扩展到核心语法的形式

在将核心语法规则应用于构造抽象语法之前,假定这些扩展是递归地按出现顺序应用的。

6.1.3. 上下文

文本格式允许使用符号 标识符 来代替 索引。为了将这些标识符解析为具体的索引,一些语法产生式由标识符上下文 索引,它作为合成属性,记录了每个 索引空间 中声明的标识符。此外,上下文记录了模块中定义的类型,以便可以为 函数 计算 参数 索引。

将标识符上下文定义为 记录 非常方便,其抽象语法如下

对于每个索引空间,这样的上下文包含分配给已定义索引的 标识符 列表。未命名的索引与这些列表中的空条目 () 相关联。

如果在没有索引空间包含重复标识符的情况下,则标识符上下文为格式正确

6.1.3.1. 约定

为了避免不必要的混乱,在写出标识符上下文时会省略空组件。例如,记录 是一个 标识符上下文 的简写形式,其组件全部为空。

6.1.4. 向量

向量 写成普通的序列,但对这些序列的长度有限制。

6.2. 词法格式

6.2.1. 字符

文本格式为源文本分配含义,源文本由字符序列组成。假设字符表示为有效的[UNICODE](第 2.4 节)标量值

注意

虽然源文本可能在注释字符串文字中包含任何 Unicode 字符,但语法其余部分仅由 7 位ASCII Unicode 子集支持的字符组成。

6.2.2. 标记

源文本中的字符流从左到右被划分为标记序列,如下面的语法定义所示。

令牌根据 *最长匹配* 规则从输入字符流中形成。 也就是说,下一个令牌总是由上述词法语法识别的最长可能的字符序列组成。 令牌可以用 空格 分隔,但除了字符串外,它们本身不能包含空格。

*关键字* 令牌是通过在本章的词法生产中以字面形式出现的 终结符 隐式定义的,例如 , 或在本章中出现的显式定义。

任何不属于其他类别的令牌都被认为是 *保留的*,并且不能出现在源代码中。

注意

定义保留令牌集的效果是,所有令牌必须用括号、空格注释 分隔。 例如, 是一个保留令牌, 也是一个保留令牌。 因此,它们不会被识别为两个独立的令牌 ,或

6.2.3. White Space

White space is any sequence of literal space characters, formatting characters, or comments. The allowed formatting characters correspond to a subset of the ASCII format effectors, namely, horizontal tabulation (), line feed (), and carriage return ().

空白字符只用于分隔tokens,除此之外将被忽略。

6.2.4. 注释

一个注释可以是行注释,以双分号 开始并延伸到行尾,也可以是块注释,用分隔符 包裹起来。块注释可以嵌套。

这里,伪标记 表示输入的结束。针对 产生的规则中的 _前瞻_ 限制,消除了语法歧义,使得只有正确的方括号形式的块注释分隔符才被允许。

注意

任何格式化和控制字符都可以在注释中使用。

6.3.

本节中的语法产生式定义了 _词法语法_,因此不允许使用任何 空白

6.3.1. 整数

所有 整数 可以用十进制或十六进制表示。在两种情况下,数字都可以选择性地用下划线分隔。

整数字面量的允许语法取决于大小和符号。此外,它们的数值必须在相应的类型范围内。

未解释整数 可以写成有符号或无符号形式,并在抽象语法中归一化为无符号形式。

6.3.2. 浮点数

浮点数 值可以用十进制或十六进制表示。

字面量的值不能超出对应 [IEEE-754-2019] 类型的可表示范围(即,数值不能溢出到 ),但它可能会被 舍入 到最接近的可表示值。

注意

可以通过使用十六进制表示法来防止舍入,该表示法不超过所需类型支持的有效位数。

浮点数也可以写成无穷大规范 NaN非数字)的常量。此外,通过提供显式有效载荷值可以表示任意 NaN 值。

6.3.3. 字符串

字符串表示字节序列,可以用来表示文本和二进制数据。它们用引号括起来,可以包含除 ASCII 控制字符、引号 () 或反斜杠 () 之外的任何字符,除非用转义序列表示。

字符串字面量中的每个字符都表示其 UTF-8 [UNICODE] (第 2.5 节) 编码对应的字节序列,除了十六进制转义序列 ,它们表示相应值的原始字节。

6.3.4. 名称

名称 是表示字面字符序列的字符串。名称字符串必须形成有效的 UTF-8 编码,如 [UNICODE](第 2.5 节)所定义,并被解释为 Unicode 标量值的字符串。

注意

假设源文本本身编码正确,不包含任何十六进制字节转义的字符串始终是有效的名称。

6.3.5. 标识符

索引 可以以数字和符号形式给出。代替索引的符号 *标识符* 以 开头,后面跟着任何不包含空格、引号、逗号、分号或括号的可打印 ASCII 字符序列。

6.3.5.1. 约定

一些缩写的扩展规则需要插入一个 *新的* 标识符。这可以是任何语法有效的标识符,只要它在给定的源文本中没有出现过。

6.4. 类型

6.4.1. 数字类型

6.4.2. 向量类型

6.4.3. 引用类型

6.4.4. 值类型

6.4.5. 函数类型

注意

函数类型中参数的可选标识符名称仅用于文档目的。它们不能在任何地方被引用。

6.4.5.1. 缩写

多个匿名参数或结果可以合并到单个声明中

6.4.6. 限制

6.4.7. 内存类型

6.4.8. 表类型

6.4.9. 全局类型

6.5. 指令

指令在语法上分为简单指令和结构化指令。

此外,作为语法上的简写,指令可以用 折叠 形式的 S 表达式来编写,以便在视觉上进行分组。

6.5.1. 标签

结构化控制指令 可以用符号化的 标签标识符 进行注释。它们是唯一可以在指令序列中局部绑定的 符号标识符。以下语法通过 组合 上下文和额外的标签条目来处理对 标识符上下文 的相应更新。

注意

在标识符上下文中,新的标签条目被插入标签列表的开头。这实际上将所有现有标签向上移动一位,反映了控制指令是相对索引而不是绝对索引的事实。

如果已经存在具有相同名称的标签,则它会被遮蔽,并且先前标签将无法访问。

6.5.2. 控制指令

结构化控制指令 可以绑定一个可选的符号 标签标识符。相同的标签标识符可以在相应的 伪指令后重复,以指示匹配的分隔符。

它们的 块类型 被指定为 类型使用,类似于 函数 的类型。但是,语法为空或仅包含单个 结果 的类型使用情况不被视为 缩写 用于内联 函数类型,而是直接解析为可选的 值类型

注意

对于 块类型的规则中,边条件说明 标识符上下文 必须仅包含未命名的条目,这强制了在任何 块类型的声明中都不能绑定标识符。

所有其他控制指令都按原样表示。

注意

对于 的规则中,边条件说明 标识符上下文 必须仅包含未命名的条目,这强制了在类型注释中出现的任何 声明中都不能绑定标识符。

6.5.2.1. 缩写

指令的关键字可以省略,如果后面的指令序列为空。

此外,为了向后兼容性,可以省略到 的表格索引,默认为 .

6.5.3. 引用指令

6.5.4. 参数化指令

6.5.5. 变量指令

6.5.6. 表指令

6.5.6.1. 缩写

为了向后兼容,所有表索引 都可以从表指令中省略,默认值是 .

6.5.7. 内存指令

内存指令的偏移量和对齐立即数是可选的。偏移量默认为 ,对齐为相应内存访问的存储大小,即其自然对齐。从语法上讲,一个短语被认为是一个单独的关键字标记,因此不允许在周围使用空白

6.5.8. 数值指令

6.5.9. 向量指令

向量内存指令具有可选的偏移量和对齐方式立即数,类似于内存指令

向量常量指令具有必需的形状 描述符,用于确定如何解析后续的值。

6.5.10. 折叠指令

指令可以写成 S-表达式,通过将它们分组为“折叠”形式。在这个表示法中,指令用圆括号括起来,并可以包含嵌套的折叠指令来表示其操作数。

块指令 的情况下,折叠形式省略了 分隔符。对于 指令,两个分支都必须用嵌套的 S-表达式括起来,并以关键字 开头。

由以下缩写递归定义的所有短语集合构成了辅助句法类 。这种折叠指令可以出现在任何常规指令可以出现的地方。

注意

例如,指令序列

可以折叠成

折叠指令仅仅是语法糖,不暗示额外的语法或基于类型的检查。

6.5.11. 表达式

表达式以指令序列的形式编写。没有显式包含 关键字,因为它们只出现在方括号的位置。

6.6. 模块

6.6.1. 索引

索引 可以以原始数字形式或作为符号 标识符 给出,当被相应构造绑定时。这些标识符会在合适的 标识符上下文 空间中查找。

6.6.2. 类型

类型定义可以绑定符号 类型标识符

6.6.3. 类型使用

类型使用是对 类型定义 的引用。它可以选择性地通过内联的 参数结果 声明进行增强。这允许绑定符号 标识符 来命名参数的 局部索引。如果给出内联声明,则它们的类型必须与引用的 函数类型 匹配。

的合成属性是一个对,它包含使用的 类型索引 和包含可能的参数标识符的局部 标识符上下文。以下辅助函数从参数中提取可选标识符

注意

两种生成方式在函数类型为 时重叠。但是,在这种情况下,它们也会产生相同的结果,因此选择无关紧要。

关于 良好格式 条件确保参数不包含重复的标识符。

6.6.3.1. 缩写

一个 也可以完全由内联的 参数结果 声明替换。在这种情况下,会自动插入一个 类型索引

其中 是当前模块中定义的最小现存 类型索引,其定义为 函数类型 。如果不存在此类索引,则会插入一个新的 类型定义,其形式为

插入到模块的末尾。

缩写按其出现的顺序扩展,因此先前插入的类型定义会被连续的扩展所重用。

6.6.4. 导入

导入中的描述符可以绑定符号函数、表格、内存或全局 标识符

6.6.4.1. 缩写

作为缩写,导入也可以与 函数表格内存全局 定义内联指定;请参见相应的章节。

6.6.5. 函数

函数定义可以绑定一个符号化的 函数标识符,以及 局部标识符,分别用于其 参数 和局部变量。

局部 标识符上下文 的定义使用以下辅助函数来从局部变量中提取可选标识符

注意

关于 良构性条件确保参数和局部变量不包含重复的标识符。

6.6.5.1. 缩写

多个匿名局部变量可以合并成单个声明。

函数可以定义为导入导出内联。

注意

如果“”包含额外的导出子句,则后一种缩写可以重复应用。因此,函数声明可以包含任意数量的导出,可能后面跟着一个导入。

6.6.6. 表格

表格定义可以绑定一个符号表格标识符

6.6.6.1. 缩写

一个元素段可以与表格定义一起内联给出,在这种情况下,它的偏移量为,并且限制表格类型中从给定段的长度推断出来。

表格可以定义为 导入导出 内联

注意

如果“”包含额外的导出子句,则后一个缩写可以重复使用。因此,表格声明可以包含任意数量的导出,后面可能紧跟着导入。

6.6.7. 内存

内存定义可以绑定符号 内存标识符

6.6.7.1. 缩写

一个数据段可以在内存定义中内联给出,在这种情况下,它的偏移量为,并且限制内存类型从数据的长度推断,向上取整到页面大小

内存可以被定义为 导入 或者 导出 内联

注意

如果“” 包含额外的导出子句,则后一种缩写可以重复使用。因此,内存声明可以包含任意数量的导出,后面可能跟着一个导入。

6.6.8. 全局变量

全局定义可以绑定一个符号化的 全局标识符.

6.6.8.1. 缩写

全局变量可以定义为 导入导出 内联

注意

如果“”包含额外的导出子句,则后一种缩写可以重复应用。因此,全局声明可以包含任意数量的导出,之后可以跟着一个导入。

6.6.9. 导出

导出的语法直接反映了它们的 抽象语法

6.6.9.1. 缩写

作为缩写,导出也可以与函数表格内存全局定义内联;请参见各自部分。

6.6.10. 开始函数

一个开始函数是根据其索引定义的。

注意

在模块中最多只能出现一个启动函数,这可以通过在语法上设置适当的辅助条件来确保。

6.6.11. 元素段

元素段允许使用可选的表格索引来标识要初始化的表格。

6.6.11.1. 缩写

作为缩写,单个指令可以替代活动元素段的偏移量或作为元素表达式。

此外,元素列表可以写成一系列函数索引

表格使用可以省略,默认值为。此外,为了向后兼容 WebAssembly 的早期版本,如果省略了表格使用,则关键字也可以省略。

作为另一种缩写形式,元素段也可以在 表格 定义中内联指定;请参阅相关部分。

6.6.12. 数据段

数据段允许使用可选的 内存索引 来标识要初始化的内存。数据以 字符串 形式写入,该字符串可以拆分为可能为空的单个字符串字面量的序列。

注意

在当前版本的 WebAssembly 中,唯一有效的内存索引是 0 或解析为相同值的符号 内存标识符

6.6.12.1. 缩写

作为一种缩写,单个指令可以出现在活动数据段的偏移量位置。

此外,可以省略内存使用,默认值为

作为另一种缩写,数据段也可以与 内存 定义一起内联指定;请参阅相应的章节。

6.6.13. 模块

模块由一系列字段组成,这些字段可以按任何顺序出现。所有定义及其相应的绑定 标识符 作用域涵盖整个模块,包括它们之前的文本。

模块可以选择绑定一个 标识符 来命名模块。该名称仅起文档作用。

注意

工具可能会在 名称段 中包含模块名称 二进制格式

模块 的组成施加以下限制: 当且仅当以下条件成立时定义:

注意

第一个条件确保最多只有一个启动函数。第二个条件强制所有 导入 必须出现在任何 函数表格内存全局 的常规定义之前,从而保持相应 索引空间 的顺序。

良好格式 条件 语法中确保没有命名空间包含重复标识符。

初始 标识符上下文 的定义使用以下辅助定义,该定义将每个相关定义映射到一个具有一个(可能是空的)标识符的单一上下文

6.6.13.1. 缩写

在源文件中,围绕模块主体的顶级 可以省略。

附录 A

A.1 嵌入

WebAssembly 实现通常会嵌入宿主环境中。嵌入器实现宿主环境与本规范主体定义的 WebAssembly 语义之间的连接。预计嵌入器将以定义明确的方式与语义交互。

本节以嵌入器可以通过其访问 WebAssembly 语义的入口点的形式定义一个合适的接口。该接口旨在完整,这意味着嵌入器不需要直接引用 WebAssembly 规范的其他功能部分。

注意

另一方面,嵌入器不需要为宿主环境提供访问此接口中定义的所有功能。例如,实现可能不支持 解析 文本格式

类型

在嵌入器接口的描述中,来自 抽象语法运行时抽象机 的语法类用作变量的名称,这些变量范围涵盖该类中的所有可能对象。因此,这些语法类也可以解释为类型。

对于数字参数,使用类似于 的符号来指定符号名称,以及相应的取值范围。

错误

接口操作失败由辅助语法类指示

除了本节中明确指定的错误条件外,当达到特定的 实现限制 时,实现也可能返回错误。

注意

根据此定义,错误是抽象且不具体的。实现可以将其细化以携带合适的分类和诊断消息。

先决条件和后置条件

某些操作会声明其参数的先决条件或其结果的后置条件。满足先决条件是嵌入器的责任。如果满足,则语义将保证后置条件。

除了每个操作中明确说明的先决条件和后置条件之外,规范还采用了以下约定,用于 运行时对象 (, , , 地址)

  • 作为参数传递的每个运行时对象都必须根据隐式先决条件是 有效的

  • 作为结果返回的每个运行时对象都必须根据隐式后置条件是 有效的

注意

只要嵌入器将运行时对象视为抽象,并且仅通过此处定义的接口创建和操作它们,所有隐式先决条件都会自动满足。

存储

  1. 返回空 存储

模块

  1. 如果存在一个推导,根据 模块的二进制语法,将 字节 序列 推导出一个 模块 ,则返回

  2. 否则,返回 .

  1. 如果存在一个推导,根据 源代码 作为 ,根据 模块的文本语法,推导出一个 模块 ,则返回

  2. 否则,返回 .

  1. 如果 有效的,则返回空值。

  2. 否则,返回 .

  1. 尝试 实例化 中,并使用 外部值 作为导入。

  1. 如果它成功地生成了一个 模块实例 ,则将 设置为 .

  2. 否则,将 设置为 .

  1. 返回新的 store 和 的组合。

注意

即使在发生错误的情况下,存储也可能会被修改。

  1. 先决条件: 有效的,其外部导入类型为 ,外部导出类型为 .

  2. 导入 .

  3. 断言: 的长度等于 .

  4. 对于每个 中以及相应的 中,执行以下操作:

  1. 为三元组 .

  1. 返回所有 的连接,按索引顺序。

  2. 后置条件:每个 有效的.

  1. 先决条件: 有效的,其外部导入类型为 ,外部导出类型为 .

  2. 导出 .

  3. 断言: 的长度等于 的长度。

  4. 对于 中的每个 以及 中对应的 ,进行以下操作:

  1. 为一对 .

  1. 返回所有 的连接,按索引顺序。

  2. 后置条件:每个 都是 有效的

模块实例

  1. 断言:由于 模块实例 有效性,所有其 导出名称 都是不同的。

  2. 如果在 中存在一个 ,使得 name 等于 ,那么

    1. 返回 外部值 .

  3. 否则,返回 .

函数

  1. 前提条件:有效的.

  2. 为在 中使用 函数类型 和主机函数代码 分配主机函数 的结果。

  3. 返回与 配对的新存储。

注意

此操作假设 满足 预置和后置条件,这些条件是类型为 的函数实例所需的。

常规(非主机)函数实例只能通过 模块实例化 间接创建。

  1. 返回 .

  2. 后置条件:返回的 函数类型有效的

  1. 尝试 调用 函数 中,使用 作为参数。

  1. 如果它成功地使用 作为结果,则让 .

  2. 否则它已捕获,因此让 .

  1. 返回新的 store 和 的组合。

注意

即使在发生错误的情况下,存储也可能会被修改。

  1. 前提条件: 有效。

  2. 为在 中分配一个类型为 的表的结果,并初始化值为 .

  3. 返回与 配对的新存储。

  1. 返回 .

  2. 后置条件:返回的 表格类型有效的.

  1. 表格实例 .

  2. 如果 大于或等于 的长度,则返回 .

  3. 否则,返回 引用值 .

  1. 表格实例 .

  2. 如果 大于或等于 的长度,则返回 .

  3. 替换为 引用值 .

  4. 返回更新后的存储。

  1. 返回 的长度。

  1. 尝试 扩展 表格实例 ,扩展 个元素,初始化值为

    1. 如果成功,返回更新后的存储区。

    2. 否则,返回 .

内存

  1. 先决条件: 有效的

  2. 为在 分配内存 的结果,内存类型为 .

  3. 返回与 配对的新存储。

  1. 返回 .

  2. 后置条件:返回的 内存类型有效的.

  1. 内存实例 .

  2. 如果 大于或等于 的长度,则返回 .

  3. 否则,返回 字节 .

  1. 内存实例 .

  2. 如果 大于或等于 的长度,则返回 .

  3. 替换 .

  4. 返回更新后的存储。

  1. 返回 的长度除以 页大小.

  1. 尝试 增长 内存实例

    1. 如果成功,返回更新后的存储区。

    2. 否则,返回 .

全局变量

  1. 前提条件: 有效的.

  2. 为在 分配全局变量 的结果,使用 全局变量类型 和初始化值 .

  3. 返回与 配对的新存储。

  1. 返回 .

  2. 后置条件:返回的 全局类型有效的.

  1. 全局实例 .

  2. 返回 .

  1. 全局实例 .

  2. 全局类型 的结构。

  3. 如果 不是 , 则返回 .

  4. 替换 .

  5. 返回更新后的存储。

A.2 实现限制

实现通常会对 WebAssembly 模块或执行的多个方面施加额外的限制。这些限制可能源于

  • 物理资源限制,

  • 嵌入器或其环境施加的约束,

  • 所选实现策略的限制。

本节列出了允许的限制。如果限制采用数值限制的形式,则不会给出最小要求,也不会假设限制是具体的、固定的数字。但是,预计所有实现都具有“合理”的较大限制,以使常见应用程序能够正常运行。

注意

符合标准的实现不允许省略单个特性。但是,将来可能会指定 WebAssembly 的指定子集。

语法限制

结构

实现可能会对模块的以下维度施加限制

如果实现的限制对于给定的模块被超过,则实现可能会拒绝 验证、编译或 实例化 该模块,并返回嵌入器特定的错误。

注意

最后一个项目允许在没有 [UNICODE] 支持的有限环境中运行的 嵌入器导入导出 的名称限制为常见的子集,例如 ASCII

二进制格式

对于以 二进制格式 给出的模块,可能会对以下维度施加额外的限制

文本格式

对于以 文本格式 给出的模块,可能会对以下维度施加额外的限制

验证

实现可以延迟对单个 函数验证,直到它们第一次被 调用

如果一个函数被发现无效,那么调用以及对同一个函数的每次连续调用都会导致 陷阱

注意

这是为了允许实现对函数使用解释或即时编译。在执行函数体之前,函数必须完全验证。

执行

在执行 WebAssembly 程序时,可能会对以下维度施加限制

如果在执行计算过程中超过了实现的运行时限制,那么它可能会终止该计算,并向调用代码报告嵌入器特定的错误。

以上某些限制可能已在实例化期间得到验证,在这种情况下,实现可能会以与 语法限制 相同的方式报告超出限制。

注意

具体的限制通常不是固定的,但可能取决于细节,相互依赖,随时间变化,或取决于其他实现或嵌入器特定的情况或事件。

A.3 验证算法

WebAssembly 验证 的规范纯粹是声明式的。它描述了 模块指令 序列必须满足的约束才能有效。

本节概述了有效验证代码(即 指令 序列)的算法的框架。(验证的其他方面很容易实现。)

事实上,该算法是在 二进制格式 中出现的操作码的扁平序列上表达的,并且只对其进行单次遍历。因此,它可以直接集成到解码器中。

该算法是用类型化伪代码表达的,其语义旨在不言自明。

数据结构

类型可以表示为枚举。

type val_type = I32 | I64 | F32 | F64 | V128 | Funcref | Externref
func is_num(t : val_type | Unknown) : bool =
  return t = I32 || t = I64 || t = F32 || t = F64 || t = Unknown

func is_vec(t : val_type | Unknown) : bool =
  return t = V128 || t = Unknown

func is_ref(t : val_type | Unknown) : bool =
  return t = Funcref || t = Externref || t = Unknown

该算法使用两个独立的堆栈:值堆栈控制堆栈。前者跟踪堆栈上操作数值的 类型,后者围绕 结构化控制指令 及其相关的

type val_stack = stack(val_type | Unknown)
type ctrl_stack = stack(ctrl_frame)
type ctrl_frame = {
  opcode : opcode
  start_types : list(val_type)
  end_types : list(val_type)
  height : nat
  unreachable : bool
}

对于每个值,值堆栈记录其 值类型,或者当类型未知时记录 Unknown

对于每个进入的块,控制堆栈记录一个控制帧,其中包含源操作码、块开始和结束时操作数堆栈顶部的类型(用于检查其结果以及分支)、块开始时操作数堆栈的高度(用于检查操作数是否未达到当前块的底部)、以及记录块的剩余部分是否不可到达的标志(用于处理分支后的 堆栈多态 类型)。

为了展示算法,操作数堆栈和控制堆栈只是作为全局变量维护的

var vals : val_stackvar ctrls : ctrl_stack

但是,这些变量不是由主检查函数直接操作的,而是通过一组辅助函数操作的

func push_val(type : val_type | Unknown) =  vals.push(type)

func pop_val() : val_type | Unknown =
  if (vals.size() = ctrls[0].height && ctrls[0].unreachable) return Unknown
  error_if(vals.size() = ctrls[0].height)
  return vals.pop()

func pop_val(expect : val_type | Unknown) : val_type | Unknown =
  let actual = pop_val()
  error_if(actual =/= expect && actual =/= Unknown && expect =/= Unknown)
  return actual

func push_vals(types : list(val_type)) = foreach (t in types) push_val(t)
func pop_vals(types : list(val_type)) : list(val_type) =
  var popped := []
  foreach (t in reverse(types)) popped.prepend(pop_val(t))
  return popped

推送一个操作数值只是将相应的类型推送到值堆栈。

弹出操作数值会检查值堆栈是否未达到当前块的底部,然后删除一个类型。但首先,会处理一个特殊情况,即块不包含任何已知值,但已被标记为不可到达。这可能在无条件分支后发生,当堆栈以 多态 的方式进行类型化时。在这种情况下,将返回未知类型。

另一个弹出操作数值的函数接受一个预期类型,将实际操作数类型与之进行检查。如果其中一个类型是 Unknown,那么类型可能会不同。该函数返回从堆栈中弹出的实际类型。

最后,还有一些累积函数用于推送或弹出多个操作数类型。

注意

符号 stack[i] 表示从顶部索引堆栈,例如,ctrls[0] 访问最后推送的元素。

控制堆栈也通过辅助函数进行操作

func push_ctrl(opcode : opcode, in : list(val_type), out : list(val_type)) =  let frame = ctrl_frame(opcode, in, out, vals.size(), false)
  ctrls.push(frame)
  push_vals(in)

func pop_ctrl() : ctrl_frame =
  error_if(ctrls.is_empty())
  let frame = ctrls[0]
  pop_vals(frame.end_types)
  error_if(vals.size() =/= frame.height)
  ctrls.pop()
  return frame

func label_types(frame : ctrl_frame) : list(val_types) =
  return (if frame.opcode == loop then frame.start_types else frame.end_types)

func unreachable() =
  vals.resize(ctrls[0].height)
  ctrls[0].unreachable := true

推送控制帧会获取标签和结果值的类型。它分配一个新的帧记录来记录它们以及操作数堆栈的当前高度,并标记该块为可到达。

弹出帧首先检查控制堆栈是否为空。然后,它会验证操作数堆栈是否包含在退出块结束时预期的正确类型的值,并将它们从操作数堆栈中弹出。之后,它会检查堆栈是否缩回其初始高度。

与控制帧关联的 标签 的类型是帧开始或结束时堆栈的类型,由它起源的操作码决定。

最后,可以将当前帧标记为不可到达。在这种情况下,所有现有的操作数类型都会从值堆栈中清除,以便在 pop_val 中的 堆栈多态 逻辑生效。由于每个函数都有一个隐式最外层标签,对应于一个隐式块帧,因此验证算法的一个不变式是在验证指令时控制堆栈上始终至少存在一个帧,因此,ctrls[0] 始终定义。

注意

即使设置了不可到达标志,连续的操作数仍会被推送到操作数堆栈和从操作数堆栈中弹出。这是为了检测无效的 示例,例如 . 但是,多态堆栈不能出现下溢,而是根据需要生成 Unknown 类型。

操作码序列的验证

以下函数展示了对操作堆栈的几个代表性指令的验证。其他指令以类似的方式进行检查。

注意

此处未显示的各种指令还需要验证 上下文 来检查 索引 的使用情况。这是一个简单的添加,因此在本次展示中省略了。

func validate(opcode) =switch (opcode)
  case (i32.add)
    pop_val(I32)
    pop_val(I32)
    push_val(I32)

  case (drop)
    pop_val()

  case (select)
    pop_val(I32)
    let t1 = pop_val()
    let t2 = pop_val()
    error_if(not ((is_num(t1) && is_num(t2)) || (is_vec(t1) && is_vec(t2))))
    error_if(t1 =/= t2 && t1 =/= Unknown && t2 =/= Unknown)
    push_val(if (t1 = Unknown) t2 else t1)

  case (select t)
    pop_val(I32)
    pop_val(t)
    pop_val(t)
    push_val(t)

  case (unreachable)
    unreachable()

  case (block t1*->t2*)
    pop_vals([t1*])
    push_ctrl(block, [t1*], [t2*])

  case (loop t1*->t2*)
    pop_vals([t1*])
    push_ctrl(loop, [t1*], [t2*])

  case (if t1*->t2*)
    pop_val(I32)
    pop_vals([t1*])
    push_ctrl(if, [t1*], [t2*])

  case (end)
    let frame = pop_ctrl()
    push_vals(frame.end_types)

  case (else)
    let frame = pop_ctrl()
    error_if(frame.opcode =/= if)
    push_ctrl(else, frame.start_types, frame.end_types)

  case (br n)
    error_if(ctrls.size() < n)
    pop_vals(label_types(ctrls[n]))
    unreachable()

  case (br_if n)
    error_if(ctrls.size() < n)
    pop_val(I32)
    pop_vals(label_types(ctrls[n]))
    push_vals(label_types(ctrls[n]))

  case (br_table n* m)
    pop_val(I32)
    error_if(ctrls.size() < m)
    let arity = label_types(ctrls[m]).size()
    foreach (n in n*)
      error_if(ctrls.size() < n)
      error_if(label_types(ctrls[n]).size() =/= arity)
      push_vals(pop_vals(label_types(ctrls[n])))
    pop_vals(label_types(ctrls[m]))
    unreachable()

注意

在当前 WebAssembly 指令集中,Unknown 类型的操作数永远不会在堆栈上被复制。如果该语言扩展了类似 dup 的堆栈指令,这种情况将会改变。在这样的扩展下,上述算法需要通过用适当的 *类型变量* 替换 Unknown 类型来进行细化,以确保所有使用都是一致的。

A.4 自定义节

本附录定义了 WebAssembly 的 二进制格式 中的专用 自定义节。这些节不会影响或改变 WebAssembly 语义,并且像任何自定义节一样,它们可以被实现忽略。但是,它们提供有用的元数据,实现可以使用这些元数据来改善用户体验或获取编译提示。

目前,只定义了一个专用自定义节,即 名称节

名称节

名称节 是一个 自定义节,其名称字符串本身为 . 名称节在模块中应该只出现一次,并且应该出现在 数据节 之后。

本节的目的是将可打印的名称附加到模块中的定义,例如,调试器或模块的某些部分需要以 文本形式 呈现时可以使用这些名称。

注意

所有 名称 都以 [UNICODE] 编码为 UTF-8 表示。名称不需要唯一。

子节

名称节的 数据 由一系列 *子节* 组成。每个子节包含:

  • 一个字节的子节 *标识符*,

  • 以字节为单位的内容的大小

  • 实际的 *内容*,其结构取决于子节标识符。

使用以下子节标识符:

ID

子节

0

模块名称

1

函数名称

2

本地名称

每个子节最多只能出现一次,并且按标识符递增的顺序排列。

名称映射

名称映射名称 映射到给定 索引空间 中的 索引。它由一个 向量 组成,其中包含按索引值递增顺序排列的索引/名称对。每个索引必须是唯一的,但分配的名称不需要唯一。

间接名称映射名称分配给二维的索引空间,其中次级索引按主索引进行分组。 它包含按升序索引值的向量形式的主索引/名称映射对,其中每个名称映射又将次级索引映射到名称。 每个主索引必须是唯一的,同样每个次级索引对于单个名称映射也是唯一的。

模块名称

模块名称小节的 ID 为 0。 它仅仅包含单个分配给模块本身的名称

函数名称

函数名称小节的 ID 为 1。 它包含将函数名称分配给函数索引名称映射

局部名称

局部名称小节的 ID 为 2。 它包含将局部名称分配给按函数索引分组的局部索引间接名称映射

A.5 健壮性

WebAssembly 的类型系统健壮的,这意味着它在 WebAssembly 语义方面既是类型安全的,又是内存安全的。 例如

  • 在运行时,验证期间声明和推导的所有类型都将被遵守;例如,每个局部全局变量将只包含类型正确的取值,每个指令只应用于预期类型的操作数,以及每个函数调用始终计算为正确类型的结果(如果它不会陷入或发散)。

  • 除了由程序明确定义的内存位置外,不会读取或写入任何内存位置,即作为局部变量全局变量表格中的元素,或线性内存中的位置。

  • 不存在未定义行为,即执行规则涵盖了有效程序中可能出现的各种情况,且这些规则相互一致。

健全性也有助于确保其他属性,最值得注意的是函数和模块作用域的封装:任何局部变量都无法在其自身函数之外访问,任何模块组件都无法在其自身模块之外访问,除非它们被明确地导出导入

定义 WebAssembly 验证的类型规则仅涵盖 WebAssembly 程序的静态组件。为了准确地陈述和证明健全性,类型规则必须扩展到抽象运行时动态组件,即存储配置管理指令[1]

结果

结果可以通过结果类型进行如下分类。

结果
结果

存储有效性

以下键入规则指定了运行时 存储 何时为有效。有效存储必须包含相对于 本身有效的 函数表格内存全局模块 实例。

为此,每种实例都由相应的 函数表格内存全局 类型分类。模块实例由模块上下文分类,它们是将常规 上下文 重新用作模块类型,用于描述模块定义的 索引空间

存储
函数实例
宿主函数实例

注意

这条规则表明,如果满足关于存储和参数的适当先决条件,那么执行宿主函数必须满足关于存储和结果的适当后置条件。这些后置条件与在 执行规则 中调用宿主函数的后置条件相匹配。

任何函数被调用的存储都被假定为当前存储的扩展。这样,函数本身就能够对未来存储做出足够的假设。

表实例
内存实例
全局实例
元素实例
数据实例
  • 该数据实例是有效的。

导出实例
模块实例

配置有效性

为了将 WebAssembly 的 类型系统 与其 执行语义 联系起来,必须将 指令的类型规则 扩展到 配置 , 它将 存储 与执行 线程 相关联。

配置和线程按其结果类型进行分类。除了存储外,线程还根据返回类型进行类型化,该类型控制是否以及用哪种类型允许指令。除了管理指令中的指令序列之外,此类型不存在()。

最后,通过帧上下文进行分类,帧上下文扩展了帧关联的模块上下文模块实例,包括帧包含的局部变量

配置
线程
Frames

管理指令

管理指令的类型规则如下所示。除了上下文 之外,这些指令的类型还根据给定的存储 定义。为此,所有之前的类型判断被推广为包含存储,如 ,通过隐式地将添加到所有规则中 - 从未被预先存在的规则修改,但它在下面给出的管理指令的额外规则中被访问。

  • 该指令的类型为, 对于任何值类型序列.

  • 指令序列 必须是 有效的,类型为 .

  • 相同,但 上下文结果类型 被添加到 向量中。

  • 在上下文 下,指令序列 必须是 有效的,类型为 .

  • 然后,复合指令是有效的,类型为 .

  • 在返回值类型 下,线程 必须是 有效的,且具有返回值类型 .

  • 然后该复合指令的类型为 的有效指令。

存储扩展

程序可以修改 存储 及其包含的实例。任何此类修改必须遵守某些不变性,例如不删除分配的实例或更改不可变定义。尽管这些不变性是 WebAssembly 指令模块 的执行语义所固有的,但 宿主函数 并不会自动遵守它们。因此,必须将所需的变量声明为对 调用 宿主函数的显式约束。只有当 嵌入器 确保这些约束时,健壮性才成立。

必要的约束由存储扩展的概念来编码:当以下规则成立时,存储状态 扩展状态 , 写作 .

注意

扩展并不意味着新存储有效,有效性是单独定义的 以上.

存储
函数实例
  • 函数实例必须保持不变。

表实例
内存实例
全局实例
元素实例
  • 向量 必须保持不变或缩短到长度

数据实例
  • 向量 必须保持不变或缩短至长度 .

定理

根据 有效配置 的定义,标准的健全性定理成立。 [2] [3]

定理(保持性)。 如果一个 配置 有效的,其 结果类型(即,),并且经过一系列步骤变为 (即,),那么 也是一个有效的配置,并且具有相同的结果类型(即,)。此外,扩展(即,)。

一个终止的 线程 是其 指令 序列为一个 结果 的线程。一个终止配置是其线程终止的配置。

定理(进展性)。 如果一个 配置 有效的(即, 对于一些 结果类型 ),那么它要么是终止的,要么可以经过一步变为某个配置 (即,)。

从保持性和进展性可以直接得出 WebAssembly 类型系统的健全性。

推论(健全性)。如果一个 配置 有效的(即, 对于某个 结果类型 ),那么它要么会发散,要么会在有限步内到达一个终端配置 (即,)是有效的,并具有相同的结果类型(即,),并且 扩展(即,)。

换句话说,有效配置中的每个线程要么永远运行,要么陷入陷阱,要么以预期类型的结果终止。因此,给定一个 有效存储,由 实例化调用 有效模块定义的计算不会“崩溃”,也不会以本规范中未涵盖的方式出现其他(错误)行为。

变更历史记录

自从 WebAssembly 规范最初发布 1.0 版以来,已经整合了许多扩展建议。以下部分概述了发生了哪些变化。

版本 2.0

符号扩展指令

添加了用于在整数表示中执行符号扩展的新数值指令 [1]

非陷阱浮点数到整数转换

添加了新的转换指令,这些指令在将浮点数转换为整数时避免陷阱 [2]

多值

将块和函数的结果类型推广以允许使用多值;此外,引入了具有块参数的能力 [3]

引用类型

添加了 作为新的值类型以及相应的指令 [4]

表格指令

新增直接访问和修改表格的指令 [4].

多个表格

新增了每个模块使用多个表格的功能 [4].

批量内存和表格指令

新增修改内存或表格条目范围的指令 [4] [5]

向量指令

新增向量类型和指令,可并行处理多个数值(也称为 _SIMD_,单指令多数据) [6]

  • 新增 值类型

  • 新增 内存指令

  • 新增常量 向量指令:

  • 新增一元 向量指令: , , , , , , , , , ,

  • New binary vector instructions: , , , , , , , , , , , , , , , , , , , , , , , , , , ,

  • 新增三元 向量指令:

  • 新增测试 向量指令: ,

  • 新的关系型 向量指令, , , , , , , , , , ,

  • 新的转换 向量指令, , , , ,

  • 新的通道访问 向量指令, , ,

  • 新的通道分割/合并 向量指令, ,

  • 新的字节重排序 向量指令: ,

  • 新的注入/投影 向量指令: , ,

A.6 类型索引

类别

构造函数

二进制操作码

类型索引

(正数,作为 )

数值类型

(-1,作为 )

数值类型

(-2,作为 )

数值类型

(-3,作为 )

数值类型

(-4,作为 )

向量类型

(-5,作为 )

(保留)

..

引用类型

(-16,作为 )

引用类型

(-17,作为 )

(保留)

..

函数类型

(-32,作为 )

(保留)

..

结果类型

(-64,作为 )

表格类型

(无)

内存类型

(无)

全局类型

(无)

A.7 指令索引

指令

二进制操作码

类型

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

(保留)

(保留)

(保留)

(保留)

(保留)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

验证

执行

验证

执行

验证

执行

(保留)

(保留)

(保留)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

(保留)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

验证

执行

验证

执行

验证

执行

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

(保留)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行

验证

执行

验证

执行

验证

执行

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (操作符)

验证

执行 (运算符)

注意

表中给出了多字节操作码的最短编码。但是,第一个字节后面的内容实际上是使用可变长度编码的u32,因此有多种可能的表示形式。

A.8 语义规则索引

静态结构的类型

结构

判断

限制

函数类型

块类型

表格类型

内存类型

全局类型

外部类型

指令

指令序列

表达式

函数

内存

全局

元素段

元素模式

数据段

数据模式

启动函数

导出

导出描述

导入

导入描述

模块

运行时结构类型

结构

判断

结果

外部值

函数实例

表格实例

内存实例

全局实例

元素实例

数据实例

导出实例

模块实例

存储

配置

线程

常量性

结构

判断

常量表达式

常量指令

匹配

结构

判断

外部类型

限制

存储扩展

结构

判断

函数实例

表格实例

内存实例

全局实例

元素实例

数据实例

存储

执行

结构

判断

指令

表达式

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语的组合来表达。本规范规范性部分中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。然而,为了易读性,这些词语在本规范中不全部使用大写字母。

本规范的所有文本均为规范性文本,除非明确标记为非规范性文本、示例或注释。[RFC2119]

本规范中的示例以“例如”开头,或与规范性文本分开设置,使用class="example",如下所示

这是一个信息性示例。

信息性注释以“注释”开头,并与规范性文本分开设置,使用class="note",如下所示

注意,这是一条提示性说明。

符合标准的算法

算法中以命令式形式表述的要求(例如“去除任何前导空格”或“返回 false 并中止这些步骤”)应根据用于引入算法的关键词(“必须”,“应该”,“可以”等)的含义来解释。

以算法或具体步骤形式表述的符合性要求可以通过任何方式实现,只要最终结果等效即可。特别是,本规范中定义的算法旨在易于理解,并非旨在提高性能。鼓励实现者进行优化。

参考

规范性参考

[IEEE-754-2019]
IEEE 浮点数算术标准. 2008年8月29日. URL: http://ieeexplore.ieee.org/servlet/opac?punumber=4610933
[RFC2119]
S. Bradner. RFC 中用于指示要求级别的关键词. 1997年3月. 最佳实践. URL: https://datatracker.ietf.org/doc/html/rfc2119
[UNICODE]
Unicode 标准. URL: https://www.unicode.org/versions/latest/