Skip to content

WebAssembly

什么是 WebAssembly

WebAssembly(简称 Wasm)是一种低级二进制指令格式,运行在基于栈的虚拟机上。它被设计为 C/C++/Rust/Go 等高级语言的编译目标,使这些语言编写的程序能够在浏览器中以接近原生的速度运行。

2017 年,四大浏览器引擎(Chrome V8、Firefox SpiderMonkey、Safari JavaScriptCore、Edge Chakra)同时宣布支持 WebAssembly MVP 版本,标志着 Web 平台进入了一个新的性能时代。

传统 Web 执行模型:

  源代码 (JS)  →  JS Engine 解析  →  解释执行 / JIT 编译  →  运行

               AST → Bytecode → Machine Code
               (多次优化,启动慢,性能受限于动态类型推断)


WebAssembly 执行模型:

  源代码 (C/Rust)  →  离线编译器  →  .wasm 二进制  →  浏览器加载  →  运行

                                   已经是低级指令
                              (解码快,无需类型推断,接近原生性能)

Wasm 的四大设计目标

┌──────────────────────────────────────────────────────────────────┐
│                    WebAssembly 设计目标                           │
├─────────────────┬────────────────────────────────────────────────┤
│   高性能         │  二进制格式紧凑,解码速度比 JS 解析快 20 倍+     │
│                 │  编译后接近原生机器码执行速度                     │
├─────────────────┼────────────────────────────────────────────────┤
│   安全           │  沙箱执行环境,无法直接访问宿主系统              │
│                 │  内存隔离,通过线性内存模型防止越界               │
├─────────────────┼────────────────────────────────────────────────┤
│   可移植         │  与平台无关的指令集,支持所有主流操作系统/架构    │
│                 │  "一次编译,到处运行"                            │
├─────────────────┼────────────────────────────────────────────────┤
│   与 JS 互操作   │  可与 JavaScript 双向调用                       │
│                 │  共享同一运行时环境,渐进式集成                   │
└─────────────────┴────────────────────────────────────────────────┘

Wasm 与 JavaScript 的关系

WebAssembly 并非要替代 JavaScript,而是互补关系:

应用层
┌───────────────────────────────────────────────────┐
│                    Web 应用                        │
│                                                   │
│   ┌──────────────────┐  ┌─────────────────────┐  │
│   │   JavaScript     │  │   WebAssembly       │  │
│   │                  │  │                     │  │
│   │  · UI 交互       │  │  · 计算密集型任务   │  │
│   │  · DOM 操作      │  │  · 图像/音视频处理  │  │
│   │  · 网络请求      │  │  · 加密/压缩算法    │  │
│   │  · 业务逻辑      │  │  · 游戏引擎         │  │
│   │  · 胶水代码      │  │  · 科学计算         │  │
│   │                  │  │                     │  │
│   └────────┬─────────┘  └──────────┬──────────┘  │
│            │      双向互调          │             │
│            └──────────┬─────────────┘             │
│                       │                           │
├───────────────────────┼───────────────────────────┤
│              浏览器引擎 (V8 / SpiderMonkey / ...)  │
│   ┌──────────────────────────────────────────┐    │
│   │  JS Compiler    │   Wasm Compiler        │    │
│   │  (Ignition/TF)  │   (Liftoff/TurboFan)   │    │
│   └──────────────────────────────────────────┘    │
├───────────────────────────────────────────────────┤
│                  Operating System                  │
└───────────────────────────────────────────────────┘
对比维度JavaScriptWebAssembly
类型系统动态类型静态类型(i32, i64, f32, f64)
执行方式解释 + JIT预编译二进制
启动速度需解析/编译,大文件慢二进制解码极快
峰值性能JIT 优化后接近原生稳定接近原生
性能可预测性低(受 JIT 去优化影响)高(无去优化)
DOM 访问直接访问需通过 JS 桥接
生态极其丰富借助 C/C++/Rust 生态
学习门槛较高(需了解编译语言)
GC引擎托管自行管理(或 Wasm GC 提案)

Wasm 工作原理

二进制格式与文本格式

Wasm 有两种表示形式:

.wasm(二进制格式)                    .wat(文本格式)
┌──────────────────┐                  ┌──────────────────────────┐
│ 00 61 73 6d      │  magic number    │ (module                  │
│ 01 00 00 00      │  version 1       │   (func $add             │
│ 01 07 01 60      │  type section    │     (param $a i32)       │
│ 02 7f 7f 01      │  ...             │     (param $b i32)       │
│ 7f 03 02 01      │  ...             │     (result i32)         │
│ 00 07 07 01      │  ...             │     local.get $a         │
│ ...              │                  │     local.get $b         │
└──────────────────┘                  │     i32.add)             │
                                      │   (export "add"          │
二进制,体积小                         │     (func $add)))        │
网络传输、浏览器执行                    └──────────────────────────┘
                                      S-expression 语法
                                      可读,用于调试/学习

.wat 文件使用 S-expression(S 表达式)语法,类似 Lisp 风格。上面的例子定义了一个 add 函数,接收两个 i32 参数并返回它们的和。

Wasm 数据类型

Wasm 目前支持四种基本值类型:

┌────────────┬───────────────────────┬───────────────────┐
│  类型       │  描述                 │  位宽              │
├────────────┼───────────────────────┼───────────────────┤
│  i32       │  32 位整数            │  32 bit            │
│  i64       │  64 位整数            │  64 bit            │
│  f32       │  32 位浮点数          │  32 bit (IEEE 754) │
│  f64       │  64 位浮点数          │  64 bit (IEEE 754) │
├────────────┼───────────────────────┼───────────────────┤
│  v128      │  128 位 SIMD 向量     │  128 bit (提案)    │
│  funcref   │  函数引用             │  引用类型 (提案)    │
│  externref │  外部引用             │  引用类型 (提案)    │
└────────────┴───────────────────────┴───────────────────┘

编译流程

源语言代码到浏览器执行的完整链路:

源代码                编译工具链              产物             浏览器
─────                ──────────             ─────            ──────

C / C++  ──→  Emscripten (emcc)  ──→  .wasm + .js glue

Rust     ──→  wasm-pack + rustc  ──→  .wasm + .js bindings

Go       ──→  GOOS=js GOARCH=wasm ──→  .wasm + wasm_exec.js

AssemblyScript ──→  asc compiler  ──→  .wasm               ──→  浏览器加载


                                                             ┌─────────────┐
                                                             │ 1. fetch    │
                                                             │ 2. compile  │
                                                             │ 3. instance │
                                                             │ 4. 调用函数  │
                                                             └─────────────┘

加载与实例化流程

浏览器加载 Wasm 模块的完整流程:

fetch(.wasm)


┌─────────────┐    ┌─────────────────┐    ┌──────────────┐
│   Network   │───→│  Streaming      │───→│   Module     │
│   Download  │    │  Compilation    │    │   (编译完成)  │
└─────────────┘    └─────────────────┘    └──────┬───────┘

                                                  │ + importObject

                                          ┌──────────────┐
                                          │   Instance   │
                                          │  (实例化完成)  │
                                          │              │
                                          │  exports:    │
                                          │   · add()    │
                                          │   · memory   │
                                          │   · table    │
                                          └──────────────┘

JavaScript 代码示例:

javascript
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, importObject);

const result = instance.exports.add(1, 2);

推荐使用流式编译(边下载边编译),性能更优:

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

const result = instance.exports.add(1, 2);

两种方式的区别:

instantiate(传统方式):
  fetch ──→ 下载完成 ──→ 开始编译 ──→ 实例化

                  等待下载完成后才编译(串行)

instantiateStreaming(流式方式):
  fetch ──→ 下载中同时编译 ──→ 下载+编译完成 ──→ 实例化

                  边下载边编译(并行,速度更快)

线性内存模型

Wasm 使用线性内存(Linear Memory)——一段连续的、可增长的字节数组,这是 Wasm 实例与外界交换复杂数据的核心机制:

                     线性内存 (Linear Memory)
    ┌──────────────────────────────────────────────────┐
    │                                                  │
    │   地址 0x0000                                    │
    │   ┌────┬────┬────┬────┬────┬────┬────┬────┐     │
    │   │ 48 │ 65 │ 6C │ 6C │ 6F │ 00 │ .. │ .. │     │
    │   └────┴────┴────┴────┴────┴────┴────┴────┘     │
    │    'H'  'e'  'l'  'l'  'o'  '\0'                 │
    │                                                  │
    │   ...                                            │
    │                                                  │
    │   ┌──────────────────────────────────────┐       │
    │   │          堆分配区域                    │       │
    │   │   malloc / free 管理                  │       │
    │   └──────────────────────────────────────┘       │
    │                                                  │
    │   ┌──────────────────────────────────────┐       │
    │   │          栈空间                       │       │
    │   │   局部变量、函数调用帧                  │       │
    │   └──────────────────────────────────────┘       │
    │                                                  │
    │   最大可增长到 4GB (32位地址空间)                  │
    └──────────────────────────────────────────────────┘

    JavaScript 通过 ArrayBuffer 访问同一块内存:
    const memory = instance.exports.memory;
    const view = new Uint8Array(memory.buffer);

线性内存的关键特性:

1. 以 page 为单位分配(1 page = 64KB = 65,536 bytes)
2. 可通过 memory.grow(n) 增长 n 个 page
3. 最大 65536 pages = 4GB(受 32 位地址空间限制)
4. JS 侧和 Wasm 侧共享同一块 ArrayBuffer
5. 内存访问会进行边界检查,越界会 trap(抛出异常)

Wasm 核心概念

Module(模块)
  │   编译后的 .wasm 二进制,类似 .dll / .so
  │   无状态,可被缓存到 IndexedDB

  ├──→ Instance(实例)
  │       模块的运行时实例,包含所有导出
  │       每次 instantiate 产生独立的实例

  ├──→ Memory(线性内存)
  │       连续字节数组,Wasm 和 JS 共享
  │       用于传递字符串、数组等复杂数据

  ├──→ Table(间接函数表)
  │       存储函数引用,支持动态函数调用
  │       用于实现函数指针、虚函数表

  └──→ Global(全局变量)
          可变或不可变的全局值
          跨 JS 和 Wasm 共享

开发工具链

Emscripten(C/C++ → Wasm)

Emscripten 是最成熟的 C/C++ 到 Wasm 的编译工具链,基于 LLVM:

源代码 (.c / .cpp)


Clang 前端 → LLVM IR


LLVM 后端 (wasm32 target)


.wasm 二进制
    +
.js 胶水代码 (提供文件系统模拟、内存管理等)
    +
.html (可选,用于测试)

安装与使用:

bash
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

编写一个简单的 C 函数并编译:

c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
bash
emcc fibonacci.c -o fibonacci.js \
  -s EXPORTED_FUNCTIONS='["_fibonacci"]' \
  -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
  -O3

在 JavaScript 中调用:

javascript
const Module = await import('./fibonacci.js');

Module.onRuntimeInitialized = () => {
  const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
  console.log(fibonacci(10));
};

Rust + wasm-pack + wasm-bindgen

Rust 拥有一流的 Wasm 支持,wasm-bindgen 提供了高级的 JS/Wasm 互操作接口:

Rust 源码 (.rs)


rustc + wasm32-unknown-unknown target


wasm-bindgen (生成 JS 绑定)


wasm-pack (打包为 npm 包)


pkg/
├── module_bg.wasm
├── module.js
├── module.d.ts
└── package.json

初始化项目:

bash
cargo install wasm-pack

wasm-pack new my-wasm-lib
cd my-wasm-lib

编写 Rust 代码:

rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

构建并在前端项目中使用:

bash
wasm-pack build --target web
javascript
import init, { fibonacci, greet } from './pkg/my_wasm_lib.js';

await init();
console.log(fibonacci(10));
console.log(greet("World"));

AssemblyScript(TypeScript-like → Wasm)

AssemblyScript 使用 TypeScript 语法的子集,前端开发者上手门槛最低:

bash
npm init
npm install --save-dev assemblyscript
npx asinit .

编写 AssemblyScript:

typescript
export function fibonacci(n: i32): i32 {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

export function add(a: i32, b: i32): i32 {
  return a + b;
}

编译与使用:

bash
npx asc assembly/index.ts --outFile build/module.wasm --optimize
javascript
const { instance } = await WebAssembly.instantiateStreaming(
  fetch('build/module.wasm')
);

const { fibonacci, add } = instance.exports;
console.log(fibonacci(10));
console.log(add(3, 4));

三种工具链对比

维度Emscripten (C/C++)Rust + wasm-packAssemblyScript
源语言C / C++RustTypeScript 子集
学习曲线陡峭(需懂 C/C++)陡峭(需懂 Rust)平缓(类 TypeScript)
产物体积较大(含运行时)小(no_std 可极小)中等
性能极高极高
生态庞大(现有 C/C++ 库)丰富(crates.io)较小但增长中
JS 互操作cwrap/ccallwasm-bindgen(优秀)内置 loader
内存管理手动 malloc/free所有权系统(安全)GC(受限)
适用场景移植现有 C/C++ 项目新项目首选前端团队快速上手
调试体验DWARF + Source MapDWARF + Source MapSource Map
成熟度★★★★★★★★★☆★★★☆☆

JavaScript 与 Wasm 互操作

WebAssembly JavaScript API

浏览器提供了一组 API 用于加载和操作 Wasm 模块:

WebAssembly 对象
├── compile(bytes)                    编译为 Module
├── instantiate(bytes, imports)       编译 + 实例化(一步到位)
├── instantiateStreaming(fetch, imp)  流式编译 + 实例化(推荐)
├── validate(bytes)                   验证二进制是否合法
├── Module                            编译后的模块类
│   ├── customSections(module, name)
│   ├── exports(module)
│   └── imports(module)
├── Instance                          运行时实例类
│   └── exports                       导出的函数/内存/表
├── Memory                            线性内存类
│   ├── buffer                        ArrayBuffer 引用
│   └── grow(pages)                   扩展内存
├── Table                             间接函数表类
│   ├── get(index)
│   ├── set(index, func)
│   └── grow(count)
├── Global                            全局变量类
│   └── value                         读写全局值
├── CompileError                      编译错误
├── LinkError                         链接错误
└── RuntimeError                      运行时错误

导入导出函数

Wasm 模块可以导入 JavaScript 函数,也可以导出函数给 JavaScript 调用:

javascript
const importObject = {
  env: {
    log: (value) => console.log('Wasm says:', value),
    abort: () => { throw new Error('abort called'); }
  },
  js: {
    mem: new WebAssembly.Memory({ initial: 1 })
  }
};

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

instance.exports.doSomething();

对应的 .wat 模块:

wasm
(module
  (import "env" "log" (func $log (param i32)))
  (import "js" "mem" (memory 1))

  (func $doSomething (export "doSomething")
    i32.const 42
    call $log
  )
)

调用流程:

JavaScript                          WebAssembly
──────────                          ───────────

importObject ──────────────────→  import 声明
  { env: { log: fn } }              (import "env" "log" ...)

                                   func $doSomething
                                     i32.const 42
instance.exports ←────────────── export "doSomething"
  .doSomething()                     call $log ──→ 回调 JS 的 log()

内存共享与数据传递

数字类型可以直接在 JS 和 Wasm 之间传递,但字符串、数组等复杂数据需要通过线性内存:

数字传递(直接):
  JS: instance.exports.add(1, 2)  →  Wasm: (param i32 i32) → 3  →  JS: 3

字符串传递(通过内存):

  JavaScript 侧                       Wasm 线性内存                  Wasm 侧
  ─────────────                       ────────────                  ────────
  1. 编码字符串为 UTF-8 字节
     const encoder = new TextEncoder()
     const bytes = encoder.encode("Hello")

  2. 写入线性内存                        ┌────────────┐
     const mem = new Uint8Array(         │ 0x00: 'H'  │
       instance.exports.memory.buffer    │ 0x01: 'e'  │
     );                                  │ 0x02: 'l'  │← 3. Wasm 读取
     mem.set(bytes, offset);             │ 0x03: 'l'  │   ptr=0, len=5
                                         │ 0x04: 'o'  │
                                         └────────────┘

  4. 调用 Wasm 函数,传递 ptr 和 len
     instance.exports.process(offset, bytes.length)

完整的字符串传递示例:

javascript
const memory = instance.exports.memory;
const alloc = instance.exports.alloc;
const dealloc = instance.exports.dealloc;
const processString = instance.exports.process_string;

function passStringToWasm(str) {
  const encoder = new TextEncoder();
  const encoded = encoder.encode(str);
  const ptr = alloc(encoded.length);
  const mem = new Uint8Array(memory.buffer);
  mem.set(encoded, ptr);
  return { ptr, len: encoded.length };
}

function readStringFromWasm(ptr, len) {
  const mem = new Uint8Array(memory.buffer);
  const slice = mem.slice(ptr, ptr + len);
  return new TextDecoder().decode(slice);
}

const { ptr, len } = passStringToWasm("Hello, Wasm!");
const resultPtr = processString(ptr, len);
dealloc(ptr, len);

SharedArrayBuffer 与多线程

Wasm 支持通过 SharedArrayBuffer 在多个 Worker 之间共享内存:

Main Thread                Worker Thread 1           Worker Thread 2
───────────                ───────────────           ───────────────
     │                           │                         │
     │   SharedArrayBuffer       │                         │
     │  ┌──────────────────────────────────────────────┐  │
     │  │          共享线性内存                          │  │
     │  │  ┌─────┬─────┬─────┬─────┬─────┬─────┐      │  │
     │  │  │  A  │  B  │  C  │  D  │  E  │  F  │      │  │
     ├──│──┴─────┴─────┴─────┴─────┴─────┴─────┘      │──┤
     │  │                                              │  │
     │  │  Atomics API 保证线程安全                      │  │
     │  │  Atomics.load / store / wait / notify         │  │
     │  └──────────────────────────────────────────────┘  │
     │                           │                         │
javascript
const memory = new WebAssembly.Memory({
  initial: 10,
  maximum: 100,
  shared: true
});

const worker = new Worker('worker.js');
worker.postMessage({ memory });

实际应用场景

图像/视频处理 — FFmpeg.wasm

FFmpeg 是最强大的音视频处理工具,FFmpeg.wasm 将其编译为 Wasm,使浏览器内视频转码成为可能:

javascript
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';

const ffmpeg = new FFmpeg();
await ffmpeg.load();

await ffmpeg.writeFile('input.mp4', await fetchFile(videoFile));

await ffmpeg.exec(['-i', 'input.mp4', '-vf', 'scale=640:480', 'output.mp4']);

const data = await ffmpeg.readFile('output.mp4');
const url = URL.createObjectURL(
  new Blob([data.buffer], { type: 'video/mp4' })
);

游戏引擎

Unity 和 Unreal Engine 都支持导出 Wasm:

Unity 导出 WebGL 项目:
┌───────────────────────────────┐
│  Unity C# 代码                │
│       ↓                       │
│  IL2CPP 转为 C++ 代码         │
│       ↓                       │
│  Emscripten 编译为 Wasm       │
│       ↓                       │
│  产物:                        │
│  ├── Build/game.wasm          │
│  ├── Build/game.js            │
│  ├── Build/game.data          │
│  └── index.html               │
└───────────────────────────────┘

加密算法

将 libsodium 等加密库编译为 Wasm,比纯 JS 实现快 5-10 倍:

javascript
import sodium from 'libsodium-wrappers';

await sodium.ready;

const key = sodium.crypto_secretbox_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
const message = sodium.from_string("Secret message");

const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
const decrypted = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);

数据压缩

Zstandard(zstd)等压缩算法的 Wasm 版本:

javascript
import { ZstdInit } from '@aspect-build/zstd-wasm';

const { ZstdStream } = await ZstdInit();

const compressed = ZstdStream.compress(originalData, 3);
const decompressed = ZstdStream.decompress(compressed);

PDF 渲染

Mozilla 的 PDF.js 部分使用 Wasm 加速渲染,MuPDF 也有纯 Wasm 版本:

javascript
import * as mupdf from 'mupdf';

const doc = mupdf.Document.openDocument(pdfBuffer, 'application/pdf');
const page = doc.loadPage(0);
const pixmap = page.toPixmap(
  mupdf.Matrix.identity,
  mupdf.ColorSpace.DeviceRGB
);
const png = pixmap.asPNG();

代码编辑器 — Tree-sitter 语法解析

Tree-sitter 是一个增量解析器,被 GitHub、Zed 编辑器等广泛使用,通过 Wasm 在浏览器中运行:

javascript
import Parser from 'web-tree-sitter';

await Parser.init();
const parser = new Parser();

const JavaScript = await Parser.Language.load('tree-sitter-javascript.wasm');
parser.setLanguage(JavaScript);

const tree = parser.parse('const x = 1 + 2;');
console.log(tree.rootNode.toString());

AI 推理 — ONNX Runtime Web

ONNX Runtime 的 Wasm 后端使浏览器可以运行 ML 模型:

javascript
import * as ort from 'onnxruntime-web';

ort.env.wasm.wasmPaths = '/wasm/';

const session = await ort.InferenceSession.create('model.onnx', {
  executionProviders: ['wasm']
});

const inputTensor = new ort.Tensor('float32', inputData, [1, 3, 224, 224]);
const results = await session.run({ input: inputTensor });
const output = results.output.data;

应用场景全景图

WebAssembly 应用场景

计算密集型                         现有代码移植                     新兴场景
──────────                        ──────────                      ─────────
· 图像滤镜/变换                    · FFmpeg → FFmpeg.wasm          · 边缘计算 (WASI)
· 视频转码/编辑                    · OpenCV → OpenCV.js            · Serverless Functions
· 音频处理/合成                    · SQLite → sql.js               · 插件沙箱系统
· 物理引擎模拟                     · libsodium → sodium.js         · 区块链智能合约
· 3D 渲染/游戏                     · Zstd → zstd-wasm              · IoT 设备
· 加密/哈希/签名                   · MuPDF → mupdf-wasm            · 容器化替代方案
· 科学计算/矩阵运算                · Tree-sitter → wasm            · 多语言通用运行时
· AI/ML 推理                      · Lua/Python → wasm             · 安全沙箱

WASI(WebAssembly System Interface)

超越浏览器

WASI 是 WebAssembly 的系统接口标准,让 Wasm 模块可以在浏览器之外运行,并安全地访问文件系统、网络、环境变量等系统资源:

                        WebAssembly 运行环境

        浏览器内                              浏览器外
       ──────────                           ──────────

  ┌──────────────────┐              ┌──────────────────────┐
  │   Web APIs       │              │   WASI               │
  │                  │              │                      │
  │  · DOM           │              │  · fd_read / write   │
  │  · fetch         │              │  · path_open         │
  │  · Canvas        │              │  · environ_get       │
  │  · WebGL/WebGPU  │              │  · clock_time_get    │
  │  · Web Audio     │              │  · sock_recv / send  │
  │                  │              │                      │
  └────────┬─────────┘              └────────┬─────────────┘
           │                                 │
           ↓                                 ↓
  ┌──────────────────────────────────────────────────────┐
  │               WebAssembly 虚拟机                      │
  │                                                      │
  │   线性内存 │ 指令执行 │ 沙箱隔离 │ 能力安全模型        │
  └──────────────────────────────────────────────────────┘

WASI 的核心理念是能力安全(Capability-based Security)

传统操作系统模型:
  进程启动后 → 拥有用户的所有权限 → 可访问任何文件

WASI 能力安全模型:
  Wasm 模块 → 默认无权限 → 运行时显式授予特定目录/资源的访问能力

  例:
  wasmtime run --dir=/data module.wasm
  (只能访问 /data 目录,其他路径完全不可见)

Wasmer 与 Wasmtime 运行时

主要 Wasm 运行时

┌──────────────┬───────────────────────────────────────────────┐
│   Wasmtime   │  Mozilla 主导,Bytecode Alliance 官方运行时    │
│              │  Cranelift JIT 编译器                          │
│              │  WASI 标准参考实现                              │
│              │  语言:Rust                                    │
├──────────────┼───────────────────────────────────────────────┤
│   Wasmer     │  支持多种编译后端 (Cranelift/LLVM/Singlepass)  │
│              │  wapm 包管理器                                  │
│              │  支持嵌入多种语言 (C/C++/Go/Python/JS...)       │
│              │  语言:Rust                                    │
├──────────────┼───────────────────────────────────────────────┤
│   WasmEdge   │  CNCF 沙箱项目                                │
│              │  针对云原生和边缘计算优化                        │
│              │  支持 AI 推理(TensorFlow/PyTorch)             │
│              │  语言:C++                                     │
├──────────────┼───────────────────────────────────────────────┤
│   wazero     │  纯 Go 实现,零 CGO 依赖                      │
│              │  Go 生态首选嵌入式运行时                        │
│              │  语言:Go                                      │
└──────────────┴───────────────────────────────────────────────┘

使用 Wasmtime 运行 Wasm 模块:

bash
curl https://wasmtime.dev/install.sh -sSf | bash

wasmtime run hello.wasm
wasmtime run --dir=. --env KEY=VALUE app.wasm

使用 Wasmer:

bash
curl https://get.wasmer.io -sSfL | sh

wasmer run hello.wasm
wasmer run --dir=. app.wasm

Wasm 与 Docker 对比

Solomon Hykes(Docker 创始人)曾说:"如果 Wasm+WASI 在 2008 年就存在,我们就不需要创造 Docker 了。"

                Docker Container            Wasm + WASI
                ────────────────            ───────────

启动时间         ~500ms - 数秒               ~1ms - 10ms
镜像体积         数十 MB - 数 GB             数 KB - 数 MB
内存开销         ~50MB+(含 OS 层)           ~数 MB
隔离机制         Linux namespace + cgroup    Wasm 沙箱(语言级隔离)
安全模型         需 root 或特权              能力安全,最小权限
可移植性         依赖 Linux 内核             任何支持 Wasm 的平台
冷启动           慢(需初始化 OS 环境)        极快(直接执行二进制)
生态成熟度       ★★★★★ 极成熟              ★★★☆☆ 快速发展中
未来可能的架构演进:

  当前主流:
  ┌──────────────────────────────┐
  │  Container (Docker)          │
  │  ┌────────────────────────┐  │
  │  │  Guest OS / Runtime    │  │
  │  │  ┌──────────────────┐  │  │
  │  │  │  Application     │  │  │
  │  │  └──────────────────┘  │  │
  │  └────────────────────────┘  │
  │  Host OS + Container Runtime │
  └──────────────────────────────┘

  未来趋势:
  ┌──────────────────────────────┐
  │  Wasm Runtime (Wasmtime)     │
  │  ┌────────────────────────┐  │
  │  │  .wasm Module          │  │  无 Guest OS
  │  │  (数 KB - 数 MB)        │  │  启动仅 ~ms
  │  └────────────────────────┘  │  沙箱隔离
  │  Host OS + WASI              │
  └──────────────────────────────┘

Wasm 性能对比

Wasm vs JavaScript:计算密集型基准测试

以 Fibonacci 递归计算为例(n = 40):

                执行时间(ms,越小越好)

  JavaScript (V8)   ████████████████████████████  ~1200ms
  Wasm (C/O3)       ██████████                    ~450ms
  Wasm (Rust)       █████████                     ~420ms
  原生 C (O3)        ████████                      ~380ms

  Wasm 约为 JavaScript 速度的 2.5-3 倍
  Wasm 约为原生速度的 85-95%

更多场景的性能对比:

场景JavaScriptWasm加速比原因
递归计算(Fibonacci)1200ms450ms2.7xWasm 函数调用开销小
矩阵乘法(1024x1024)3500ms800ms4.4xSIMD + 内存连续访问
图像灰度化(4K)85ms12ms7.1x批量像素操作 + SIMD
SHA-256 哈希(1MB)45ms8ms5.6x位运算密集
JSON 解析(大文件)120ms150ms0.8xV8 对 JSON 高度优化
DOM 操作(1000节点)15ms45ms0.3xWasm 调 JS API 开销大
正则匹配30ms35ms0.9xV8 正则引擎已极度优化

何时该用 Wasm

✅ 适合使用 Wasm 的场景:

  · 计算密集型任务(数学计算、加密、压缩)
  · 需要移植现有 C/C++/Rust 库到 Web
  · 需要稳定可预测的性能(无 GC 暂停、无 JIT 去优化)
  · 大量数值运算(科学计算、物理模拟、AI 推理)
  · 图像/音视频处理
  · 游戏引擎、3D 渲染

❌ 不适合使用 Wasm 的场景:

  · DOM 操作密集的 UI 逻辑(跨边界调用开销大)
  · 简单的业务逻辑和数据处理
  · I/O 密集型操作(网络请求、文件读写受限于浏览器 API)
  · 频繁与 JavaScript 交互的场景
  · 项目团队不熟悉编译型语言
  · V8 已高度优化的操作(JSON 解析、正则表达式)

性能优化建议

1. 减少 JS ↔ Wasm 跨边界调用
   ┌────────────────────────────────────────┐
   │  ❌ 反模式:每个像素调用一次 Wasm       │
   │  for (pixel of pixels)                 │
   │    wasm.processPixel(pixel)            │
   │                                        │
   │  ✅ 正确做法:批量传递到 Wasm 处理       │
   │  wasm.processImage(imagePtr, width, h) │
   └────────────────────────────────────────┘

2. 利用 SIMD 指令
   Wasm SIMD 可以一次处理 4 个 f32 / 16 个 i8
   编译时启用:-msimd128 (Emscripten) / target-feature=+simd128 (Rust)

3. 使用流式编译
   instantiateStreaming 比 instantiate 快 20-30%

4. 缓存编译后的 Module
   const module = await WebAssembly.compileStreaming(fetch('m.wasm'));
   将 module 序列化到 IndexedDB,后续加载直接反序列化

5. 合理设置初始内存
   避免频繁 memory.grow()(每次 grow 可能导致 ArrayBuffer 重新分配)

6. 启用优化编译
   Emscripten: -O3 -flto
   Rust: --release + lto = true

Wasm 进阶特性与提案

重要提案进展

已进入标准 (Phase 4+):
├── MVP (2017)                基本指令集
├── Sign Extension (2019)     符号扩展指令
├── Multi-value (2020)        多返回值
├── Reference Types (2021)    externref / funcref
├── Bulk Memory (2021)        批量内存操作
├── SIMD (2021)               128 位向量指令
└── Tail Call (2023)          尾调用优化

活跃开发中 (Phase 2-3):
├── Exception Handling        异常处理(try/catch)
├── GC                        垃圾回收(支持 Java/Kotlin/Dart)
├── Threads                   原子操作 + 共享内存
├── Component Model           模块组合与接口类型
├── Memory64                  64 位地址空间(突破 4GB 限制)
└── Stack Switching           协程 / async-await 支持

Wasm GC 提案

Wasm GC 提案让 Wasm 原生支持垃圾回收,使 Java、Kotlin、Dart 等 GC 语言可以高效编译到 Wasm:

无 GC 提案时:
  Java → 编译 → .wasm + 自带 GC 运行时 (数 MB)
  ·  产物巨大
  ·  自带 GC 与浏览器 GC 各自为政
  ·  无法与 JS 对象自然交互

有 GC 提案后:
  Java → 编译 → .wasm(使用宿主 GC)
  ·  产物大幅缩小
  ·  复用浏览器高度优化的 GC
  ·  可直接引用 JS 对象

Component Model

Component Model 是 Wasm 模块化和互操作的未来方向:

当前:模块只能交换数字和线性内存字节

Component Model 之后:

  Component A (Rust)                Component B (Python)
  ┌──────────────────┐              ┌──────────────────┐
  │  interface:      │              │  interface:      │
  │    process(      │              │    format(       │
  │      records:    │     WIT      │      data:       │
  │      list<record>│ ←─────────→  │      string      │
  │    ) -> string   │  Interface   │    ) -> string   │
  │                  │   Types      │                  │
  └──────────────────┘              └──────────────────┘

  WIT (Wasm Interface Type):
  · 定义高级类型:string, list, record, variant, enum
  · 跨语言类型安全互操作
  · 无需手动管理线性内存传递

调试与开发体验

Chrome DevTools 调试 Wasm

1. 启用 DWARF 调试信息:
   Emscripten: emcc -g source.c -o output.js
   Rust: cargo build (debug 模式默认包含)

2. 安装 C/C++ DevTools Support 扩展
   Chrome Web Store 搜索安装

3. DevTools 中的 Wasm 调试能力:
   ┌──────────────────────────────────────────┐
   │  Sources 面板                             │
   │  ├── 查看 .wat 文本格式                   │
   │  ├── 设置断点                             │
   │  ├── 单步执行                             │
   │  ├── 查看局部变量                         │
   │  └── 查看线性内存(Memory Inspector)     │
   │                                          │
   │  Performance 面板                         │
   │  ├── Wasm 函数执行时间                    │
   │  └── JS ↔ Wasm 调用开销                   │
   │                                          │
   │  带 DWARF 信息时:                        │
   │  ├── 映射回原始 C/Rust 源码               │
   │  ├── 查看原始变量名和类型                  │
   │  └── 在原始源码中设置断点                  │
   └──────────────────────────────────────────┘

产物体积优化

减小 .wasm 文件体积的策略:

1. 编译器优化
   · Emscripten: -Oz(最小体积优化)
   · Rust: opt-level = "z" + lto = true

2. wasm-opt 后处理
   wasm-opt -Oz -o output.wasm input.wasm
   (通常能再减少 10-30% 体积)

3. Rust 特定优化
   [profile.release]
   opt-level = "z"
   lto = true
   codegen-units = 1
   panic = "abort"
   strip = true

4. Tree Shaking
   只导出必要的函数
   避免引入不需要的库功能

5. 压缩传输
   服务端启用 gzip / brotli 压缩
   .wasm 文件压缩比通常在 60-70%

体积参考:
  空模块                        ~8 bytes
  简单 add 函数                  ~40 bytes
  Rust hello world              ~2 KB (优化后)
  图像处理库                     ~50-200 KB
  FFmpeg.wasm (完整)            ~25 MB

面试高频问题

问题一:WebAssembly 是什么?它和 JavaScript 是什么关系?

回答思路

WebAssembly 是一种低级二进制指令格式,设计为 Web 上高性能代码的编译目标。它不是要替代 JavaScript,而是作为补充——JS 擅长 UI 交互和业务逻辑,Wasm 擅长计算密集型任务。两者可以双向互调,共享同一运行时环境。

追问:为什么 Wasm 比 JS 快?

关键差异有三点:1)Wasm 是预编译的静态类型二进制,省去了 JS 的解析、类型推断和 JIT 优化/去优化过程;2)Wasm 的指令格式紧凑,解码速度极快;3)Wasm 没有 GC 暂停,性能更可预测。但注意在 DOM 操作等场景中 JS 反而更快,因为 Wasm 需要跨边界调用。

问题二:Wasm 的线性内存模型是怎么工作的?JS 和 Wasm 之间如何传递复杂数据?

回答思路

Wasm 使用一段连续的字节数组(Linear Memory),以 64KB 的 page 为单位分配和增长。在 JS 侧,它表现为一个 ArrayBuffer。数字可以直接通过函数参数传递,但字符串、数组等复杂数据需要先写入线性内存(通过 TypedArray 视图),然后传递指针和长度给 Wasm 函数。读取结果时同理,从内存中按指针和长度切片后解码。

追问:内存安全如何保证?

Wasm 运行时对每次内存访问都做边界检查,如果访问超出线性内存的当前大小就会触发 trap(运行时异常),而不会访问到沙箱之外的宿主内存。这是 Wasm 安全模型的重要一环。

问题三:描述一下 Wasm 模块从编译到执行的完整流程

回答思路

  1. 编写源代码:用 C/C++/Rust/Go 等语言编写功能逻辑
  2. 离线编译:通过 Emscripten/wasm-pack 等工具链编译为 .wasm 二进制
  3. 网络传输:浏览器通过 fetch 下载 .wasm 文件
  4. 编译:浏览器 Wasm 引擎将二进制编译为机器码(推荐 instantiateStreaming 流式编译)
  5. 实例化:结合 importObject 创建模块实例,链接导入的函数和内存
  6. 调用:通过 instance.exports 调用导出的函数

追问:为什么推荐 instantiateStreaming?

因为它可以边下载边编译,下载和编译是并行的。而 instantiate 需要先下载完整个文件到 ArrayBuffer,再开始编译,是串行的。在大模块场景下 streaming 版本可以快 20-30%。

问题四:什么场景适合用 Wasm?什么场景不适合?

回答思路

适合:计算密集型任务(加密、压缩、图像处理、物理模拟、AI 推理)、需要移植现有 C/C++/Rust 库到 Web、对性能稳定性要求高的场景。

不适合:DOM 操作密集的 UI 逻辑(跨边界调用开销大于收益)、简单业务逻辑(JS 已经足够快)、I/O 密集型操作、V8 已高度优化的操作(JSON 解析、正则匹配)。

核心判断标准:如果计算时间远大于 JS↔Wasm 跨边界调用的开销,就值得使用 Wasm。

问题五:WASI 是什么?它有什么意义?

回答思路

WASI(WebAssembly System Interface)是一套标准化的系统接口,让 Wasm 模块可以在浏览器之外运行,安全地访问文件系统、网络等操作系统能力。它采用能力安全(Capability-based Security)模型——模块默认无权限,运行时显式授予特定资源的访问能力。

WASI 的意义在于将 Wasm "一次编译到处运行"的理念扩展到了服务端,被认为可能成为轻量级容器的替代方案。Docker 创始人甚至说过"如果 Wasm+WASI 早几年出现,Docker 就不需要被发明了"。

追问:相比 Docker,Wasm 有什么优势和不足?

优势:启动速度快数百倍(ms 级 vs 秒级)、镜像体积小几个数量级(KB-MB 级 vs MB-GB 级)、沙箱隔离更安全(语言级 vs OS 级)。不足:生态还不够成熟、WASI 标准仍在演进、不能运行任意 Linux 二进制。

问题六:如何优化 Wasm 应用的性能?

回答思路

六个方向:

  1. 减少跨边界调用:批量传递数据而非逐个调用
  2. 利用 SIMD:启用 SIMD 指令一次处理多个数据
  3. 流式编译:使用 instantiateStreaming
  4. 缓存 Module:将编译后的 Module 存入 IndexedDB
  5. 预分配内存:设置足够的 initial memory,避免频繁 grow
  6. 编译优化:启用 -O3/-Oz、LTO、wasm-opt 后处理

追问:为什么 memory.grow 开销大?

因为每次 grow 可能导致底层 ArrayBuffer 被重新分配到更大的连续内存空间。在 JS 侧,旧的 TypedArray 视图会失效,需要重新创建。如果使用 SharedArrayBuffer,情况更复杂,因为不能原地扩展共享内存。

问题七:Wasm 如何实现多线程?

回答思路

Wasm 多线程基于 SharedArrayBuffer + Web Workers。主线程和 Worker 线程共享同一块 Wasm 线性内存(创建 Memory 时设置 shared: true),通过 Atomics API(load、store、wait、notify)实现线程同步。Emscripten 的 pthreads 支持可以将 C/C++ 的多线程代码直接编译为 Wasm 多线程。

注意 SharedArrayBuffer 因为 Spectre 漏洞曾被浏览器禁用,现在需要设置跨域隔离头(COOP + COEP)才能使用。

问题八:Wasm GC 提案解决了什么问题?

回答思路

在 Wasm GC 提案之前,Java、Kotlin、Dart 等需要 GC 的语言编译到 Wasm 时,必须将整个 GC 运行时一起打包,导致产物体积巨大(可能数 MB 的 GC 代码),而且自带 GC 和浏览器 JS GC 互相独立,无法高效互操作。

Wasm GC 提案在 Wasm 指令集中引入了 struct、array 等堆类型,让 Wasm 模块可以直接使用宿主环境的 GC。好处:产物体积大幅缩小、复用浏览器高度优化的 GC、可以自然地与 JS 对象交互。Google 的 Dart/Flutter Web 和 JetBrains 的 Kotlin/Wasm 是最积极的推动者。


延伸阅读

最后更新于:

用心学习,用代码说话 💻