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. 为要分配的函数,而 为其模块实例。