关于 WASM:1. WASM 基础原理
一、WASM 简介
1.1 WebAssembly 是什么?
WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C++、Rust)编译生成,并由浏览器中的 WebAssembly 引擎执行。
简而言之,WASM 是浏览器中的一个“轻量虚拟机”,它的目标是运行速度快、安全性高、体积小,适合执行计算密集型任务。
1.2 设计目标与用途
目标 | 解释说明 |
---|---|
高性能 | WASM 的指令非常接近底层机器语言,性能可达原生应用的 80%~90%。 |
可移植 | 编译后的 .wasm 文件可以在任意支持 WASM 的环境中运行,如浏览器、Node.js、区块链运行时等。 |
安全执行 | 运行在浏览器的沙箱环境中,无法直接访问文件系统或网络,只能访问明确授权的资源。 |
与 JavaScript 协同 | 设计目标不是取代 JS,而是补强——JS 做逻辑控制,WASM 处理高强度任务。 |
可快速加载 | .wasm 是压缩后的二进制格式,比 .js 加载更快、体积更小。 |
主要用途
-
图像/音频/视频处理:如将 C 写的 FFmpeg 编译为 WASM 在浏览器中运行;
-
游戏引擎移植:如 Unity、Unreal Engine 游戏编译成 Web 版本;
-
加解密算法实现:比如 AES、SHA256;
-
PDF / Word / Markdown 渲染;
-
AI/ML 推理模块;
-
区块链智能合约执行环境(如 EOS、Polkadot)。
1.3 与 JavaScript 的关系与运行方式
WebAssembly 的运行方式高度依赖 JavaScript,主要是 JS 扮演“调用者”的角色,WASM 负责“执行者”。
协作模式
-
JS 调用 WASM: 加载
.wasm
模块、传参、获取结果; -
WASM 导出函数: 供 JS 调用;
-
WASM 可导入 JS 提供的函数: 比如浏览器的打印、时间、内存等 API;
-
共享内存: 二者可以共享 ArrayBuffer(线性内存)。
示例流程图:
[ C/C++/Rust ]|↓┌───────────────┐│ 编译成 .wasm │ <── LLVM、Emscripten└───────────────┘↓┌──────────────────────────┐│ JavaScript 加载 .wasm ││ └ fetch / instantiate │└──────────────────────────┘↓┌──────────────────────────┐│ 浏览器 WASM 引擎执行代码 │└──────────────────────────┘
实际代码调用方式
HTML 页面中 JavaScript 调用 add.wasm
:
fetch('add.wasm') // 从服务器获取名为 'add.wasm' 的 WebAssembly 模块文件.then(response => response.arrayBuffer()) // 将响应数据转换为 ArrayBuffer(二进制格式).then(bytes => WebAssembly.instantiate(bytes)) // 将 ArrayBuffer 实例化为 WebAssembly 模块.then(result => { // 实例化成功后执行这个回调函数const add = result.instance.exports.add; // 从模块中导出名为 'add' 的函数console.log(add(5, 7)); // 调用导出的 'add' 函数,传入参数 5 和 7,输出结果 12});
注:需要先将一个用 C 写的加法函数编译为 .wasm
文件,JS 才能调用。
1.4 浏览器对 WASM 的支持情况
WASM 是由 Google、Mozilla、Microsoft、Apple 四大厂商联合设计的标准,当前所有主流浏览器均已支持 WebAssembly。
浏览器 | 支持版本(最低) | 是否默认开启 |
---|---|---|
Chrome | 57+ | 是 |
Firefox | 52+ | 是 |
Safari | 11+ | 是 |
Edge(旧版) | 16+ | 是 |
Edge(Chromium) | 所有版本 | 是 |
Node.js | 8+ | 是 |
检测支持的方法:
console.log(typeof WebAssembly === "object"); // true 表示支持
二、WASM 架构与运行模型
WebAssembly 本质上是一个基于栈机的运行时系统,核心架构包括:
-
模块(Module)
-
实例(Instance)
-
内存(Memory)与表(Table)
-
导入与导出(Import / Export)
-
栈式指令结构(Stack Machine Model)
2.1 模块(Module)
定义:模块是 WebAssembly 的最小部署单元,可以理解为 .wasm
文件中封装的逻辑程序块,就像一个 JavaScript 模块或 C 的动态链接库(.so/.dll)。
它包含:
-
类型(函数签名)
-
函数(函数体)
-
内存/表定义
-
全局变量
-
导入(import)项
-
导出(export)项
-
启动函数(start,可选)
示例
(module ;; 定义一个 WebAssembly 模块(func $add ;; 声明一个名为 $add 的函数(param $a i32) ;; 函数参数 $a,类型为 i32(32 位整数)(param $b i32) ;; 函数参数 $b,类型为 i32(result i32) ;; 返回值类型为 i32local.get $a ;; 读取本地变量 $a 的值local.get $b ;; 读取本地变量 $b 的值i32.add) ;; 对 $a 和 $b 执行加法运算(export "add" (func $add)) ;; 将 $add 函数导出,命名为 "add",供外部调用
)
以上 .wat
表示一个导出 add
函数的模块,两个参数相加。
注意:
特性 | .wat | .wasm |
---|---|---|
全称 | WebAssembly Text format | WebAssembly Binary format |
表现形式 | 可读的文本格式 | 压缩的二进制格式 |
人类可读性 | (适合调试、学习) | (需反编译工具查看) |
执行效率 | (需先转为 .wasm ) | (浏览器/引擎直接执行) |
使用场景 | 编写、调试 WebAssembly 模块 | 运行、部署 WebAssembly 模块 |
转换工具 | 使用 wat2wasm 转为 .wasm | 使用 wasm2wat 转为 .wat |
2.2 实例(Instance)
定义:模块是“代码模板”,实例(Instance)是模块在内存中的实际运行副本,加载后才具有运行能力。
它包含:
-
运行时内存
-
已绑定的导入值
-
各类资源的实例化副本(函数、表、内存、全局变量)
关系图:
.wasm↓ 编译Module(静态)↓ 实例化Instance(运行态)
通过 JS 调用 .wasm
,其实是使用模块创建了一个实例,然后调用其导出函数。
2.3 内存(Memory)与表(Table)
内存(Memory)
WebAssembly 的内存是一个线性内存(Linear Memory),表现为一个连续的字节数组(ArrayBuffer),供模块读写数据。
-
单位是“页”,每页 64KB(
65536 bytes
) -
只支持一段内存(Memory Index 为 0)
-
可以动态增长(grow)
-
由 JS 与 WASM 共享
// JS 中获取 wasm memory:
const memory = instance.exports.memory;
const bytes = new Uint8Array(memory.buffer); // 访问 wasm 中的内存数据
表(Table)
WebAssembly 的表是用于存储**函数引用(Function References)**的结构,用于支持间接调用(Indirect Calls)。
常用于:
-
动态函数调用(类似 C 的函数指针)
-
多态实现(如虚函数表)
2.4 导入与导出(Import / Export)
导入(Import)
允许模块使用外部定义的函数、内存、表、全局变量等,通常由 JavaScript 提供。
示例:
(import "env" "log" (func $log (param i32)))
上述表示模块希望导入一个 JS 的 env.log
函数。
导出(Export)
允许模块暴露函数、内存、表等供外部使用(JS 调用)。
(export "add" (func $add))
表示把 add
函数导出。
JS 调用关系
const imports = { // 定义导入对象 imports,供 wasm 模块使用env: { // 创建一个 "env" 命名空间(WebAssembly 中常见的导入命名空间)log: (arg) => console.log("WASM Log:", arg) // 定义一个 JS 函数 log,它会被 WebAssembly 模块中的代码调用,用于打印日志}
};WebAssembly.instantiate(bytes, imports) // 使用 imports 导入对象实例化 WebAssembly 模块.then(result => { // 实例化成功后执行这个回调const add = result.instance.exports.add; // 从模块中获取导出的函数 "add"console.log(add(2, 3)); // 调用 add(2, 3),输出 5(例如是加法函数)});
2.5 栈机结构(Stack Machine Model)
定义:WebAssembly 是一门基于栈的虚拟机语言(Stack Machine),所有指令不需要显式传参,而是通过操作数栈进行计算。
特点
-
无寄存器:不像 x86 或 ARM,WASM 不使用寄存器;
-
操作数栈工作方式:先压栈,再计算,再出栈;
-
函数、局部变量、控制流全靠指令栈操作完成
示例:
(func (param $a i32) (param $b i32) (result i32)local.get $a ;; → 将 $a 放入栈顶local.get $b ;; → 将 $b 放入栈顶i32.add ;; → 出栈两个数相加,将结果入栈
)
执行流程是这样的(假设 a=3,b=4):
[ ] ← 初始空栈
[3] ← local.get $a
[3, 4] ← local.get $b
[7] ← i32.add(弹出两个数,相加,再压入)
结果 7 就是函数返回值。
2.6 小结图示
┌──────────────┐
│ Module (.wasm) │
└──────┬───────┘↓ 实例化
┌──────────────┐
│ Instance │
│ ┌───────────┐ │
│ │ Memory │◄── JS 可访问 ArrayBuffer
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Table ││── 动态函数表
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Export │◄── JS 调用函数
│ └───────────┘ │
└──────────────┘▲│ Import│┌──────────────┐│ JavaScript │└──────────────┘
组件 | 作用描述 |
---|---|
Module | 静态代码定义 .wasm 文件 |
Instance | 模块运行时实例(JS 调用它) |
Memory | 线性内存,WASM/JS 共享 |
Table | 函数表,支持间接调用 |
Import | 模块向外请求的资源(JS 提供) |
Export | 模块向外暴露的函数或变量 |
Stack Machine | 栈结构执行指令,支持无寄存器运算 |
三、WASM 文件格式
WebAssembly 文件主要有三种格式:
-
.wasm
:二进制格式,供浏览器加载执行 -
.wat
:文本格式,便于阅读与手写 -
.wast
:测试格式(已废弃)
3.1 .wasm
二进制格式
定义:.wasm
是 WebAssembly 的主流部署格式,采用高度压缩的二进制编码(binary encoding),专为浏览器高效解析而设计。
特点
特性 | 描述 |
---|---|
高性能 | 加载与编译速度快,接近原生执行 |
不可读 | 对人类不友好,必须借助工具解析 |
可移植 | 不依赖平台,跨浏览器兼容 |
安全沙箱 | 所有行为在沙箱中运行,不会直接访问本地资源 |
文件结构(简化)
+---------------------+
| Magic Header (4B) | 0x00 0x61 0x73 0x6D → "\0asm"
| Version (4B) | 当前一般是 0x01 0x00 0x00 0x00
+---------------------+
| Section 1: Type |
| Section 2: Import |
| Section 3: Function |
| Section 4: Table |
| Section 5: Memory |
| Section 6: Export |
| Section 7: Code |
| Section 8: Data |
+---------------------+
可以使用工具如 wasm-objdump
来反编译:
wasm-objdump -x my_module.wasm # 查看段结构
wasm-objdump -d my_module.wasm # 反汇编
3.2 .wat
文本格式(WebAssembly Text Format)
定义:.wat
是 WebAssembly 的文本表示形式,类似汇编语言,用于手写、调试与学习。
特点
特性 | 描述 |
---|---|
可读性强 | 类 Lisp 风格,结构化、显式函数与局部变量 |
易于调试 | 方便修改和反编译观察 |
与 .wasm 可互转 | 通过官方工具如 wat2wasm 、wasm2wat |
示例 .wat
文件
(module;; 从 JS 中导入一个名为 "log" 的函数,接受一个 i32 参数(import "env" "log" (func $log (param i32)));; 定义加法函数 $add(func $add (param $a i32) (param $b i32) (result i32)local.get $a ;; 获取参数 alocal.get $b ;; 获取参数 bi32.add ;; 相加,栈顶变为结果(保留在栈顶);; 为了既返回又调用 log,我们使用 local.tee,把结果保存到临时变量local.tee 0 ;; 把结果存到局部变量 0,并保留在栈上用于返回call $log ;; 调用 log 打印结果)(export "add" (func $add)) ;; 导出函数
)
搭配 JavaScript 使用:
const imports = {env: {log: (arg) => console.log("WASM Log:", arg) // log 会在 WASM 中被调用}
};WebAssembly.instantiate(bytes, imports).then(result => {const add = result.instance.exports.add;console.log("JS Result:", add(2, 3)); // 先 WASM 打印,再 JS 打印
});
输出效果:
WASM Log: 5 // 来自 WebAssembly 内部调用 log
JS Result: 5 // 来自 JS 的 console.log
格式转换
需要安装 WABT 工具集(WebAssembly Binary Toolkit):
# 安装
brew install wabt # MacOS
sudo apt install wabt # Ubuntu# 转换为二进制
wat2wasm add.wat -o add.wasm# 反编译为文本
wasm2wat add.wasm -o add.wat
3.3 工具推荐
工具 | 作用 |
---|---|
wat2wasm | 文本转二进制 |
wasm2wat | 二进制转文本 |
wasm-objdump | 查看段信息 |
wasm-interp | 执行 wast 测试脚本 |
WebAssembly Studio | 在线 IDE |
wasm-pack | Rust 生成 WebAssembly |
binaryen | 优化 .wasm |
四、WebAssembly 实战
4.1 安装 WebAssembly 工具:WABT
WABT(WebAssembly Binary Toolkit) 是官方的 WASM 编译工具链,包含:
-
wat2wasm
:将文本.wat
转成二进制.wasm
-
wasm2wat
:反编译.wasm
为文本 -
wasm-objdump
:查看段信息
MacOS:
brew install wabt
Ubuntu/Debian:
sudo apt update
sudo apt install wabt
Windows:
-
前往 GitHub 下载编译好的 ZIP: https://github.com/WebAssembly/wabt/releases
-
解压并将
bin/
目录添加到系统环境变量Path
4.2 项目目录结构
mkdir wasm-demo && cd wasm-demo
项目目录:
wasm-demo/
├── index.html # 网页
├── main.js # JavaScript 逻辑
├── add.wat # WebAssembly 文本源码
├── add.wasm # 编译生成的二进制文件
4.3 编写 WebAssembly 文本文件(add.wat)
创建文件 add.wat
:
;; 文件:add.wat
(module(func $add (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.add)(export "add" (func $add))
)
这是一个简单的加法函数,接收两个 i32
整数,返回它们的和,并通过 export
导出。
4.4 编译 .wat 为 .wasm
wat2wasm add.wat -o add.wasm
成功后看到生成的 add.wasm
二进制文件,浏览器可以直接加载它。
4.5 编写 HTML 页面(index.html)
<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WebAssembly 加法演示</title>
</head>
<body><h1>WebAssembly 示例:3 + 4 = <span id="result">?</span></h1><script src="main.js"></script>
</body>
</html>
4.6 编写 JS 加载逻辑(main.js)
// 文件:main.jsasync function initWASM() {const response = await fetch('add.wasm'); // 从服务器加载名为 add.wasm 的 WebAssembly 文件const bytes = await response.arrayBuffer(); // 将响应内容转换为 ArrayBuffer(二进制)const { instance } = await WebAssembly.instantiate(bytes); // 实例化 WebAssembly 模块,获取模块实例const result = instance.exports.add(3, 4); // 调用导出的 add 函数,传入 3 和 4document.getElementById('result').textContent = result; // 将计算结果显示在页面 id="result" 的元素中
}initWASM(); // 执行函数,初始化并调用 WASM 模块
说明:
-
fetch()
加载.wasm
文件 -
WebAssembly.instantiate()
编译 + 实例化 -
调用导出的函数
add(3, 4)
-
显示结果到网页
4.7 运行本地服务器测试
浏览器无法直接读取本地文件的 .wasm
,必须通过 HTTP 服务运行它。
推荐方法:使用 Python(快速)
# Python 3.x
python -m http.server 8080
然后访问:
http://localhost:8080
如果一切成功,网页上会显示:
WebAssembly 示例:3 + 4 = 7
4.8 小结
步骤 | 内容 |
---|---|
1 | 编写 .wat 文件 |
2 | 使用 wat2wasm 编译 |
3 | 写 HTML 页面结构 |
4 | 用 JS 加载 .wasm |
5 | 使用本地 HTTP 服务运行 |