Skip to content

WebAssembly 速查表

用于在 Web 上快速执行的二进制指令格式。

01

入门

在 JS 中加载 Wasm

WebAssembly (Wasm) 是一种二进制格式,在浏览器中以接近原生的速度运行。instantiateStreaming 加载并编译 .wasm 文件。导出的是可从 JavaScript 调用的函数。

webassembly
// Load and instantiate a .wasm module
WebAssembly.instantiateStreaming(fetch('module.wasm'))
  .then(result => {
    const { instance } = result;
    // call exported functions
    console.log(instance.exports.add(2, 3)); // 5
  });

// Alternative: from ArrayBuffer
const bytes = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
WebAssembly.instantiate(bytes).then(result => {
  console.log(result.instance.exports);
});

WebAssembly 模块与实例

WebAssembly.Module 是编译后的无状态表示,可在多个实例间安全复用。WebAssembly.Instance 持有运行时状态(内存、全局变量)。编译一次、多次实例化的方式非常适合生成并行 worker。

webassembly
// Two-step: compile then instantiate
const module = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(module, importObject);
console.log(instance.exports.add(1, 2));

// Module is stateless & shareable across instances
const instance2 = await WebAssembly.instantiate(module, importObject);
// Each instance has its own memory and globals

浏览器支持与特性检测

WebAssembly 1.0 在所有现代浏览器中均受支持。较新的提案(SIMD、异常、线程、GC)需要特性检测。WebAssembly.validate 检查字节是否构成有效模块而不进行编译。

webassembly
// Check basic WebAssembly support
if (typeof WebAssembly !== 'undefined') {
  console.log('WebAssembly is supported');
}

// Verify the engine can actually compile
WebAssembly.validate(new Uint8Array([
  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
])); // true if valid module

// Feature-detect newer proposals
const hasSIMD = typeof WebAssembly.Module !== 'undefined';
const hasExceptions = 'exceptions' in WebAssembly;

流式与非流式

instantiateStreaming 在网络流式传输时编译模块,启动更快。服务器必须发送 'Content-Type: application/wasm'。如果 CDN 或静态主机没有该头,则需回退到 ArrayBuffer 路径。

webassembly
// Streaming (preferred): compiles while downloading
WebAssembly.instantiateStreaming(fetch('m.wasm'), imports);

// Non-streaming: must download fully first
const res = await fetch('m.wasm');
const buf = await res.arrayBuffer();
WebAssembly.instantiate(buf, imports);

// Streaming requires correct MIME type:
//   Content-Type: application/wasm

Wasm 魔数与文件头

Wasm 二进制魔数是 ASCII 字符串 '\0asm',后跟 4 字节小端版本号。该文件头用于在编译前验证文件是否为 Wasm。格式按节组织(Type、Import、Function、Memory 等)。

webassembly
// Every .wasm file starts with this 8-byte header:
//   0x00 0x61 0x73 0x6d  -> "\0asm"
//   0x01 0x00 0x00 0x00  -> version 1
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];

// Quick validity check
function isWasm(bytes) {
  return bytes[0] === 0x00 && bytes[1] === 0x61
      && bytes[2] === 0x73 && bytes[3] === 0x6d;
}

Hello Wasm(最小示例)

一个最小 Wasm 应用:模块从 JS 导入 'log' 函数并导出 'greet' 调用它。WAT (WebAssembly Text) 格式是人类可读的表示,用 wat2wasm 编译为二进制。浏览器只执行二进制形式。

webassembly
<!-- index.html -->
<script>
  WebAssembly.instantiateStreaming(fetch('hello.wasm'))
    .then(({ instance }) => {
      instance.exports.greet(); // logs "Hello, Wasm!"
    });
</script>

;; hello.wat (compiled to hello.wasm via wat2wasm)
(module
  (import "console" "log" (func $log (param i32)))
  (func (export "greet")
    i32.const 72   ;; 'H'
    call $log
  )
)
02

WAT 文本格式

模块与 S-表达式

WAT (WebAssembly Text 格式) 是 Wasm 的文本表示。一切都被包裹在 (module ...) S-表达式中。指令以栈式顺序书写:先压入操作数,再由操作消费它们。

webassembly
;; WAT uses s-expressions: (keyword args...)
(module
  ;; function definitions, exports, etc. go here
)

;; Nested expressions form the instruction sequence
(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
  (export "add" (func $add))
)

WAT 中的注释

WAT 支持两种注释样式:';;' 用于单行,'(; ... ;)' 用于块注释。块注释可以嵌套并跨越多行,适合在开发时禁用大段代码。

webassembly
;; Single-line comment (starts with ;;)
(func (export "f")
  i32.const 1  ;; push 1
  drop         ;; discard
)

(; Block comment
   can span
   multiple lines ;)
(func (export "g")
  nop
)

函数定义语法

函数用 (func ...) 声明。参数使用 (param $name type),结果使用 (result type)。'$name' 是可选的,没有它时函数/局部变量通过数字索引引用。多值返回需要 multi-value 提案(已广泛支持)。

webassembly
;; Named function with params and result
(func $name (param $p1 i32) (param $p2 f64) (result i32)
  ;; body
)

;; Anonymous function (referenced by index)
(func (param i32) (result i32)
  local.get 0
)

;; Multiple results (multi-value proposal)
(func $swap (param i32 i32) (result i32 i32)
  local.get 1
  local.get 0
)

内联导出与导入

WAT 支持在函数、内存、表和全局变量上使用内联 (export "name") 和 (import "module" "field") 注解,是展开 (export ...)/(import ...) 形式的简写。两者编译为相同的二进制输出。

webassembly
;; Inline export on a function
(func (export "add") (param i32 i32) (result i32)
  local.get 0
  local.get 1
  i32.add
)

;; Inline import
(func $log (import "env" "log") (param i32))

;; Inline export on memory
(memory (export "mem") 1)

;; Expanded form (equivalent)
(func $add (param i32 i32) (result i32) ...)
(export "add" (func $add))

数据段

数据段在实例化时初始化线性内存(active),或通过 memory.init 懒加载(passive)。字符串包含转义序列,如 \00 表示空终止符、\n 表示换行。data.drop 在复制后释放段的存储。

webassembly
;; Initialize memory with bytes at offset 0
(memory 1)
(data (i32.const 0) "Hello, Wasm!\00")

;; Named data segment (active)
(data $msg (i32.const 0) "Hello\00")

;; Passive data segment (used with memory.init)
(data $passive "lazy-loaded bytes")

;; Drop a data segment to free its memory
(data.drop $msg)

wat2wasm 与 wasm2wat 转换

WABT (WebAssembly Binary Toolkit) 提供 wat2wasm(文本→二进制)、wasm2wat(二进制→文本)、wasm-validate 和 wasm-objdump。这些工具对检查、调试和手写 Wasm 模块至关重要。.wat 形式面向人类,.wasm 形式才是浏览器执行的。

webassembly
# Install the WebAssembly Binary Toolkit (WABT)
# macOS:  brew install wabt
# Ubuntu: apt install wabt

# Convert text -> binary
wat2wasm hello.wat -o hello.wasm

# Convert binary -> text (disassemble)
wasm2wat hello.wasm -o hello.wat

# Validate a .wasm file
wasm-validate hello.wasm

# Print module structure
wasm-objdump -x hello.wasm
03

值类型

基本值类型 (i32/i64/f32/f64)

Wasm 只有四种基本值类型:i32、i64、f32、f64。整数的符号性由操作决定(如 i32.div_s 与 i32.div_u),而非类型。这让类型系统保持最小化且可预测。

webassembly
;; Wasm has exactly four basic value types:
;;   i32  - 32-bit integer
;;   i64  - 64-bit integer
;;   f32  - 32-bit float
;;   f64  - 64-bit float

(func (param i32) (param i64) (param f32) (param f64)
  (result f64)
  local.get 2   ;; f32
  f64.promote_f32
)

;; Integers are signed or unsigned PER OPERATION,
;; not per type. Use i32.add_s / i32.div_u etc.

WAT 中的数字字面量

WAT 中的数字字面量支持十进制、十六进制 (0x) 和二进制 (0b) 形式。浮点数支持科学计数法、无穷大 (inf) 和 NaN。WAT 字面量默认无符号,负整数用前导 '-' 编码。

webassembly
;; Integer literals
i32.const 42
i32.const -7
i32.const 0xff       ;; hex
i32.const 0b1010     ;; binary

;; Float literals
f64.const 3.14
f64.const 1e10
f64.const -0.0
f64.const inf
f64.const nan

;; 64-bit
i64.const 9007199254740992  ;; 2^53

类型转换

转换使用一致的命名方案:extend(小→大)、wrap(大→小)、convert(整型↔浮点)、reinterpret(位级转换)、demote/promote(浮点精度)。有符号变体用 _s,无符号用 _u。

webassembly
;; Extend i32 to i64
i64.extend_i32_s      ;; sign-extend
i64.extend_i32_u      ;; zero-extend

;; Wrap i64 to i32
i32.wrap_i64

;; Integer <-> Float
f32.convert_i32_s
f64.convert_i64_u
i32.trunc_f32_s       ;; truncate (toward zero)
i32.reinterpret_f32   ;; bit-level reinterpret

;; Demote / promote floats
f32.demote_f64
f64.promote_f32

v128 SIMD 类型

v128 类型将 16 字节打包进单个 SIMD 寄存器。通道按 i32x4(4×int32)、f32x4(4×float32)、i16x8、i8x16、f64x2 寻址。SIMD 通过每条指令处理多个值来加速图像/音频/加密工作负载。

webassembly
;; SIMD: 128-bit vector type (fixed-width SIMD proposal)
(module
  (func (param v128) (result v128)
    local.get 0
    v128.not            ;; bitwise NOT
  )
  (func (result v128)
    v128.const i32x4 1 2 3 4
  )
)

;; Vector operations: i32x4, i16x8, i8x16, f32x4, f64x2
v128.const f32x4 1.0 2.0 3.0 4.0
i32x4.add

引用类型 (funcref/externref)

引用类型提案添加了 funcref(函数指针)和 externref(不透明主机引用)。externref 功能强大:让 Wasm 持有 JS 对象而无需复制或序列化。与表结合,可实现灵活的回调模式。

webassembly
;; Reference types proposal adds:
;;   funcref   - reference to a function (in a table)
;;   externref - opaque reference to a host (JS) object

(module
  (table 1 funcref)
  (func (param externref) (result externref)
    local.get 0    ;; pass through a JS object
  )
)

;; In JS, externref lets you pass arbitrary objects
;; through Wasm without serialization:
instance.exports.store({ hello: 'world' });
04

函数

定义与调用函数

函数是代码的基本单元。call 通过名称/索引调用函数。参数在调用前压入栈,结果在函数返回时留在栈上。栈必须匹配 (result ...) 声明。

webassembly
(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
  (func $double (param $x i32) (result i32)
    local.get $x
    local.get $x
    i32.add      ;; or: i32.const 2  i32.mul
  )
  (func (export "use")
    i32.const 5
    i32.const 7
    call $add     ;; -> 12 on stack
    drop
  )
)

参数与局部变量

参数是从 0 开始索引的不可变局部变量。声明的局部变量 (local $name type) 被零初始化且可变。使用 local.get、local.set 和 local.tee(设置并留在栈上)。局部变量是内存/全局变量之外 Wasm 唯一的可变存储。

webassembly
(func $f (param $a i32) (param $b i32) (result i32)
  (local $sum i32)         ;; mutable local
  (local $i i32)
  local.get $a
  local.get $b
  i32.add
  local.set $sum           ;; store into local
  i32.const 0
  local.set $i
  local.get $sum
)

;; tee: set and keep on stack
local.tee $sum             ;; $sum = top; stays on stack

多返回值

multi-value 提案允许函数返回多个值(并接受零个)。自 2020 年起所有主流引擎都支持。这消除了通过内存传递出参的需要,使 swap/minmax 等惯用法变得自然。

webassembly
;; Multi-value: return two i32 values
(func $minmax (param $a i32) (param $b i32) (result i32 i32)
  local.get $a
  local.get $b
  i32.lt_s
  if (result i32 i32)
    local.get $a
    local.get $b
  else
    local.get $b
    local.get $a
  end
)

;; Caller receives both on the stack
(func (export "use")
  i32.const 3
  i32.const 8
  call $minmax
  ;; stack: [min=3, max=8]
  drop drop
)

递归

Wasm 支持直接递归——函数可以调用自身。MVP Wasm 没有尾调用优化(tail-call 提案添加了 return_call)。深递归有栈溢出风险,因此生产代码优先使用迭代循环。

webassembly
(module
  (func $fact (param $n i32) (result i32)
    local.get $n
    i32.const 1
    i32.le_s
    if (result i32)
      i32.const 1
    else
      local.get $n
      local.get $n
      i32.const 1
      i32.sub
      call $fact     ;; recursive call
      i32.mul
    end
  )
  (export "factorial" (func $fact))
)

内联导出与命名调用

函数定义上的内联 (export "name") 是独立 (export ...) 声明的简写。call_ref(引用类型提案)直接通过 funcref 调用函数,无需表查找即可实现一等函数值。

webassembly
(module
  ;; Inline export on definition
  (func $add (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add
  )

  ;; call_ref: call via funcref (reference types)
  (func $caller (param $f funcref) (param i32) (result i32)
    local.get 1
    local.get 0
    call_ref (param i32) (result i32)
  )
)

Start 函数

模块可以声明一个 (start $func) 函数,在实例化时自动运行——用于初始化。它不接受参数也不返回值。每个模块只允许一个 start 函数。

webassembly
(module
  (func $init
    ;; runs once at instantiation
    i32.const 0
    i32.const 42
    i32.store
  )
  (start $init)        ;; declare as start function
  (memory (export "mem") 1)
)

;; The start function runs automatically after
;; instantiation, before instantiate() resolves.
;; It takes no params and returns nothing.
05

内存

声明线性内存

线性内存是一个连续的类似 ArrayBuffer 的区域,按 64KiB 页增长。(memory 1 10) 声明 initial=1、max=10。内存是在 Wasm 和 JS 之间传递批量数据(字符串、数组)的唯一方式。导出它以便从 JS 访问。

webassembly
(module
  ;; 1 page = 64KiB. Declare 1 page, max 10.
  (memory $mem 1 10)
  (export "memory" (memory $mem))

  ;; JS-side: read/write the exported memory
  ;; const buf = new Uint8Array(instance.exports.memory.buffer);
)

内存扩容

memory.grow 添加页并返回之前的大小(页数),失败时返回 -1。关键的是,扩容会分离底层的 ArrayBuffer——所有现有的 TypedArray 视图将不可用。在任意 grow 调用后必须重新创建视图。

webassembly
(func $grow (result i32)
  i32.const 1            ;; request 1 more page
  memory.grow            ;; returns previous size (or -1)
)

;; In JS, growing detaches the old buffer!
const prev = instance.exports.grow();
const buf = new Uint8Array(instance.exports.memory.buffer);
// OLD buf references are now detached — re-create views!

加载与存储操作

load/store 按大小(8/16/32/64 位)和符号扩展(_s/_u)有变体。offset=N 向地址操作数添加常量;align= 是提示(必须 ≤ 大小)。所有地址都是线性内存中的字节偏移。

webassembly
;; Load: read from memory at address (top of stack)
i32.load  offset=0 align=2     ;; 4-byte aligned load
i32.load8_u                    ;; 1 byte, zero-extended
i32.load16_s                   ;; 2 bytes, sign-extended
f64.load                       ;; 8-byte float load

;; Store: write value at address
i32.const 100        ;; address
i32.const 42         ;; value
i32.store            ;; mem[100] = 42

i32.const 100
i32.const 0xFF
i32.store8           ;; write 1 byte

内存大小与对齐

Wasm 中的对齐是性能提示,而非正确性约束——非对齐访问总是有效。'align' 值不得超过操作的自然大小。memory.size 返回当前页数(每页 64KiB)。

webassembly
;; Each load/store has a natural alignment:
;;   i32.load   -> 4 bytes
;;   i64.load   -> 8 bytes
;;   f32.load   -> 4 bytes
;;   i32.load8  -> 1 byte

;; Explicit alignment hint (does not affect correctness)
i32.load align=2    ;; may be slower but still works

;; memory.size returns current pages
(func $pages (result i32)
  memory.size
)

共享内存与原子操作

共享内存(threads 提案)让多个 Wasm 实例/Worker 共享一个内存以实现并行。原子操作(i32.atomic.load/store/rmw.add/cmpxchg)提供同步。在浏览器中需要跨源隔离(COOP/COEP 头)。

webassembly
(module
  ;; Shared memory (threads proposal)
  (memory 1 1 shared)
  (export "memory" (memory 0))

  ;; Atomic operations
  (func (param i32 i32)
    local.get 0       ;; address
    local.get 1       ;; value
    i32.atomic.store
  )
  (func (param i32) (result i32)
    local.get 0
    i32.atomic.load
  )
  (func (param i32) (result i32)
    local.get 0
    i32.const 1
    i32.atomic.rmw.add   ;; atomic read-modify-write
  )
)

从内存读取字符串

Wasm 没有原生字符串类型——字符串是线性内存中的字节序列,通常作为(指针,长度)对传递。TextEncoder/TextDecoder 处理 UTF-8 转换。写入前务必确保内存区域足够大。

webassembly
// Wasm has no string type — strings live in linear memory.
// Decode UTF-8 from a (ptr, len) pair returned by Wasm:

function readString(memory, ptr, len) {
  const bytes = new Uint8Array(memory.buffer, ptr, len);
  return new TextDecoder('utf-8').decode(bytes);
}

// Writing a string into Wasm memory:
function writeString(memory, str, ptr) {
  const bytes = new TextEncoder().encode(str);
  const view = new Uint8Array(memory.buffer, ptr, bytes.length);
  view.set(bytes);
  return bytes.length; // return length
}
06

声明表

表是引用数组(通常是 funcref),用于间接调用和动态分派。与内存(持有字节)不同,表持有 Wasm 无法伪造的不透明引用。(elem ...) 段在实例化时初始化表槽。

webassembly
(module
  ;; A table of function references, initial size 1
  (table 1 funcref)
  (export "table" (table 0))

  ;; With max size
  (table $t 4 16 funcref)

  ;; elem segment: initialize table slots
  (elem (i32.const 0) $fn1 $fn2 $fn3)
  (func $fn1)
  (func $fn2)
  (func $fn3)
)

funcref 表与 call_indirect

call_indirect 通过索引从表项分派调用。(type ...) 声明是必需的——Wasm 在运行时检查函数签名是否匹配(签名不匹配会陷入)。这是 Wasm 中 vtable 和面向对象分派的基础。

webassembly
(module
  (type $bin (func (param i32 i32) (result i32)))
  (table 3 funcref)

  (func $add (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.add)
  (func $sub (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.sub)
  (func $mul (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.mul)

  (elem (i32.const 0) $add $sub $mul)

  (func (export "dispatch") (param i32 i32 i32) (result i32)
    local.get 0      ;; arg a
    local.get 1      ;; arg b
    local.get 2      ;; table index
    call_indirect (type $bin)
  )
)

表的扩容、读取与设置

table.grow 追加槽并返回之前的大小(或 -1)。table.get/table.set 按索引读/写条目。扩容表支持运行时注册回调。初始大小 + max 限制扩容以防止失控分配。

webassembly
(func $addSlot (param funcref) (result i32)
  local.get 0
  i32.const 1            ;; grow by 1
  table.grow             ;; returns old size, or -1
)

(func $read (param i32) (result funcref)
  local.get 0
  table.get
)

(func $write (param i32 funcref)
  local.get 0     ;; index
  local.get 1     ;; funcref
  table.set
)

externref 表用于宿主对象

externref 表存储对宿主(JS)对象的不透明引用——Wasm 可以持有并传递它们,但无法检查其内容。非常适合回调注册、JS 句柄表,以及将面向对象的 JS API 桥接到 Wasm。

webassembly
(module
  ;; Table holding opaque JS objects
  (table 8 externref)
  (export "table" (table 0))

  (func $store (param i32 externref)
    local.get 0
    local.get 1
    table.set
  )
  (func $load (param i32) (result externref)
    local.get 0
    table.get
  )
)

// JS: pass arbitrary objects
instance.exports.store(2, { callback: () => 42 });

动态分派模式

动态分派模式:将操作存储在表中,然后按索引 call_indirect。结合强制的类型检查,提供安全的 vtable 风格多态。Rust 枚举、C++ 虚函数和解释器都以此实现分派。

webassembly
(module
  (type $op (func (param i32 i32) (result i32)))
  (table 4 funcref)
  (elem (i32.const 0) $add $sub $mul $div)

  (func $add (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.add)
  (func $sub (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.sub)
  (func $mul (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.mul)
  (func $div (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.div_s)

  (func (export "apply") (param $op i32) (param $a i32) (param $b i32)
    (result i32)
    local.get $a
    local.get $b
    local.get $op
    call_indirect (type $op)
  )
)
07

全局变量

声明全局变量

全局变量是模块级的单值,可以是不可变(默认)或可变(mut)。它们在函数调用间持久存在,用于计数器、配置标志和常量。可变全局变量是内存/表之外唯一的共享可变状态。

webassembly
(module
  ;; Immutable global (const)
  (global $counter (mut i32) (i32.const 0))

  ;; Read-only global
  (global $pi f64 (f64.const 3.14159265))

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

可变与不可变全局变量

没有 'mut',全局变量不可变,global.set 是编译错误。初始化器必须是常量表达式(如 i32.const、ref.null、不可变导入全局变量的 global.get)。默认选择不可变以确保安全。

webassembly
(module
  ;; (global $name type init)        -> immutable
  ;; (global $name (mut type) init)  -> mutable

  (global $version i32 (i32.const 3))
  ;; global.set $version  ;; ERROR: immutable

  (global $state (mut i32) (i32.const 0))
  (func (export "toggle")
    global.get $state
    i32.eqz
    if
      i32.const 1  global.set $state
    else
      i32.const 0  global.set $state
    end
  )
)

导入全局变量

导入的全局变量让宿主在实例化时将配置值(或 WebAssembly.Global 对象)注入模块。不可变的导入全局变量可以初始化其他全局变量。WebAssembly.Global 是可变共享全局变量的 JS 可见包装器。

webassembly
(module
  ;; Import a global from JS
  (global $env (import "env" "version") i32)

  ;; Mutable imported global
  (global $flag (import "env" "flag") (mut i32))

  (func (export "ver") (result i32)
    global.get $env
  )
)

// JS side
const imports = {
  env: {
    version: 3,
    flag: new WebAssembly.Global({ value: 'i32', mutable: true }, 0)
  }
};

导出与共享全局变量

导出的全局变量在 JS 中作为 WebAssembly.Global 对象访问,具有 .value 属性(可读,如果可变则可写)。在多个模块实例间共享 Global 对象是不触碰内存而协调状态的简洁方式。

webassembly
(module
  (global $g (export "counter") (mut i32) (i32.const 0))
  (func (export "inc")
    global.get $g
    i32.const 1
    i32.add
    global.set $g
  )
)

// JS reads/writes the global directly
const g = instance.exports.counter;
g.value;        // 0
instance.exports.inc();
g.value;        // 1
g.value = 100;  // write directly

全局变量的用例

全局变量适合三种模式:(1) 导入的配置常量,(2) 跨实例共享的持久计数器/状态,(3) 缓存的计算结果。对于单值它们比内存更简单,如果通过 JS 共享则对多个实例可见。

webassembly
(module
  ;; Configuration constants
  (global $maxSize (import "config" "maxSize") i32)
  (global $debug   (import "config" "debug")   i32)

  ;; Persistent counters
  (global $calls (mut i32) (i32.const 0))

  (func (export "handle") (result i32)
    global.get $calls
    i32.const 1
    i32.add
    global.set $calls
    global.get $calls
  )
)
08

导入与导出

导入函数

导入让 Wasm 调用宿主(JS)函数。每个导入由两级名称(module, field)标识。传递给 instantiate 的导入对象必须匹配这些名称。导入是 Wasm 触达外部世界的方式——I/O、日志、调度。

webassembly
(module
  ;; Import a function under module.field naming
  (func $log (import "console" "log") (param i32))
  (func $now (import "env" "now") (result f64))

  (func (export "run")
    i32.const 42
    call $log        ;; -> console.log(42)
    call $now
    drop
  )
)

// JS
WebAssembly.instantiateStreaming(fetch('m.wasm'), {
  console: { log: x => console.log('wasm:', x) },
  env:     { now: () => performance.now() }
});

导入内存

导入内存让多个 Wasm 模块(和 JS)共享一个内存区域——Wasm '库'的基础。一个模块分配并写入;另一个读取。这在不复制数据的情况下组合模块,是组件模型的先驱。

webassembly
(module
  (memory (import "env" "memory") 1)
  (func (export "writeHi")
    i32.const 0
    i32.const 72     ;; 'H'
    i32.store8
  )
)

// Shared memory across multiple modules:
const memory = new WebAssembly.Memory({ initial: 1 });
WebAssembly.instantiateStreaming(fetch('a.wasm'), { env: { memory } });
WebAssembly.instantiateStreaming(fetch('b.wasm'), { env: { memory } });
// Both modules read/write the SAME memory

导入全局变量与表

全局变量和表也可以导入,实现跨模块的共享状态和函数指针表。WebAssembly.Table 是 JS 构造器。导入可变全局变量让宿主在不重建模块的情况下动态影响 Wasm 行为。

webassembly
(module
  (global $seed (import "env" "seed") i32)
  (table (import "env" "table") 4 funcref)

  (func (export "rand") (result i32)
    global.get $seed
    i32.const 1103515245
    i32.mul
    i32.const 12345
    i32.add
    ;; (truncated LCG step)
  )
)

// JS provides the imports
const imports = {
  env: {
    seed: 42,
    table: new WebAssembly.Table({
      element: 'funcref', initial: 4, maximum: 16
    })
  }
};

导出函数、内存、表

模块中声明的任何内容都可以导出:函数、内存、表、全局变量。导出成为 instance.exports 的属性。内存导出让 JS 查看线性堆;全局变量导出暴露 WebAssembly.Global 对象;表导出暴露函数引用。

webassembly
(module
  (memory 1)
  (table 2 funcref)
  (global $c (mut i32) (i32.const 0))

  (func (export "add") (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.add)
  (export "memory" (memory 0))
  (export "table"  (table 0))
  (export "counter" (global $c))
)

// JS usage
const { add, memory, table, counter } = instance.exports;
add(2, 3);                              // 5
new Uint8Array(memory.buffer);          // raw bytes
table.get(0);                           // funcref
counter.value;                          // 0

内联导入与导出

内联 (import "m" "f") 和 (export "name") 注解是语法糖——编译为与独立 (import ...)/(export ...) 形式相同的二进制。单个定义可以有内联导出,但导入项的导入和导出是分开声明的。

webassembly
(module
  ;; Inline import (compact form)
  (func $log (import "env" "log") (param i32))

  ;; Inline export on function
  (func (export "add") (param i32 i32) (result i32)
    local.get 0  local.get 1  i32.add
  )

  ;; Inline export on memory
  (memory (export "mem") 1)

  ;; Inline import + export combined is NOT allowed;
  ;; a definition imports OR exports, not both.
)

导入对象最佳实践

结构化导入对象以匹配模块的 (module, field) 名称。在实例化前验证必需的导入以给出清晰错误。在适当位置用 try/catch 包装宿主函数——导入内部未捕获的抛出会使 Wasm 调用者陷入。

webassembly
// Build a typed import object
const imports = {
  env: {
    log:       (i) => console.log('[wasm]', i),
    now:       () => performance.now(),
    abort:     () => { throw new Error('wasm abort'); },
    memory:    new WebAssembly.Memory({ initial: 1 }),
    seed:      42,
  }
};

// Validate imports before instantiation:
function checkImports(imports, required) {
  for (const [mod, fields] of Object.entries(required)) {
    for (const f of fields) {
      if (!imports[mod]?.[f] === undefined) {
        throw new Error(`Missing import: ${mod}.${f}`);
      }
    }
  }
}
09

控制流

block

block 创建一个带标签的作用域,分支通过 br 退出。Wasm 中的分支是结构化的——只能跳转到外层 block/loop/if 的末尾。带 (result ...) 的 block 在分支到或穿透时将其值留在栈上。

webassembly
;; block: a labeled group with a single exit
(block $b
  i32.const 1
  i32.const 2
  i32.add
  ;; br $b exits the block (with optional value)
  br $b
  ;; unreachable code
)
;; stack now has the result of the block

;; Blocks can produce a value (multi-value):
(block $b (result i32)
  i32.const 42
  br $b
)

loop

loop 类似 block,但 br $loop 跳转到 loop 的顶部(继续迭代)而非末尾。这与 block 相反。Wasm 没有 for/while 关键字——迭代由 loop + 条件 br_if 构建。

webassembly
;; loop: a block whose br jumps back to the TOP
(func $count (param $n i32) (result i32)
  (local $i i32) (local $sum i32)
  i32.const 0  local.set $i
  i32.const 0  local.set $sum
  (loop $loop
    local.get $i
    local.get $n
    i32.lt_s
    if
      local.get $sum
      local.get $i
      i32.add
      local.set $sum
      local.get $i
      i32.const 1
      i32.add
      local.set $i
      br $loop        ;; jump back to loop start
    end
  )
  local.get $sum
)

if / else

if 从栈消费一个 i32(非零 = true)。(result ...) 子句在两个分支类型一致时让 if 产生值。允许没有 else 的 if;分支可以为空。if 是 block + br_if 的语法糖。

webassembly
;; if consumes a condition from the stack
(func $abs (param $x i32) (result i32)
  local.get $x
  if (result i32)
    local.get $x
  else
    i32.const 0
    local.get $x
    i32.sub       ;; negate
  end
)

;; if without else (no result)
(func (param i32)
  local.get 0
  if
    ;; do something only if non-zero
  end
)

br 与 br_if

br $label 跳转到命名 block/if 的末尾(或 loop 的顶部)。br_if 条件性地做同样的事。标签也可以是数字(br 0 = 最内层)。带值分支将其留在栈上——这是 Wasm 提前返回的惯用方式。

webassembly
;; br: unconditional branch to a label
(block $outer
  (block $inner
    br $inner       ;; exits inner
    br $outer       ;; exits outer (unreachable here)
  )
)

;; br_if: branch only if top of stack is non-zero
(func $clamp (param $x i32) (result i32)
  (block $done (result i32)
    local.get $x
    i32.const 100
    i32.gt_s
    br_if $done          ;; if x > 100, branch with...
    i32.const 100        ;; ...100 on the stack
    local.get $x
    i32.const 0
    i32.lt_s
    br_if $done          ;; if x < 0, branch with...
    i32.const 0          ;; ...0
    local.get $x         ;; else x
  )
)

br_table (switch)

br_table 实现 switch:消费一个 i32 索引并跳转到列出的标签之一,或范围外时跳转到最后一个(默认)标签。从最外到最内嵌套 block,以便每个 case 可以返回其值。这是按标签分派最高效的方式。

webassembly
;; br_table: jump table (like a C switch)
(func $dayName (param $d i32) (result i32)
  (block $default
    (block $sat
      (block $sun
        local.get $d
        br_table $sun $sat $default
      )
      i32.const 0  ;; Sunday
      return
    )
    i32.const 6   ;; Saturday
    return
  )
  i32.const -1    ;; default
)

;; br_table $l0 $l1 $l2 ... $default
;; index 0 -> $l0, 1 -> $l1, ... out-of-range -> $default

return 与 unreachable

return 立即用栈上的值退出函数。unreachable 总是陷入(抛出运行时异常)——用于标记不应执行的代码路径,类似于 assert(false)。两者都是结构化 block 模型之外的控制流逃生舱。

webassembly
(func $find (param $arr i32) (param $len i32) (param $key i32)
  (result i32)
  (local $i i32)
  i32.const 0  local.set $i
  (block $notFound
    (loop $loop
      local.get $i
      local.get $len
      i32.ge_s
      br_if $notFound
      ;; load arr[i] and compare...
      local.get $i
      i32.const 1
      i32.add
      local.set $i
      br $loop
    )
  )
  unreachable        ;; traps if reached
)

;; return: early exit from the function
;; unreachable: always traps (used to mark bugs)
10

JS 互操作

传递数字

i32/f32/f64 直接映射到 JS number。i64 映射到 JS BigInt(大整数超出 Number 的安全范围)。i64 参数/返回值始终使用 BigInt。转换在边界是零拷贝的,但频繁跨越有开销——在 Wasm 中批量操作。

webassembly
// Numbers pass directly across the Wasm/JS boundary.
const exports = instance.exports;

exports.add(2, 3);          // 5  (i32 -> JS number)
exports.square(4);          // 16
exports.compute(1.5, 2.5);  // f64 -> JS number

// i64 caveat: before BigInt integration, i64 returns
// caused problems. Now Wasm i64 maps to JS BigInt:
const big = exports.bigResult();  // 9007199254740993n

// Passing a BigInt to an i64 param:
exports.takeBigInt(123n);

传递字符串

字符串以 UTF-8 字节序列跨越边界:JS 将其编码到 Wasm 内存中,然后传递(指针,长度)。通常 Wasm 导出 alloc/free 辅助函数,或将其打包进单个 i64(低 32 = ptr,高 32 = len)。始终释放或复用缓冲区以避免泄漏。

webassembly
// Wasm has no string type. Encode UTF-8 into memory,
// pass (ptr, len) to Wasm:

function passString(memory, str) {
  const bytes = new TextEncoder().encode(str);
  const ptr = instance.exports.alloc(bytes.length);
  new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
  instance.exports.processString(ptr, bytes.length);
  instance.exports.dealloc(ptr, bytes.length);
}

// Receiving a string back:
function recvString(memory, ptrLen) {
  const ptr = ptrLen >>> 0;
  const len = ptrLen >>> 32;
  const bytes = new Uint8Array(memory.buffer, ptr, len);
  return new TextDecoder().decode(bytes);
}

传递数组与批量数据

对于批量数值数据,将 Wasm 内存视为正确类型的 TypedArray(Float32Array、Int32Array 等)并使用 .set() 复制。这是最快的路径——类 memcpy。记住指针对齐(i32/f32 4 字节,i64/f64 8 字节)并始终释放内存。

webassembly
// Write a JS array into Wasm memory:
function passArray(memory, arr) {
  const view = new Float32Array(memory.buffer);
  const ptr = instance.exports.alloc(arr.length * 4);
  const offset = ptr / 4;
  for (let i = 0; i < arr.length; i++) view[offset + i] = arr[i];
  return ptr;
}

// Faster: use TypedArray.set with a subarray
function passArrayFast(memory, arr) {
  const ptr = instance.exports.alloc(arr.length * 4);
  new Float32Array(memory.buffer, ptr, arr.length).set(arr);
  return ptr;
}

跨边界内存管理

Wasm 内存是手动管理的——没有垃圾回收器。如果 Wasm 导出 alloc/free(或使用 malloc 风格辅助函数),用 try/finally 包装 JS 访问以保证释放。注意 memory.grow 分离 ArrayBuffer 视图——扩容后重新创建视图。

webassembly
// Wasm has no GC — JS must manage allocations.
// Pattern: Wasm exports alloc/free, JS uses them:

const { memory, alloc, free, process } = instance.exports;

function withBuffer(size, fn) {
  const ptr = alloc(size);
  try {
    return fn(new Uint8Array(memory.buffer, ptr, size));
  } finally {
    free(ptr, size);
  }
}

// CAUTION: TypedArray views become detached after
// memory.grow — re-create them after any grow call.

WebAssembly.Global

WebAssembly.Global 是 Wasm 全局变量值的 JS 包装器。创建一个并传递给多个模块实例可干净地共享可变状态而无需触碰内存。.value 属性读/写底层 i32/i64/f32/f64(或 v128/externref)。

webassembly
// Create a global on the JS side
const counter = new WebAssembly.Global(
  { value: 'i32', mutable: true },
  0
);

// Pass to Wasm at instantiation
WebAssembly.instantiateStreaming(fetch('m.wasm'), {
  env: { counter }
});

// Read/write from JS
counter.value;        // 0
counter.value = 10;   // set
counter.value++;      // 11

// Share across multiple instances
WebAssembly.instantiateStreaming(fetch('m2.wasm'), {
  env: { counter }    // same Global object
});

WebAssembly.Table 与错误

WebAssembly.Table 存储可从 JS 和 Wasm 访问的函数引用——对回调注册表和分派表很有用。Wasm 陷入(除零、越界内存、栈溢出)在 JS 中表现为 WebAssembly.RuntimeError——用 try/catch 包装有风险的调用。

webassembly
// Table of function references, callable from JS indirectly
const table = new WebAssembly.Table({
  element: 'funcref',
  initial: 4,
  maximum: 16
});

// Set a slot to a Wasm-exported function
table.set(0, instance.exports.add);
table.get(0)(2, 3);  // 5

// Errors propagate as 'RuntimeError' across the boundary:
try {
  instance.exports.divide(1, 0);
} catch (e) {
  // e instanceof WebAssembly.RuntimeError
  console.error('Wasm trapped:', e.message);
}
11

编译 C/C++ 到 Wasm

Emscripten 安装

Emscripten 是将 C/C++(及 LLVM 语言)编译为 Wasm 的规范工具链。它捆绑了 Clang、Binaryen 和移植到浏览器的 libc/libstdc++。在每个 shell 会话中用 emsdk_env 激活环境。更新频繁——定期重装。

webassembly
# Install the Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh   # Linux/macOS
# Windows: emsdk_env.bat

# Verify
emcc --version
# emcc (Emscripten gcc/clang-like replacement)

C 语言 Hello World

标准 C 直接可用——printf 写入 Emscripten 提供的虚拟终端。main() 在模块加载时自动调用。从 JS 调用的函数应用 EMSCRIPTEN_KEEPALIVE 或 -s EXPORTED_FUNCTIONS 标志导出。

webassembly
// hello.c
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    printf("Hello from C!\n");
    printf("2 + 3 = %d\n", add(2, 3));
    return 0;
}

用 emcc 编译

emcc 驱动编译。-O3 启用激进的优化(内联、死代码消除)。EXPORTED_FUNCTIONS 列出要暴露的 C 符号(带前导 _)。MODULARIZE 将加载器包装为工厂供打包器使用。输出格式:仅 .wasm、.js 胶水 + .wasm,或 .html 演示。

webassembly
# Standalone .wasm (no main, exported functions only)
emcc add.c -O3 -o add.wasm \
  -s EXPORTED_FUNCTIONS='["_add"]' \
  -s ALLOW_MEMORY_GROWTH=1

# HTML + JS + Wasm bundle (auto-runs main)
emcc hello.c -o hello.html

# With optimizations and a specific output
emcc hello.c -O3 -s WASM=1 -o hello.js

# Common flags:
#   -O0/-O1/-O2/-O3/-Os   optimization level
#   -s MODULARIZE=1       export as JS module
#   -s EXPORT_ES6=1       ES module output
#   -s ENVIRONMENT=web    target only the web

Emscripten HTML 输出与 Module

Emscripten 的 'Module' 对象是 JS 端运行时:暴露带 _ 前缀的 C 函数,管理内存(_malloc/_free),并提供钩子(print、onRuntimeInitialized)。MODULARIZE 使其成为适合打包器和异步加载的 Promise 返回工厂。

webassembly
// Generate a runnable HTML demo
//   emcc hello.c -o hello.html
// Then open hello.html in a browser.

// Modularized loader (use from JS):
//   emcc hello.c -o hello.js -s MODULARIZE=1 -s EXPORT_NAME='createModule'

import createModule from './hello.js';

const Module = await createModule({
  print: (text) => console.log('C said:', text),
  onRuntimeInitialized: () => {
    Module._main();          // call main
    const sum = Module._add(2, 3);
    console.log('sum:', sum);  // 5
  }
});

从 C 调用 JS (EM_JS / EM_ASM)

EM_JS 内联定义 C 可直接调用的 JS 函数——高效且同步。EM_ASM 嵌入内联 JS 片段。用 $0、$1、... 表示参数。这些宏将 C 粘合到浏览器 API(DOM、fetch、Web Audio)而无需编写单独的 JS 绑定。

webassembly
#include <emscripten.h>

// EM_JS: define a JS function callable from C
EM_JS(void, callAlert, (int x), {
  alert("Value from C: " + x);
});

// EM_ASM: inline JS (use $n for arguments)
void logValue(int n) {
  EM_ASM({
    console.log("C passed:", $0);
  }, n);
}

int main() {
  callAlert(42);
  logValue(99);
  return 0;
}

文件系统支持 (MEMFS)

Emscripten 提供 MEMFS,一个内存中虚拟文件系统。fopen/fread/fwrite 与 C 中相同。使用 --preload-file 在编译时将资源嵌入 .data 边车。对于持久存储,IDBFS 同步到 IndexedDB;NODERAWFS 在 Node 中暴露真实文件系统。

webassembly
#include <stdio.h>
#include <emscripten.h>

int main() {
    // MEMFS: a virtual in-memory filesystem
    FILE *f = fopen("/tmp/data.txt", "w");
    fprintf(f, "Hello, MEMFS!\n");
    fclose(f);

    f = fopen("/tmp/data.txt", "r");
    char buf[64];
    fgets(buf, sizeof(buf), f);
    fclose(f);
    printf("%s", buf);
    return 0;
}

// Preload files at compile time:
//   emcc app.c -o app.html \
//     --preload-file assets/data.txt
12

编译 Rust 到 Wasm

wasm-pack 安装

Rust 通过 wasm32-unknown-unknown 目标编译为 Wasm。wasm-pack 是推荐工具——它包装 cargo,运行 wasm-bindgen,并生成 npm 就绪的包。对于 WASI 目标,使用 wasm32-wasi。

webassembly
# Install Rust (rustup)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add the wasm32 target
rustup target add wasm32-unknown-unknown

# Install wasm-pack (the canonical Wasm tool for Rust)
cargo install wasm-pack
# or: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

# Verify
wasm-pack --version

Rust 库结构

wasm-bindgen 是 Rust 和 JS 之间的桥梁。#[wasm_bindgen] 标记要导出的项。字符串、Vec<u8> 和 JS 对象自动转换。constructor 属性将 new() 暴露为 JS 构造器。这生成让 Rust 感觉原生的 JS 胶水。

webassembly
// src/lib.rs
use wasm_bindgen::prelude::*;

// Export a function callable from JS
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Export a struct with methods
#[wasm_bindgen]
pub struct Counter {
    count: i32,
}

#[wasm_bindgen]
impl Counter {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Counter {
        Counter { count: 0 }
    }
    pub fn increment(&mut self) -> i32 {
        self.count += 1;
        self.count
    }
}

用 wasm-pack 构建

wasm-pack build 生成可发布的 npm 包:.wasm 二进制、JS 胶水和 TypeScript .d.ts 文件。--target web 提供自包含的 ES 模块供浏览器直接使用;--target bundler 与 webpack/vite 集成。生成的类型提供完整 TS 自动补全。

webassembly
# Build for the browser (npm package output)
wasm-pack build --target web

# Targets:
#   --target web       ES module, modern browsers
#   --target bundler   for webpack/rollup/vite
#   --target nodejs    CommonJS for Node.js
#   --target no-modules  global-based

# Output goes to ./pkg/
#   my_crate.js       JS glue
#   my_crate_bg.wasm  the binary
#   my_crate.d.ts     TypeScript types

# Use in JS:
import init, { add } from './pkg/my_crate.js';
await init();           // load the .wasm
console.log(add(2, 3)); // 5

wasm-bindgen: JS 互操作

extern 块将 JS 函数和类导入 Rust。js_namespace = console 调用 console.log。method 在导入类型上暴露 JS 方法。wasm-bindgen 处理字符串转换(Rust &str <-> JS 字符串)和所有权——无需手动内存操作。

webassembly
use wasm_bindgen::prelude::*;

// Call a JS function from Rust
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn run() {
    log("Hello from Rust!");
}

// Import a JS class
#[wasm_bindgen(module = "/js/util.js")]
extern "C" {
    pub type JsCounter;
    #[wasm_bindgen(constructor)]
    pub fn new() -> JsCounter;
    #[wasm_bindgen(method)]
    pub fn tick(this: &JsCounter) -> i32;
}

从 Rust 调用 JS 与 Panic

导入的 JS 函数表现为普通 Rust 函数。使用 console_error_panic_hook 在浏览器控制台获得可读的 Rust panic 回溯(默认 panic 会无消息陷入)。#[wasm_bindgen(start)] 属性在实例化时运行 main()——类似 C 的 main。

webassembly
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

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

// Install a panic hook for readable errors:
#[wasm_bindgen(start)]
pub fn main() {
    console_error_panic_hook::set_once();
}
13

AssemblyScript

安装与 Hello World

AssemblyScript 是一种类 TypeScript 语言,编译为 Wasm。面向希望 Wasm 性能但不学习 C/Rust 的开发者。语法镜像 TS 但有严格类型(i32 而非 number)。使用 loader 或直接导入 .wasm。

webassembly
# Install AssemblyScript compiler
npm install -g assemblyscript
# or use the boilerplate:
npm init assemblyscript my-app
cd my-app
npm install
npm run asbuild

// assembly/index.ts
export function add(a: i32, b: i32): i32 {
  return a + b;
}

// Load from JS:
import { instantiate } from "@assemblyscript/loader";
const module = await instantiate(fetch("build/release.wasm"));
console.log(module.exports.add(2, 3)); // 5

AssemblyScript 中的类型

与 TypeScript 不同,AssemblyScript 强制具体数值类型:i32、i64、f32、f64、u8、u16、u32 等。'number' 在编译时被拒绝。StaticArray 避免 GC;ArrayBuffer/TypedArray 镜像 JS API。严格类型是编译为高效 Wasm 的关键。

webassembly
// Distinct numeric types (NOT just 'number')
let a: i32 = 42;
let b: i64 = 9007199254740993n;
let f: f64 = 3.14;
let u: u32 = 100;

// Bool, string, arrays
let flag: bool = true;
let s: string = "hi";
let arr: i32[] = [1, 2, 3];

// Static arrays (no GC overhead)
let sa: StaticArray<i32> = [1, 2, 3];

// TypedArray-like
let buf: ArrayBuffer = new ArrayBuffer(8);

内存与字符串

AssemblyScript 对托管对象(Array、Map、String)有小型 GC。跨越到 JS 时使用 __retain/__release 控制生命周期。@assemblyscript/loader 提供 __getString、__newString、__getArray 等跨边界桥接。

webassembly
// Strings are UTF-16 in AssemblyScript memory
export function greet(name: string): string {
  return "Hello, " + name + "!";
}

// Manual memory management for buffers
export function process(len: i32): i32 {
  let buf = new ArrayBuffer(len);
  // ...fill buf...
  let result = __retain(buf);  // prevent GC
  return result;
}

// In JS, use the loader's __getString/__newString:
const ptr = module.exports.greet("World");
console.log(module.exports.__getString(ptr));

导出与入口点

标记 export 的函数和类成为 Wasm 导出。类通过 loader 映射到 JS 兼容对象。_start 是惯例入口点(类似 C 的 main)。AssemblyScript 的标准库镜像 JS Math、Array、Map——使迁移曲线平缓。

webassembly
// assembly/index.ts
export function fib(n: i32): i32 {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);
}

export class Vector {
  constructor(public x: f32, public y: f32) {}
  magnitude(): f32 {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }
}

// Module start function (runs at instantiation)
export function _start(): void {
  // initialization code
}

编译与优化

asc 是 AssemblyScript 编译器。--optimize (-O) 运行 Binaryen 的 wasm-opt。--runtime 控制 GC:'full'(默认)、'half'(更小)、'none'(手动)。--importMemory 让你跨模块共享内存。Source map 支持在原始 .ts 源码中调试。

webassembly
# Build debug vs release
npm run asbuild           # both debug and release
# or:
asc assembly/index.ts --outFile release.wasm --optimize

# Common flags
asc index.ts -o release.wasm \
  --optimize \
  --noAssert \
  --runtime half          # smaller GC runtime
  --importMemory          # use imported memory
  --sourceMap             # emit source map

# Optimize for size:
asc index.ts -o tiny.wasm --optimize --runtime none \
  --conformance --noDebug
14

WASI (WebAssembly 系统接口)

什么是 WASI

WASI 是浏览器外 Wasm 的可移植、基于能力的系统接口。与 Wasm 调用 JS 的 Web 不同,WASI 给 Wasm 直接(沙箱化)访问文件、网络和环境。基于能力的安全意味着程序只能触及显式为其预开的资源。

webassembly
// WASI = WebAssembly System Interface
// A standardized API for Wasm to access OS capabilities:
//   - filesystem (files, directories)
//   - network sockets (preview2)
//   - environment variables, args
//   - clocks, random numbers
//   - process exit

// Capability-based security: a Wasm program can
// ONLY access resources it was explicitly granted.

// Targets:
//   wasm32-wasi           (preview1)
//   wasm32-wasip1         (preview1, new name)
//   wasm32-wasip2         (preview2, component model)

// Runtimes: wasmtime, wasmer, WasmEdge, browser_wasi_shim

wasmtime 运行时

wasmtime 是 Bytecode Alliance 的参考 WASI 运行时。--dir 预开宿主目录(授予文件访问)。--env 传递环境变量。--net 在 preview2 中启用网络。没有显式授予,Wasm 程序什么也看不到——这就是能力安全的实际运作。

webassembly
# Install wasmtime (the reference WASI runtime)
# macOS:  brew install wasmtime
# Linux:  curl https://wasmtime.dev/install.sh -sSf | bash
# Windows: scoop install wasmtime

# Run a WASI module
wasmtime hello.wasm

# Pass arguments
wasmtime hello.wasm arg1 arg2

# Preopen a directory (grant filesystem access)
wasmtime --dir=. app.wasm

# Set environment variables
wasmtime --env FOO=bar app.wasm

# Allow network (preview2)
wasmtime --net allow app.wasm

用 WASI 运行 Wasm (C 示例)

为 WASI 编写的程序看起来像使用 libc 的普通 C 程序。用 clang --target=wasm32-wasi 和 WASI sysroot 编译(下载 wasi-sdk 发布版)。相同的 .wasm 无需重新编译即可在 wasmtime、wasmer、WasmEdge 和其他 WASI 主机上运行——真正的可移植性。

webassembly
// wasi_hello.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("Hello from WASI!\n");
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++) printf("  argv[%d] = %s\n", i, argv[i]);
    return 0;
}

# Compile with the WASI SDK
clang --target=wasm32-wasi \
  --sysroot=/path/to/wasi-sysroot \
  -o hello.wasm wasi_hello.c

# Run
wasmtime hello.wasm foo bar
# Hello from WASI!
# argc = 3
#   argv[0] = hello.wasm
#   argv[1] = foo
#   argv[2] = bar

WASI 文件系统访问

WASI 暴露类 POSIX 文件系统 API(open、read、write、close)。预开目录外的文件不可见。使用 --dir=path:alias 授予访问并重命名。WASI preview2 引入组件模型接口(wasi filesystem、wasi sockets)以提供更丰富的 API。

webassembly
// wasi_fs.c — read a file passed via --dir
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd = open("input.txt", O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }
    char buf[256];
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    if (n > 0) { buf[n] = '\0'; printf("%s", buf); }
    close(fd);
    return 0;
}

# Grant access, then run:
#   wasmtime --dir=. wasi_fs.wasm

WASI 环境变量与参数

WASI 通过标准 C 接口(argv、environ、getenv)提供参数和环境变量。宿主控制可见内容——--env 授予特定变量。这使得 Wasm 程序无需更改代码即可配置,并限制宿主机密的泄漏。

webassembly
// wasi_env.c
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main(int argc, char *argv[]) {
    printf("Args:\n");
    for (int i = 0; i < argc; i++) printf("  %s\n", argv[i]);

    printf("Env:\n");
    for (char **e = environ; *e; e++) printf("  %s\n", *e);

    const char *home = getenv("HOME");
    printf("HOME=%s\n", home ? home : "(unset)");
    return 0;
}

# Run with env vars:
#   wasmtime --env HOME=/tmp --env FOO=bar wasi_env.wasm a b c

WASI 套接字 (预览)

WASI 套接字是 preview2(组件模型)的一部分。wasi:sockets/tcp 和 wasi:sockets/udp 提供网络;wasi:http 提供更高级的 HTTP 客户端。API 正在稳定——今天为可移植性优先使用 wasi:http。wasmtime 和 WasmEdge 等运行时实现了这些。

webassembly
// WASI preview2 adds TCP/UDP sockets.
// In Rust (using wasi crate):
use wasi::sockets::network::{IpAddress, IpSocketAddress};

// Component-model based sockets:
//   - wasi:sockets/tcp
//   - wasi:sockets/udp
//   - wasi:sockets/ip-name-lookup

# Compile a Rust program for preview2:
cargo build --target wasm32-wasip2 --release

# Run with network access:
wasmtime --net allow target/wasm32-wasip2/release/myapp.wasm

// Note: socket APIs are still stabilizing.
// Use HTTP via wasi:http/types for stability.
15

调试

wasm-objdump

wasm-objdump(来自 WABT)打印模块结构:类型、导入、导出、函数签名、代码反汇编和节大小。-x 给出高级摘要;-d 将函数体反汇编为类 WAT 指令。对理解模块实际内容至关重要。

webassembly
# Inspect a .wasm file's structure
wasm-objdump -x module.wasm       # everything
wasm-objdump -d module.wasm       # disassemble functions
wasm-objdump -s module.wasm       # raw section contents
wasm-objdump -j Import -x m.wasm  # only the Import section

# Sample -x output:
# Section Type (1) at 0x0008:
#   type[0] (i32, i32) -> i32
# Section Function (3) at 0x0015:
#   func[0] type=0

wasm2wat 检查

wasm2wat 将二进制 .wasm 转换回人类可读的 WAT。这对理解第三方模块、调试意外行为和逆向工程极其有用。往返(wasm2wat → 编辑 → wat2wasm)保留行为,使 WAT 成为真正的源码格式。

webassembly
# Disassemble binary to readable text
wasm2wat module.wasm -o module.wat

# View in your editor
cat module.wat

# Round-trip: edit .wat, recompile
wat2wasm module.wat -o module.wasm

# Common inspection: find exports
grep "(export" module.wat

# Find imports
grep "(import" module.wat

浏览器 DevTools

所有主流浏览器都可在 DevTools 中调试 Wasm:查看反汇编的 WAT、设置断点、检查局部变量和调用栈。借助 source map(Rust、AssemblyScript、Emscripten -g),DevTools 映射回原始源码。DWARF 调试信息为 C/C++/Rust 启用更丰富的调试。

webassembly
// Chrome/Edge/Firefox DevTools support Wasm debugging:
//   1. Sources tab -> file tree shows .wasm entries
//   2. Click a .wasm to view disassembled WAT
//   3. Set breakpoints on function entries
//   4. Inspect the call stack and locals

// With source maps (Rust/AS), DevTools shows the
// ORIGINAL source (.rs / .ts) instead of WAT.

// Enable in chrome://flags:
//   "WebAssembly debugging: Support DWARF"

// Console logging from Wasm:
//   Import a JS log function and call it from WAT.
//   Rust: wasm_logger / console_log crate.
//   AS:   console.log via env import.

Source Map

Source map 将 Wasm 指令链接回原始源语言。Rust 通过 wasm-pack 发出;AssemblyScript 通过 --sourceMap;Emscripten 通过 -g4。.wasm(自定义节)中的 sourceMappingURL 注释告诉 DevTools 在哪找 .map。没有它,你调试的是原始 WAT。

webassembly
# Rust: source maps come automatically with wasm-pack
wasm-pack build --target web --dev

# AssemblyScript: emit source maps
asc index.ts -o release.wasm --sourceMap \
  --sourceMap include 'src/**/*.ts'

# Emscripten: include debug info and source map
emcc app.c -g4 -O0 -o app.js \
  --source-map-base http://localhost:8000/

# The .map file points DevTools back to the
# original .rs / .ts / .c source files.

# Validate a source map exists:
wasm-objdump -x app.wasm | grep sourceMappingURL

常见错误与陷阱

常见 Wasm 错误:缺失导入(名称/类型不匹配)、陷阱(unreachable、越界内存、除零整数溢出)、分离的 ArrayBuffer(grow 后)和 i64 边界问题。用 try/catch 包装有风险的调用,并提前验证导入/指针以尽早暴露问题。

webassembly
// 1. LinkError: "import not found"
//    Cause: import object missing a name.
const imports = { env: { log: console.log } };
// Must match (import "env" "log" ...) exactly.

// 2. RuntimeError: "unreachable"
//    Cause: Wasm executed unreachable / panic / assert.

// 3. RuntimeError: "out of bounds memory access"
//    Cause: load/store at an invalid address.
//    Fix: check ptr/len before access; grow memory.

// 4. CompileError: "section size mismatch"
//    Cause: corrupted or truncated .wasm file.

// 5. TypeError: "i64 not supported"
//    Cause: i64 returns without BigInt integration.
//    Fix: use BigInt, or return two i32 values.

// 6. "detached ArrayBuffer"
//    Cause: used a TypedArray view after memory.grow.
//    Fix: re-create views after grow.
16

性能优化

内联与 -O 标志

-O3 启用激进的内联和循环优化。wasm-opt (Binaryen) 运行额外的 Wasm 级遍历(内联、死代码、常量折叠)——生产环境始终运行它。-Os/-Oz 以一些速度换取更小的下载。-flto 启用全程序优化。

webassembly
# Compilation optimization levels
emcc app.c -O0 -o app.wasm   # no optimization (debug)
emcc app.c -O1 -o app.wasm   # basic
emcc app.c -O2 -o app.wasm   # standard
emcc app.c -O3 -o app.wasm   # aggressive (inlining)
emcc app.c -Os -o app.wasm   # optimize for size
emcc app.c -Oz -o app.wasm   # max size reduction

# Post-optimize with Binaryen
wasm-opt -O3 app.wasm -o app.opt.wasm
wasm-opt -Os app.wasm -o app.small.wasm

# LTO across translation units (C/C++)
emcc app.c -flto -O3 -o app.wasm

内存布局与访问模式

Wasm 线性内存只是一个 ArrayBuffer——CPU 缓存行为适用。连续访问模式(行主序数据用行主序遍历)比跨步访问快得多。保持自然对齐(i32 4 字节)使 load/store 命中单条指令。

webassembly
// Cache-friendly access: linear, contiguous iteration
// GOOD: row-major traversal of a 2D array
for (i32 row = 0; row < rows; row++) {
  for (i32 col = 0; col < cols; col++) {
    sum += data[row * cols + col];
  }
}

// BAD: column-major traversal (cache misses)
for (i32 col = 0; col < cols; col++) {
  for (i32 row = 0; row < rows; row++) {
    sum += data[row * cols + col];
  }
}

// Prefer aligned access (4-byte for i32, 8 for i64).
// Allocators should respect natural alignment.

最小化 JS 边界跨越

每个 JS→Wasm 调用都有开销(参数编排、栈设置)。对于紧凑循环,将循环移入 Wasm 并用缓冲区调用一次。这是最大的 Wasm 性能技巧——批量操作比逐元素调用快几个数量级。

webassembly
// BAD: call Wasm per element (boundary overhead)
for (let i = 0; i < arr.length; i++) {
  result[i] = exports.process(arr[i]);
}

// GOOD: batch — one call for the whole array
const ptr = exports.alloc(arr.length * 4);
new Float32Array(memory.buffer, ptr, arr.length).set(arr);
exports.processBatch(ptr, arr.length);   // single call
const out = Array.from(new Float32Array(memory.buffer, ptr, arr.length));
exports.free(ptr, arr.length * 4);

// Each crossing costs ~tens of nanoseconds.
// Batch loops to amortize the overhead.

SIMD (单指令多数据)

SIMD (v128) 每条指令处理 4 个浮点或 4 个 int32——对图像/音频/矩阵数学有 4 倍加速。Emscripten 用 -msimd128 启用;Rust 使用 std::arch::wasm32 内联函数。自动向量化有帮助,但显式内联函数给出可预测的收益。在浏览器中广泛支持。

webassembly
// C/C++ with Emscripten auto-vectorization
#include <emscripten/simd.h>
void addArrays(float *a, float *b, float *out, int n) {
    for (int i = 0; i < n; i += 4) {
        v128_t va = wasm_v128_load(a + i);
        v128_t vb = wasm_v128_load(b + i);
        wasm_v128_store(out + i, wasm_f32x4_add(va, vb));
    }
}

# Compile with SIMD enabled
emcc simd.c -O3 -msimd128 -o simd.wasm

// Rust: use std::arch::wasm32 intrinsics
use std::arch::wasm32::*;
unsafe { let v = f32x4_splat(1.0); }

线程与 SharedArrayBuffer

Wasm 线程跨 Web Worker 共享 Memory,使用原子操作同步。需要 COOP/COEP 头(跨源隔离)。Emscripten 的 -pthread 模拟 pthreads;Rust 的 std::thread 在 wasm32-wasi-threads 上工作。适合 CPU 密集型并行工作——图像处理、编解码器、ML 推理。

webassembly
// Enable threads in Emscripten
# emcc app.c -O3 -pthread -s PTHREAD_POOL_SIZE=4 -o app.js

// Browser requires cross-origin isolation:
//   /_headers (Cloudflare/Netlify)
//   Cross-Origin-Opener-Policy: same-origin
//   Cross-Origin-Embedder-Policy: require-corp

// Spawn workers sharing Wasm memory:
const memory = new WebAssembly.Memory({
  initial: 10, maximum: 100, shared: true
});
// Each Worker instantiates the module with this memory
// and uses Atomics for synchronization.

性能分析与基准测试

用 performance.now() 和 DevTools Performance 标签分析——Wasm 像 JS 一样出现在火焰图中。测量前预热(编译开销是一次性的)。与优化后的 JS 比较——现代 V8 很快;Wasm 在可预测性能、数值内核和大型工作负载上取胜,而非微任务。

webassembly
// Use performance.now() for high-res timing
const t0 = performance.now();
exports.heavyWork(ptr, len);
const t1 = performance.now();
console.log(`Wasm: ${(t1 - t0).toFixed(2)}ms`);

// Compare against JS equivalent
const t2 = performance.now();
jsEquivalent(data);
console.log(`JS:   ${(performance.now() - t2).toFixed(2)}ms`);

// Chrome DevTools Performance tab: profile Wasm
// like JS — flame charts show function time.

// Avoid microbenchmarks pitfalls:
//   - warm up first (JIT compilation)
//   - run many iterations
//   - compare against OPTIMIZED JS, not naive JS
17

Wasm 与 JavaScript

性能对比

Wasm 在计算密集型数值工作(图像处理、编解码器、加密、ML)上表现出色——通常比 JS 快 2-10 倍。对于 DOM、JSON 或字符串密集型工作,JS 通常更快,因为工作已经在原生代码中(V8 的 JSON.parse、浏览器的 DOM)。通过分析来决定——绝不要假设 Wasm 总是更快。

webassembly
// Wasm vs JS — typical scenarios:
//
// Image processing (8MB):
//   JS:          120 ms
//   Wasm (-O3):   35 ms    ~3.4x faster
//
// SHA-256 (1MB):
//   JS:           45 ms
//   Wasm:          8 ms    ~5.6x faster
//
// DOM manipulation:
//   JS:            5 ms
//   Wasm:         20 ms    SLOWER (DOM is JS-native)
//
// JSON parse:
//   JS:            3 ms
//   Wasm:         15 ms    SLOWER (JSON.parse is C++ in V8)
//
// Rule of thumb: Wasm wins on compute-heavy numeric
// workloads; JS wins on DOM/JSON/string-heavy tasks.

Wasm 的用例

Wasm 擅长:移植现有 C/C++/Rust 代码库(Figma、Photoshop web)、计算密集型数值内核(图像/ML/加密)和沙箱化插件。实际成功案例包括 Figma(渲染器)、Photoshop(完整应用移植)和 Google Meet(背景模糊)。对于纯 DOM 应用,JS 通常是正确的选择。

webassembly
// Strong Wasm use cases:
//   - Image/video/audio processing (FFmpeg, OpenCV)
//   - Games & 3D engines (Unity, Unreal)
//   - Cryptography (hashing, encryption, ZK proofs)
//   - Machine learning inference (ONNX, TFLite)
//   - Codecs (AV1, VP9, MP3)
//   - Emulators (DOSBox, retro consoles)
//   - Heavy math (physics simulations, linear algebra)
//   - Reusing C/C++/Rust libraries (FFI alternative)
//   - Sandboxed plugins in editors (Figma, Photoshop)

// Figma: rendering engine in Wasm
// Photoshop: web port of full desktop app
// Google Meet: video background blur

何时使用 JS

JS 仍是 UI、DOM、浏览器 API 和大多数 Web 应用逻辑的正确选择——V8 经过高度优化。当分析揭示 JS 无法优化的热点,或移植现有原生代码库时,才选择 Wasm。混合方法(JS UI + Wasm 热点)通常是理想的。

webassembly
// JS is better when:
//   - DOM manipulation dominates
//   - Working with browser APIs (fetch, IndexedDB, WebRTC)
//   - Heavy string/JSON processing
//   - Small utilities and glue code
//   - Rapid prototyping (no compile step)
//   - Team lacks C/Rust expertise
//   - Module size matters more than runtime speed

// JS engines (V8, SpiderMonkey, JSC) are highly
// optimized. For most web apps, JS is fast enough
// AND simpler to ship and maintain.

// Hybrid: keep UI in JS, offload hotspots to Wasm.

加载与启动成本

Wasm 有 JS 没有的固定启动成本(下载 + 编译 + 实例化)。通过流式编译、通过 Cache API 缓存编译模块以及懒加载非关键 Wasm 来缓解。预热后,Wasm 函数调用与 JS 一样快——成本在前端,由更快的执行偿还。

webassembly
// Wasm has upfront costs JS doesn't:
//   1. Download the .wasm binary
//   2. Compile to machine code (~10-50 ms for small modules)
//   3. Instantiate (initialize memory, run start function)

// Mitigations:
//   - Cache compiled modules (WebAssembly.compileStreaming + Cache API)
//   - Stream compilation (instantiateStreaming)
//   - Lazy-load non-critical Wasm on demand
//   - Use Web Workers to compile off the main thread

// Streaming compile of a 1MB module:
//   ~30 ms download + ~10 ms compile (parallel)
// Cached: ~5 ms (re-instantiate from cached module)

互操作成本与边界开销

JS↔Wasm 调用便宜但非免费(每次约 10-100ns,字符串更多)。对于高频事件,在 JS 中批量处理并用缓冲区调用 Wasm 一次。避免每次调用传递 JS 对象——使用 externref 或将字节复制到内存中。边界对数字快,对序列化数据慢。

webassembly
// Each JS<->Wasm call costs ~10-100 ns:
//   - Marshal arguments (i32/f64 fast; strings/objects slow)
//   - Stack frame setup
//   - Inlined trampoline in the engine

// Implications:
//   - Don't call Wasm 1M times for tiny work — batch!
//   - Don't pass large objects per-call — copy once, share memory
//   - For DOM events, keep handlers in JS, call Wasm with batched data

// Pseudocode: batched event handling
let pendingEvents = [];
button.addEventListener('click', e => {
  pendingEvents.push(...);
  if (pendingEvents.length >= 100) {
    exports.processBatch(ptr, pendingEvents.length);
    pendingEvents = [];
  }
});
18

安全模型

沙箱模型

Wasm 的安全基础是沙箱:模块无法访问主机内存、文件、网络或设备,除非主机通过导入显式提供函数。这使得 Wasm 可以安全运行不受信任的代码——它只能做其导入允许的事。结合类型安全,这防止了缓冲区溢出和 RCE。

webassembly
// Wasm executes in a sandboxed virtual machine:
//   - No direct access to host memory (only its linear memory)
//   - No syscalls (must go through imported functions)
//   - No I/O unless the host provides it
//   - Control flow is structured (no arbitrary jumps)
//   - Type-checked at compile and run time

// All host access is EXPLICIT through imports.
// A module that didn't import "fs" cannot read files.

// Code:
//   (module
//     (import "env" "log" (func (param i32)))
//     ;; the ONLY host capability this module has is log()
//   )

同源策略与 Fetch

Wasm 不脱离浏览器安全模型——同源策略和 CORS 适用于 .wasm 获取,就像任何资源一样。跨源 Wasm 需要正确的 CORS 头和 application/wasm MIME 类型。Wasm 也无法绕过 CSP、cookie 或任何其他浏览器安全机制。

webassembly
// Wasm modules obey the same-origin policy:
//   - fetch('https://other-origin/x.wasm') needs CORS headers
//   - The remote server must send:
//       Access-Control-Allow-Origin: *
//   - And the correct MIME: application/wasm

// Load cross-origin Wasm with streaming:
const { instance } = await WebAssembly.instantiateStreaming(
  fetch('https://cdn.example.com/lib.wasm', {
    mode: 'cors'
  }),
  imports
);

// Wasm cannot bypass CORS — it's subject to the
// browser's normal security model.

内存隔离

Wasm 内存运行时有边界检查——任何越界访问立即陷入而不是损坏内存。模块无法读取彼此的内存、JS 堆或任意主机地址。函数指针(call_indirect)经过类型检查,防止调用目标伪造。这消除了整类 C/C++ 漏洞。

webassembly
// Each Wasm instance has its OWN linear memory.
//   - Out-of-bounds access traps (RuntimeError)
//   - One module's memory cannot read another's
//   - JS writes via TypedArray views are bounds-checked

// Even shared memory (threads) requires explicit
// opt-in and is still bounded:
const mem = new WebAssembly.Memory({ initial: 1, maximum: 10 });

// Wasm code CANNOT:
//   - Read JS heap
//   - Read other instances' memory
//   - Forge function pointers (call_indirect type-checks)
//   - Read arbitrary host memory addresses

// All memory access is bounds-checked at runtime.

基于能力的安全

Wasm 遵循基于能力的安全:导入对象就是模块的能力。通过仔细选择导入内容,你给模块最小权限。只导入 log() 的插件不能做其他任何事。这是安全插件系统(Figma、Shopify functions 等)的基础。

webassembly
// Capability security: a module can ONLY do what
// the host explicitly grants via imports.

// Strict imports = minimal privilege
const imports = {
  env: {
    log: msg => console.log('[wasm]', msg)
    // NO file IO, NO network, NO timers
  }
};
WebAssembly.instantiateStreaming(fetch('plugin.wasm'), imports);

// The plugin can ONLY call log(). It cannot:
//   - Access the DOM
//   - Make network requests
//   - Read files
//   - Spawn workers
// unless you import those capabilities.

CSP 与 Trusted Types

内容安全策略将 Wasm 视为 eval——默认情况下 CSP 阻止它。将 'wasm-unsafe-eval' 添加到 script-src 以允许 Wasm 同时保持 eval() 被阻止。这比 unsafe-eval 更宽松,推荐用于生产。Trusted Types 兼容性要求将任何动态 JS 胶水包装在策略中。

webassembly
// Content Security Policy applies to Wasm:
//   - 'wasm-unsafe-eval' is required for instantiation
//   - Without it, CSP blocks WebAssembly.compile

// HTTP header:
//   Content-Security-Policy:
//     default-src 'self';
//     script-src 'self' 'wasm-unsafe-eval';

// Wasm output (e.g., Emscripten) often uses eval-like
// patterns for instantiation — CSP 'wasm-unsafe-eval'
// specifically allows Wasm while still blocking eval().

// Trusted Types: Wasm glue code must produce
// trusted scripts; use policy.createScript() for
// any dynamically generated JS that loads Wasm.
19

工具链

WABT (wat2wasm, wasm2wat)

WABT 是处理 Wasm 文本和二进制格式的规范工具包。wat2wasm/wasm2wat 往返;wasm-objdump 检查结构;wasm-interp 在参考解释器中运行模块。wasm-decompile 生成类 C 的可读形式。对手写、调试和教学至关重要。

webassembly
# WABT = WebAssembly Binary Toolkit
# Install:
#   macOS:  brew install wabt
#   Ubuntu: apt install wabt
#   Windows: scoop install wabt

# Tools included:
wat2wasm hello.wat -o hello.wasm       # text -> binary
wasm2wat hello.wasm -o hello.wat       # binary -> text
wasm-validate hello.wasm               # validate
wasm-objdump -x hello.wasm             # inspect structure
wasm-interp hello.wasm                 # interpret (run)
wasm-decompile hello.wasm -o hello.dly # decompile to C-like

# Online: https://webassembly.github.io/wabt/demo/

Emscripten SDK

Emscripten 是最成熟的 Wasm 工具链——编译 C/C++、Fortran 和其他 LLVM 语言。emcc/em++ 包装 Clang;emcmake/emconfigure 让你无需修改即可构建现有 CMake/Autotools 项目。它还提供 libc、SDL、OpenGL(通过 WebGL)和 pthreads 移植以兼容。

webassembly
# Emscripten: compile C/C++/LLVM languages to Wasm
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh

# Key tools:
emcc        # C/C++ compiler (gcc/clang-like)
em++        # C++ compiler
emcmake     # wrapper for cmake
emconfigure # wrapper for ./configure
emranlib    # archive tool
emar        # archiver

# Common output:
emcc app.c -O3 -o app.js   # JS glue + .wasm
emcc app.c -o app.html     # runnable HTML demo

wasm-pack (Rust)

wasm-pack 是官方的 Rust→Wasm 工具:构建、运行 wasm-bindgen、生成 TypeScript 类型并为 npm 打包。--target 选择 JS 模块格式。wasm-pack test 通过 webdriver 在真实浏览器中运行 Rust 测试。使用 wasm-pack-template 获取就绪的项目脚手架。

webassembly
# wasm-pack: build, test, publish Rust Wasm packages
cargo install wasm-pack

# Build for various targets
wasm-pack build --target web       # modern browsers
wasm-pack build --target bundler   # webpack/vite
wasm-pack build --target nodejs    # Node.js

# Test in a headless browser
wasm-pack test --headless --chrome

# Publish to npm
wasm-pack publish

# Generate a template project
cargo generate --git https://github.com/rustwasm/wasm-pack-template

Binaryen (wasm-opt)

Binaryen 是 Emscripten 和 wasm-pack 内部使用的优化工具包。wasm-opt 运行 Wasm 级遍历(内联、死代码消除、常量折叠)——生产构建始终运行它。-O3 用于速度,-Oz 用于最小大小。wasm-metadce 跨模块移除未使用的导出。

webassembly
# Binaryen: Wasm optimization toolkit
# Often installed via Emscripten or standalone:
npm install -g binaryen

# wasm-opt: optimize .wasm files
wasm-opt -O3 input.wasm -o output.wasm       # speed
wasm-opt -Os input.wasm -o output.wasm       # size
wasm-opt -Oz input.wasm -o output.wasm       # min size

# Other tools:
wasm-as input.wat -o output.wasm             # assemble
wasm-dis input.wasm -o output.wat            # disassemble
wasm-merge a.wasm b.wasm -o merged.wasm      # link
wasm-shell input.wat                         # interpreter
wasm-metadce input.wasm -o output.wasm       # aggressive dead-code

wasm-tools (Bytecode Alliance)

wasm-tools 是 Bytecode Alliance 的现代基于 Rust 的工具包。它解析/打印/验证,但其突出特点是组件模型支持(component new/embed/describe)。它在许多操作上比 WABT 更严格、更快,是组件模型工作的推荐工具。

webassembly
# wasm-tools: modern Rust-based Wasm toolkit
cargo install wasm-tools

# Subcommands:
wasm-tools parse input.wat -o output.wasm      # text -> binary
wasm-tools print input.wasm -o output.wat      # binary -> text
wasm-tools validate input.wasm                 # validate
wasm-tools component new input.wasm -o c.wasm  # wrap as component
wasm-tools strip input.wasm -o stripped.wasm   # remove debug info
wasm-tools objdump input.wasm                  # inspect
wasm-tools smith                                # generate random modules (fuzzing)

# Component model support is a key feature.
# Stronger WAT parsing than WABT in edge cases.

运行时 (wasmtime, wasmer, WasmEdge)

三个主要的独立运行时:wasmtime(Bytecode Alliance 参考,最佳组件模型支持)、wasmer(多后端,包括 LLVM 以获得峰值性能,JS 绑定)、WasmEdge(CNCF,针对云原生和 AI 推理优化)。所有都实现 WASI,因此相同的 .wasm 可跨它们运行。根据生态适配选择。

webassembly
# wasmtime — Bytecode Alliance reference runtime
curl https://wasmtime.dev/install.sh -sSf | bash
wasmtime app.wasm

# wasmer — cross-platform, multiple backends
curl https://get.wasmer.io -sSf | sh
wasmer run app.wasm

# WasmEdge — CNCF, optimized for cloud/edge
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
wasmedge app.wasm

# Comparison:
#   wasmtime:  reference, WASI preview1+2, components
#   wasmer:   multiple backends (Cranelift, LLVM), JS API
#   WasmEdge:  AI/ML extensions, Kubernetes-friendly

// All three run WASI modules portably.
20

组件与最佳实践

Wasm 组件概览

组件模型是 Wasm 的演进:一种模块格式,让组件使用富类型(字符串、记录、列表)通信,无需手动通过线性内存编排。组件像跨语言库一样组合(Rust 组件 + JS 组件)。由 wasmtime、WasmEdge 和更广泛的 Bytecode Alliance 生态支持。

webassembly
// Component Model: a higher-level module format
// that lets Wasm modules talk to each other and hosts
// using rich types (strings, records, variants),
// not just (i32, f64).

// Core Wasm module:  exports raw functions over memory.
// Component:         wraps a core module, exposes typed
//   interfaces described in WIT (Wasm Interface Type).

// Build flow:
//   1. Write WIT interface
//   2. Generate bindings for your language (Rust, C, JS)
//   3. Implement, compile to core .wasm
//   4. Wrap into a component with wasm-tools

// Tooling:
wasm-tools component new app.wasm -o app.wasm

// Run with wasmtime:
wasmtime app.wasm  # uses component interfaces

WIT (Wasm 接口类型)

WIT (Wasm Interface Type) 是组件的 IDL——声明记录、变体、枚举、资源和函数。wit-bindgen 生成特定语言的绑定(Rust、C、JS、Python)。'world' 关键字捆绑组件提供/消费的导入和导出。这是可移植、语言无关 Wasm 库的基础。

webassembly
// example.wit — describe a component's interface
package example:greeter;

interface api {
    // A record type
    record person {
        name: string,
        age: u32,
    }

    // A function taking/returning rich types
    greet: func(p: person) -> string;
}

world greeter-world {
    export api;
}

# Generate bindings:
#   Rust:     cargo component bindings
#   JS:       jco transpile
#   C/C++:    wit-bindgen c component.wit

# wit-bindgen is the binding generator
cargo install wit-bindgen-cli

最佳实践:小型、聚焦的模块

优先使用多个小型、聚焦的 Wasm 模块而非一个庞然大物。更小的模块下载更快,缓存更好,并可懒加载(仅在需要时加载音频模块)。通过导入跨模块共享内存,或使用组件模型进行类型安全的模块间调用。在每个上运行 wasm-opt -Oz。

webassembly
// GOOD: One focused .wasm per concern
//   - renderer.wasm (drawing)
//   - audio.wasm (mixing)
//   - physics.wasm (simulation)
// Loaded lazily as needed.

// BAD: One giant monolithic .wasm
//   - app.wasm (1MB+, includes everything)

// Benefits of small modules:
//   - Faster initial load (only what's needed)
//   - Better caching (changed modules re-download)
//   - Easier parallel development
//   - Smaller attack surface per module

// Use wasm-opt -Oz to minimize each module.
// Share memory across modules via imports.

最佳实践:错误处理

Wasm 陷阱(除零、越界内存、unreachable)无法从 Wasm 内部捕获——它们中止函数。设计导出函数返回错误代码或 Result 类型而不是陷入。在 JS 端捕获 WebAssembly.RuntimeError 作为最后手段。组件模型原生支持类型化错误变体。

webassembly
// Wasm traps are unrecoverable — handle errors gracefully.

// BAD: let divide crash
(func (export "div") (param i32 i32) (result i32)
  local.get 0  local.get 1  i32.div_s  ;; traps on 0
)

// GOOD: return a sentinel or error code
(func (export "div") (param i32 i32) (result i32)
  local.get 1
  i32.eqz
  if (result i32)
    i32.const -1   ;; error sentinel
  else
    local.get 0
    local.get 1
    i32.div_s
  end
)

// In Rust/AS: use Result<T, E> and export
// error variants via the component model.

最佳实践:内存管理

像对待 C 一样对待 Wasm 内存:每个 alloc 需要匹配的 free。导出 alloc/free 辅助函数并用 try/finally 包装 JS 端缓冲区使用。在任何 memory.grow 后重新创建 TypedArray 视图(ArrayBuffer 分离)。记录所有权:谁负责释放——调用者还是被调用者?泄漏会随时间降低性能。

webassembly
// Export alloc/free and use them consistently.
// C/C++ (Emscripten): _malloc / _free
// Rust:               wasm_bindgen's __alloc / __free
// AssemblyScript:     __new / __retain / __release

// JS wrapper for safe buffer passing:
function withBuffer(size, fn) {
  const ptr = exports.alloc(size);
  try {
    return fn(new Uint8Array(memory.buffer, ptr, size));
  } finally {
    exports.free(ptr, size);
  }
}

// NEVER leak:
//   - Re-create TypedArray views after memory.grow
//   - Free buffers passed in from JS
//   - For long-lived allocations, document ownership

最佳实践:测试与 CI

在真实环境中测试 Wasm:wasm-pack test 在无头浏览器中运行 Rust 测试;AssemblyScript 和 Emscripten 有各自的运行器。在 CI 中添加 wasm-validate 和大小检查。使用 -s ASSERTIONS=1 (Emscripten) 和调试构建以尽早发现问题。用 wat2wasm 进行 lint,并始终在发布构件上运行 wasm-opt。

webassembly
# Test Wasm just like any other code.

# Rust: wasm-pack test runs in a real browser
wasm-pack test --headless --chrome --firefox

# AssemblyScript: use the built-in test framework
npm run test

# C/C++ (Emscripten): use emcc with -s ASSERTIONS=1
#   and write tests in C, run with node:
node tests.js

# Validate .wasm in CI:
wasm-validate module.wasm

# Check size budgets:
ls -la module.wasm  # e.g., must be < 200KB

# Lint WAT with wat2wasm (catches syntax errors)
# Run wasm-opt -O3 to ensure optimization is applied

# GitHub Actions example:
# - uses: actions-rs/toolchain@v1
# - run: cargo install wasm-pack
# - run: wasm-pack test --headless

这篇内容对您有帮助吗?