摘要
本文从 WebAssembly 的模块加载与解析、执行引擎、系统接口(WASI)等方面阐述其运行时原理,还涉及线性内存布局和垃圾回收机制提案等内容
1. 前言
本文主要围绕 WebAssembly 展开,狭义上它是一种二进制格式,广义上是一个完整生态。WebAssembly 的基础核心架构包括二进制格式、前端(高级编程语言、编译工具链和语言核心库)以及提供运行环境的虚拟机。之前的章节对核心架构中的二进制规范等内容已详述,本文将从运行时的维度,如模块加载和解析器、执行引擎以及与宿主的系统交互接口(WASI)等来分析 WebAssembly 的核心原理及基础能力。
2. WebAssembly 解析器
2.1 模块加载和解码
WebAssembly 模块的二进制格式是其抽象语法的压缩线性编码,以段编码为模块文件。其加载和解码阶段按照属性文法解析函数将字节流转换为内部数据结构,存于“WasmRuntime Data Areas”中的几个关键区域,如全局数据区、方法区等。例如,在加载函数对象时,会在执行环境中创建对应的“function space”内存空间,并按顺序分配索引,导入函数则预留空间。
// 示例代码展示函数空间创建(假设伪代码)
function loadFunction(module) {
let functionSpace = createFunctionSpace();
for (let i = 0; i < module.functions.length; i ++) {
if (module.functions[i].isImported) {
reserveIndex(functionSpace, i);
} else {
functionSpace[i] = createFunctionObject(module.functions[i]);
}
}
return functionSpace;
}
2.2 模块验证
模块加载后需要校验是否格式正确。有效性由类型系统定义,对于每个抽象语法段都有类型规则指定约束。Validation Algorithm 提供了类型检查的框架,它在二进制格式操作码序列上进行一次传递检查。以 local.tee 指令为例,有特定的验证约束规则,只有满足规则指令序列才能正常执行,否则会抛出异常。
// 示例代码表示验证逻辑(假设伪代码)
function validateInstruction(instructions) {
for (let i = 0; i < instructions.length; i ++) {
if (instructions[i].name === "local.tee") {
let context = getCurrentContext();
let localVar = context.getLocalVariable(instructions[i].inputParam);
if (localVar.type === instructions[i].inputParam.type && localVar.type === instructions[i].returnType) {
continue;
} else {
throw new Error("Invalid local.tee instruction");
}
}
}
return true;
}
2.3 实例化
完成前两个阶段后,解析器要进行模块实例化。主要工作是根据加载过程中的数据结构创建对象实例,并完成符号解析和链接。例如函数内存空间,在加载时虽有索引空间布局,但未完成符号解析和链接,实例化过程中要从外部模块获取并解析导入的符号对象,保存到索引空间中。
3. 执行引擎
3.1 栈解释器
WebAssembly 指令集基于栈的架构设计。与基于寄存器的指令集相比,零地址指令在空间紧缺环境中有优势。在基于栈的虚拟机中,指令执行时从栈中取操作数,运算后将结果压入栈。WebAssembly 选择这种架构便于模块验证,如验证内存访问范围、函数返回值类型等。通过一个简单函数示例,展示了源代码转换为 WebAssembly 字节码,以及函数执行时调用栈的结构,包括局部变量区和操作数栈等。
// 示例中函数转换为 WebAssembly 字节码
(module
(type (;0;) (func (result i32)))
(func (;0;) (export "foo") (type 0) (result i32)
(local i32 i32 i32)
i32.const 1
local.set 0
i32.const 2
local.set 1
local.get 0
local.get 1
i32.add
i32.const 5
i32.mul
local.tee 2
return))
3.2 线性内存管理
WebAssembly 内存包含托管和非托管内存,非托管的线性内存是地址连续、可字节寻址的结构。每个实例有默认线性内存,其类型由模块的内存段描述。WebAssembly 线性内存处于沙箱环境,与其他内存分离,可在进程内不同应用中共存且安全。不同前端语言编译产物的内存布局会带来技术挑战,LLVM 编译工具链借助 wasm - ld 链接器实现内存布局,分为全局静态数据区、未初始化数据区、辅助栈区和堆区等,可通过示例展示 wasm - ld 的默认和栈优先两种线性内存布局方式。
3.3 垃圾回收器(GC)
主流内存管理方式有手动和垃圾收集器管理两类。手动管理在大型软件中易出现内存问题,垃圾收集器管理虽让开发人员无需过多考虑内存,但也存在一些弊端。WebAssembly 初期目标面向静态强类型语言,没有垃圾收集器,对于复杂数据结构需手动管理内存。但 Garbage collection 提案到了第三阶段,实现垃圾收集器可带来多方面好处,如支持更多高级语言、解放开发人员内存管理工作、实现多语言无缝互操作。该提案还涉及类型系统扩展等内容,如增加高级数据类型和对应指令,并且 WebAssembly 逐步向类型化汇编语言演进。
4. WebAssembly 系统接口(WASI)
WebAssembly 是跨平台运行的低级语言,需要系统接口与外界交互,即 WASI。它是标准化 API 的模块化集合,以模块化方式制定接口,从基础模块开始逐步完善。wasi - core 标准接口提供基本能力,在 wasi - libc 中提供使用支持,涵盖 POSIX 相关的多种能力,如文件、网络连接等操作相关的 API。
5. 总结
本文详细介绍了 WebAssembly 运行时原理,包括模块加载与解析、执行以及与宿主交互机制,还介绍了线性内存布局和 GC 机制相关内容。WebAssembly 虽已发展但仍不完善,其未来还有很多新特性等待挖掘。