跳到主要内容

基础概念

深入理解 WebAssembly 的核心概念,包括模块、函数、内存、表和全局变量。

模块(Module)

模块是 WebAssembly 的基本编译和部署单元。一个模块包含:

  • 函数:可执行的代码块
  • 内存:线性内存区域
  • :函数引用数组
  • 全局变量:模块级变量
  • 导入:从外部导入的功能
  • 导出:向外部暴露的功能

模块结构

一个典型的 WebAssembly 模块结构:

(module
;; 导入部分
(import "env" "memory" (memory 1))
(import "env" "log" (func $log (param i32 i32)))

;; 全局变量
(global $counter (mut i32) (i32.const 0))

;; 内存
(memory (import "env" "memory") 1)

;; 函数定义
(func $increment (result i32)
global.get $counter
i32.const 1
i32.add
global.set $counter
global.get $counter)

;; 导出
(export "increment" (func $increment)))

模块实例化

模块需要实例化后才能执行:

const module = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(module, importObject);

或使用流式 API:

const { module, instance } = await WebAssembly.instantiateStreaming(
fetch('module.wasm'),
importObject
);

函数(Function)

函数是 WebAssembly 代码的基本组成单位。

函数签名

函数签名定义了参数和返回值类型:

(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)

参数类型

WebAssembly 支持以下基本类型:

类型说明范围
i3232位整数-2³¹ 到 2³¹-1
i6464位整数-2⁶³ 到 2⁶³-1
f3232位浮点数IEEE 754 单精度
f6464位浮点数IEEE 754 双精度
v128128位向量SIMD 指令使用
funcref函数引用函数指针
externref外部引用JavaScript 对象

局部变量

函数可以声明局部变量:

(func $example (param $x i32) (result i32)
(local $temp i32)
local.get $x
i32.const 2
i32.mul
local.set $temp
local.get $temp
i32.const 1
i32.add)

函数调用

使用 call 指令调用函数:

(module
(func $double (param $x i32) (result i32)
local.get $x
i32.const 2
i32.mul)

(func $quadruple (param $x i32) (result i32)
local.get $x
call $double
call $double))

栈式虚拟机

WebAssembly 采用栈式虚拟机架构,指令通过操作数栈进行计算。

栈操作原理

(func $add (param $a i32) (param $b i32) (result i32)
local.get $a ;; 将 $a 压入栈
local.get $b ;; 将 $b 压入栈
i32.add) ;; 弹出两个值,相加,结果压入栈

执行过程:

初始状态: 栈 = []

执行 local.get $a: 栈 = [a]

执行 local.get $b: 栈 = [a, b]

执行 i32.add: 栈 = [a + b]

常用栈操作指令

;; 压入常量
i32.const 42 ;; 压入 32 位整数 42
i64.const 100n ;; 压入 64 位整数 100
f32.const 3.14 ;; 压入 32 位浮点数
f64.const 2.718 ;; 压入 64 位浮点数

;; 局部变量操作
local.get 0 ;; 获取局部变量并压栈
local.set 0 ;; 弹出栈顶值存入局部变量
local.tee 0 ;; 复制栈顶值存入局部变量(不弹出)

;; 全局变量操作
global.get 0 ;; 获取全局变量并压栈
global.set 0 ;; 弹出栈顶值存入全局变量

内存(Memory)

WebAssembly 使用线性内存,是一块可增长的连续字节缓冲区。

内存声明

;; 声明 1 页内存(64KB),最大 10 页
(memory 1 10)

;; 导入内存
(memory (import "env" "memory") 1)

;; 导出内存
(memory (export "memory") 1)

内存页

WebAssembly 内存以页为单位管理,每页 64KB(65536 字节)。

const memory = new WebAssembly.Memory({ 
initial: 1, // 初始 1 页 = 64KB
maximum: 10 // 最大 10 页 = 640KB
});

内存访问指令

;; 加载
i32.load ;; 加载 32 位整数
i32.load8_s ;; 加载 8 位有符号整数
i32.load8_u ;; 加载 8 位无符号整数
i64.load ;; 加载 64 位整数
f32.load ;; 加载 32 位浮点数
f64.load ;; 加载 64 位浮点数

;; 存储
i32.store ;; 存储 32 位整数
i32.store8 ;; 存储 8 位整数
i64.store ;; 存储 64 位整数
f32.store ;; 存储 32 位浮点数
f64.store ;; 存储 64 位浮点数

内存操作示例

(module
(memory 1)

(func $store_value (param $offset i32) (param $value i32)
local.get $offset
local.get $value
i32.store)

(func $load_value (param $offset i32) (result i32)
local.get $offset
i32.load)

(export "store_value" (func $store_value))
(export "load_value" (func $load_value))
(export "memory" (memory 0)))

JavaScript 端:

const { instance } = await WebAssembly.instantiateStreaming(
fetch('memory.wasm')
);

instance.exports.store_value(0, 42);
const value = instance.exports.load_value(0);
console.log(value);

const memory = instance.exports.memory;
const view = new Int32Array(memory.buffer);
console.log(view[0]);

表(Table)

表是函数引用的可变数组,用于实现间接调用。

表声明

;; 声明包含 2 个函数引用的表
(table 2 funcref)

;; 导出表
(table (export "table") 2 funcref)

表初始化

(module
(table 2 funcref)

(func $f1 (result i32) i32.const 1)
(func $f2 (result i32) i32.const 2)

;; 元素段初始化表
(elem (i32.const 0) $f1 $f2)

(export "table" (table 0)))

间接调用

使用 call_indirect 通过表进行间接调用:

(module
(type $return_i32 (func (result i32)))

(table 2 funcref)
(func $f1 (result i32) i32.const 1)
(func $f2 (result i32) i32.const 2)
(elem (i32.const 0) $f1 $f2)

(func $call_by_index (param $i i32) (result i32)
local.get $i
call_indirect (type $return_i32))

(export "call_by_index" (func $call_by_index)))

JavaScript 端:

const result = instance.exports.call_by_index(0);

全局变量(Global)

全局变量可以在模块间共享数据。

全局变量声明

;; 不可变全局变量
(global $pi f64 (f64.const 3.14159))

;; 可变全局变量
(global $counter (mut i32) (i32.const 0))

全局变量操作

(module
(global $counter (mut i32) (i32.const 0))

(func $get_counter (result i32)
global.get $counter)

(func $increment_counter
global.get $counter
i32.const 1
i32.add
global.set $counter)

(export "get_counter" (func $get_counter))
(export "increment_counter" (func $increment_counter)))

JavaScript 创建全局变量

const global = new WebAssembly.Global(
{ value: 'i32', mutable: true },
0
);

const importObject = {
env: {
global: global
}
};

导入和导出

导入

WebAssembly 可以从外部导入函数、内存、表和全局变量:

(module
;; 导入函数
(import "env" "log" (func $log (param i32)))

;; 导入内存
(import "env" "memory" (memory 1))

;; 导入全局变量
(import "env" "global" (global i32))

;; 导入表
(import "env" "table" (table 1 funcref)))

JavaScript 端:

const importObject = {
env: {
log: (value) => console.log(value),
memory: new WebAssembly.Memory({ initial: 1 }),
global: new WebAssembly.Global({ value: 'i32' }, 42),
table: new WebAssembly.Table({
initial: 1,
element: 'funcref'
})
}
};

导出

WebAssembly 可以导出函数、内存、表和全局变量:

(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)

(memory 1)
(table 1 funcref)
(global $counter (mut i32) (i32.const 0))

(export "add" (func $add))
(export "memory" (memory 0))
(export "table" (table 0))
(export "counter" (global $counter)))

数据段和元素段

数据段

数据段用于在实例化时初始化内存:

(module
(memory 1)

;; 在偏移 0 处写入字符串
(data (i32.const 0) "Hello, WebAssembly!")

(export "memory" (memory 0)))

元素段

元素段用于在实例化时初始化表:

(module
(table 3 funcref)

(func $f1 (result i32) i32.const 1)
(func $f2 (result i32) i32.const 2)
(func $f3 (result i32) i32.const 3)

;; 在偏移 0 处填充函数引用
(elem (i32.const 0) $f1 $f2 $f3))

下一步

理解基础概念后,你可以继续学习: