基础概念
深入理解 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 支持以下基本类型:
| 类型 | 说明 | 范围 |
|---|---|---|
| i32 | 32位整数 | -2³¹ 到 2³¹-1 |
| i64 | 64位整数 | -2⁶³ 到 2⁶³-1 |
| f32 | 32位浮点数 | IEEE 754 单精度 |
| f64 | 64位浮点数 | IEEE 754 双精度 |
| v128 | 128位向量 | 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))
下一步
理解基础概念后,你可以继续学习:
- 文本格式 WAT - 深入学习 WAT 语法
- JavaScript 交互 - JavaScript 与 WebAssembly 互操作
- 内存管理 - 深入理解内存模型