凌云的博客

行胜于言

WebAssembly 概述[译]

分类:wasm| 发布时间:2024-12-27 08:56:00

概念

原文:WebAssembly Overview

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

值 (Values)

WebAssembly 只提供四种基本的数字类型。 这些类型包括整数和 IEEE 754 数字,每种有 32 位和 64 位两种宽度。 32 位整数还用作布尔值和内存地址。 这些类型支持常见的操作,包括它们之间的完整转换矩阵。 整数类型不区分有符号和无符号。 相反,整数根据相应的操作被解释为无符号或有符号的二进制补码表示形式。

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

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

指令 (Instructions)

WebAssembly 的计算模型基于栈机器。 代码由一系列按顺序执行的指令组成。 指令操作隐式操作数栈上的值[1],并分为两大类:

  • 简单指令:对数据执行基本操作。这些指令从操作数栈中弹出参数,并将结果压入栈中。
  • 控制指令:改变控制流。控制流是结构化的,即通过良好嵌套的构造(如块、循环和条件语句)来表示。分支只能跳转到这些结构。

异常 (Traps)

在某些条件下,某些指令可能会触发异常,导致立即中止执行。 WebAssembly 代码无法处理这些异常,但会将它们报告给外部环境,外部环境通常能够捕捉这些异常。

函数 (Functions)

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

表 (Tables)

表是一个特定元素类型的不透明值数组。 它允许程序通过动态索引操作数间接选择这些值。 目前,唯一支持的元素类型是不带类型的函数引用或对外部主机值的引用。 通过这种方式,程序可以通过动态索引间接调用函数。 例如,这使得通过表索引来模拟函数指针成为可能。

线性内存 (Linear Memory)

线性内存是一个连续的、可变的原始字节数组。 这样的内存在创建时有一个初始大小,但可以动态增长。 程序可以从线性内存的任何字节地址(包括非对齐地址)加载和存储值。 整数的加载和存储可以指定小于相应值类型大小的存储大小。 如果访问超出当前内存大小的范围,会触发异常。

模块 (Modules)

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

嵌入器 (Embedder)

WebAssembly 实现通常会嵌入到宿主环境中。 该环境定义了如何启动模块加载,如何提供导入(包括宿主侧的定义),以及如何访问导出。 然而,任何特定嵌入的详细信息超出了本规范的范围,而是由互补的、特定于环境的 API 定义提供。

语义阶段

从概念上讲,WebAssembly 的语义被分为三个阶段。 对于每个阶段,规范都明确规定了它们的处理方式。

解码(Decoding)

WebAssembly 模块是以二进制格式分发的。 解码过程将这种格式转化为模块的内部表示。 在本规范中,这种表示通过抽象语法来建模,但实际的实现可能会直接编译为机器码。

验证(Validation)

一个解码后的模块必须是有效的。 验证阶段检查一系列结构正确性条件,以确保模块是有意义且安全的。 特别地,它会对函数和函数体内的指令序列进行类型检查,确保例如操作栈的使用是一致的。

执行(Execution)

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

  • 实例化(Instantiation):模块实例是模块的动态表示,包含它自己的状态和执行栈。 实例化阶段执行模块体本身,前提是所有的导入已经被定义。 它会初始化全局变量、内存和表,如果模块定义了启动函数会在此时调用模块的启动函数。 它返回模块的导出实例。

  • 调用(Invocation)

一旦模块被实例化,就可以通过在模块实例上调用导出函数来发起进一步的 WebAssembly 计算。 给定所需的参数,函数会被执行并返回其结果。

实例化和调用是在嵌入的虚拟环境中进行的。

注解

  1. 实际上,实现者不必维护一个实际的操作栈。 相反,栈可以视为一组匿名寄存器,这些寄存器由指令隐式引用。 类型系统确保堆栈高度以及任何引用的寄存器始终是静态已知的。