介绍
WebAssembly(wasm):safe,portable,low-level code format,专为高效执行和紧凑表示而设计。
它的主要目标是在 Web 上启用高性能应用程序,但它没有做出任何特定于 Web 的假设或提供特定于 Web 的功能,因此它也可以在其他环境中使用。
WebAssembly 是由 W3C 社区团体开发的开放标准。本文档介绍了核心 WebAssembly 标准的 2.0 版(草案 2023-12-01)。预计它将被未来具有附加功能的新增量版本所取代。
设计目标
- Fast, safe, and portable semantics:
- 快速:以接近本机代码的性能执行,利用所有当代硬件的通用功能。
- 安全:代码经过验证并在内存安全、沙盒环境中执行,防止数据损坏或安全漏洞。
- 明确定义:以一种易于非正式和正式推理的方式完整而精确地定义有效的程序及其行为。
- 独立于硬件:可以在所有现代架构、桌面或移动设备以及嵌入式系统上进行编译。
- 与语言无关:不特权任何特定语言、编程模型或对象模型。
- 平台无关:可以嵌入浏览器中,作为独立虚拟机运行,或集成在其他环境中。
- 开放:程序可以以简单且通用的方式与其环境进行互操作。
- 高效且可移植的表示:
- 紧凑:比典型文本或本机代码格式更小,传输速度更快的二进制格式。
- 模块化:程序可以分成更小的部分,可以单独传输、缓存和使用。
- 高效:可以在 a single pass 中进行解码、验证和编译,与即时 (JIT) 或提前 (AOT) 编译相同。
- 可流式传输:允许在看到所有数据之前尽快开始解码、验证和编译。
- 可并行化:允许将解码、验证和编译拆分为许多独立的并行任务。
- 可移植:不做出现代硬件未广泛支持的架构假设。
WebAssembly 代码还旨在易于检查和调试,尤其是在 Web 浏览器等环境中,但此类功能超出了本规范的范围。
Scope
WebAssembly 的核心是虚拟指令集架构(虚拟 ISA)。因此,它有许多用例,并且可以嵌入到许多不同的环境中。 为了涵盖其多样性并实现最大程度的重用,WebAssembly 规范被拆分并分层为多个文档。
本文档涉及 WebAssembly 的核心 ISA 层。 它定义了指令集、二进制编码、验证和执行语义,以及文本表示。 然而,它没有定义 WebAssembly 程序如何与它们执行的特定环境交互,也没有定义如何从这样的环境中调用它们。
相反,该规范由定义特定嵌入环境(例如 Web)接口的附加文档来补充。 它们各自定义适合给定环境的 WebAssembly 应用程序编程接口 (API)。
安全考虑
WebAssembly 不提供对执行代码的计算环境的环境访问。 与环境的任何交互,例如 I/O、对资源的访问或操作系统调用,只能通过调用嵌入器(embedder)提供并导入到 WebAssembly 模块中的函数来执行。 嵌入器可以通过控制或限制可导入的功能来建立适合各自环境的安全策略。 这些考虑因素是嵌入者的责任,也是特定环境的 API 定义的主题。 由于 WebAssembly 被设计为可转换为直接在主机硬件上运行的机器代码,因此它可能容易受到硬件级别的侧通道攻击。 在存在此问题的环境中,嵌入器可能必须采取适当的缓解措施来隔离 WebAssembly 计算。
依赖
WebAssembly 依赖于两个现有标准:
- IEEE 7544,用于浮点数据的表示和相应数字运算的语义。
- Unicode5,用于表示导入/导出名称和文本格式。
然而,为了使本规范自成一体,上述标准的相关方面被定义并形式化为本规范的一部分,例如浮点值的二进制表示和舍入,以及 Unicode 的值范围和 UTF-8 字符编码。
注:上述标准是所有相关定义的权威来源。 本规范中给出的形式化旨在匹配这些定义。 所描述的语法或语义中的任何差异都将被视为错误。
Overview
概念
- Values:WebAssembly 仅提供四种基本数字类型。这些是整数和 IEEE 7546 数字,分别为 32 和 64 位宽度。32 位整数也用作布尔值和内存地址。这些类型的常用操作都可用,包括它们之间的完整转换矩阵。有符号和无符号整数类型之间没有区别。相反,整数被相应的操作解释为无符号或有符号的二进制补码表示形式。除了这些基本数字类型之外,还有一个 128 位宽的向量类型代表不同类型的打包数据。支持的表示形式为 4 个 32 位或 2 个 64 位 IEEE 7547 数字,或不同宽度的打包整数值,具体为 2 个 64 位整数、4 个 32 位整数、8 个 16 位整数或 16 个 8 位整数。最后,值可以由表示指向不同类型实体的指针的不透明引用组成。与其他类型不同,它们的大小或表示形式是不可观察的。
- Instructions:WebAssembly 的计算模型基于堆栈机。代码由按顺序执行的指令序列组成。指令操作隐式操作数堆栈上的值,分为两大类。简单的指令对数据执行基本操作。它们从操作数堆栈中弹出参数并将结果推回操作数堆栈。控制指令改变控制流。控制流是结构化的,这意味着它是用良好嵌套的结构(例如块、循环和条件)来表达的。 分支只能针对此类构造。
- Traps:在某些情况下,某些指令可能会产生陷阱,从而立即中止执行。陷阱无法由 WebAssembly 代码处理,但会报告给外部环境,通常可以在外部环境中捕获陷阱。
- Functions:代码被组织成单独的函数。 每个函数都采用一系列值作为参数,并返回一系列值作为结果。 函数可以相互调用,包括递归调用,从而产生无法直接访问的隐式调用堆栈。 函数还可以声明可用作虚拟寄存器的可变局部变量。
- Tables:表是特定元素类型的不透明值的数组。它允许程序通过动态索引操作数间接选择此类值。目前,唯一可用的元素类型是无类型函数引用或对外部主机值的引用。因此,程序可以通过表的动态索引间接调用函数。例如,这允许通过表索引来模拟函数指针。
- Linear Memory:线性内存是一个连续的、可变的原始字节数组。这样的内存是用初始大小创建的,但可以动态增长。程序可以在任何字节地址(包括未对齐的)处从线性存储器加载值或将值存储到线性存储器。整数加载和存储可以指定小于相应值类型大小的存储大小。如果访问不在当前内存大小的范围内,则会发生陷阱。
- Modules:WebAssembly 二进制文件采用模块的形式,其中包含函数、表和线性存储器的定义,以及可变或不可变的全局变量。还可以导入定义,指定模块/名称对和合适的类型。每个定义都可以选择以一个或多个名称导出。除了定义之外,模块还可以为其存储器或表定义初始化数据,这些数据采用复制到给定偏移量的段的形式。他们还可以定义自动执行的启动函数。
- Embedder:WebAssembly 实现通常会嵌入到主机环境中。 该环境定义了如何启动模块加载、如何提供导入(包括主机端定义)以及如何访问导出。 然而,任何特定嵌入的细节超出了本规范的范围,而是由补充的、特定于环境的 API 定义来提供。
Semantic Phases
从概念上讲,WebAssembly 的语义分为三个阶段。
- 解码:WebAssembly 模块以二进制格式分发。解码处理格式并将其转换为模块的内部表示。在本规范中,这种表示形式是通过抽象语法建模的,但实际的实现可以直接编译为机器代码。
- 验证:解码的模块必须有效。验证会检查许多格式良好的条件,以保证模块有意义且安全。特别是,它对函数及其主体中的指令序列执行类型检查,例如确保操作数堆栈的使用一致。
- 执行:最后,可以执行有效的模块。 执行又可以分为两个阶段:
- Instantiation。模块实例是模块的动态表示,包含其自己的状态和执行堆栈。实例化执行模块主体本身,并给出其所有导入的定义。它初始化全局变量、内存和表,并调用模块的启动函数(如果已定义)。它返回模块导出的实例。
- Invocation。实例化后,可以通过调用模块实例上的导出函数来启动进一步的 WebAssembly 计算。 给定所需的参数,执行相应的函数并返回其结果。
实例化和调用是嵌入环境中的操作。