WebAssembly (WASM) 深度解析:從二進制格式到執行引擎
0. 前言與生成說明
本文深入解析 WebAssembly (WASM) 的內部機制,讓讀者理解這個改變 Web 與系統程式設計的技術。
文章目標
讀完本文,你將理解:
- WebAssembly 的設計目標與核心特性
- 二進制格式的完整結構
- 指令集架構與操作碼
- 堆疊機執行模型
- 類型系統與驗證算法
- 記憶體模型與安全性
1. WebAssembly 概覽
1.1 什麼是 WebAssembly?
WebAssembly (Wasm) 是一種二進制指令格式,為堆疊式虛擬機設計。
“WebAssembly is a binary instruction format for a stack-based virtual machine.”
| 特性 | 描述 |
|---|---|
| 可攜性 | 跨平台、跨瀏覽器執行 |
| 高效能 | 接近原生程式碼速度 |
| 安全性 | 沙盒執行環境 |
| 語言無關 | 可從 C/C++/Rust/Go 等編譯 |
| 可驗證 | 載入時類型檢查 |
1.2 WASM 的兩種格式
| 格式 | 副檔名 | 用途 |
|---|---|---|
| 二進制格式 | .wasm | 實際執行的緊湊格式 |
| 文本格式 | .wat | 人類可讀的 S-表達式 |
;; 文本格式範例:加法函數
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
對應的二進制格式(十六進制):
00 61 73 6D ; 魔術數字 \0asm
01 00 00 00 ; 版本號 1
01 07 ; 類型段,7 字節
01 ; 1 個類型
60 ; 函數類型
02 7F 7F ; 2 個 i32 參數
01 7F ; 1 個 i32 返回值
03 02 ; 函數段,2 字節
01 ; 1 個函數
00 ; 使用類型索引 0
07 07 ; 導出段
01 ; 1 個導出
03 61 64 64 ; "add"
00 ; 函數類型
00 ; 函數索引 0
0A 09 ; 代碼段
01 ; 1 個函數體
07 ; 函數體 7 字節
00 ; 0 個局部變數
20 00 ; local.get 0
20 01 ; local.get 1
6A ; i32.add
0B ; end
1.3 版本演進
timeline
title WebAssembly 版本演進
2017 : MVP (1.0)
: 核心指令集
: 線性記憶體
2019 : W3C 推薦標準
2022 : Wasm 2.0 Draft
: 多返回值
: 參考類型
: SIMD
2025-09 : Wasm 3.0
: 64位地址空間
: 尾調用
: 異常處理
2. 二進制格式詳解
2.1 模組結構
每個 WASM 模組以固定的 8 字節頭部開始:
┌─────────────────────────────────────────────────────────┐
│ WASM 模組結構 │
├─────────────────────────────────────────────────────────┤
│ Magic Number: 0x00 0x61 0x73 0x6D ("\0asm") │
│ Version: 0x01 0x00 0x00 0x00 (v1) │
├─────────────────────────────────────────────────────────┤
│ Section 1: Type Section │
│ Section 2: Import Section │
│ Section 3: Function Section │
│ Section 4: Table Section │
│ Section 5: Memory Section │
│ Section 6: Global Section │
│ Section 7: Export Section │
│ Section 8: Start Section │
│ Section 9: Element Section │
│ Section 10: Code Section │
│ Section 11: Data Section │
│ Section 0: Custom Sections (可選,可多個) │
└─────────────────────────────────────────────────────────┘
2.2 段落定義
每個段落的格式:
┌─────────────┬──────────────────┬───────────────────────┐
│ Section ID │ Size (ULEB128) │ Payload │
│ (1 byte) │ (變長編碼) │ (變長) │
└─────────────┴──────────────────┴───────────────────────┘
ULEB128 編碼範例:
def encode_uleb128(value):
"""無符號 LEB128 編碼"""
result = []
while True:
byte = value & 0x7F
value >>= 7
if value != 0:
byte |= 0x80 # 設置繼續位
result.append(byte)
if value == 0:
break
return bytes(result)
# 範例
encode_uleb128(624485) # -> [0xE5, 0x8E, 0x26]
2.3 各段落詳解
Type Section (ID: 1)
定義函數簽名(類型):
┌─────────────────────────────────────────────────────────┐
│ Type Section │
├─────────────────────────────────────────────────────────┤
│ Count: 2 (ULEB128) │
├─────────────────────────────────────────────────────────┤
│ Type 0: │
│ 0x60 (func 標記) │
│ Params: [i32, i32] │
│ Results: [i32] │
├─────────────────────────────────────────────────────────┤
│ Type 1: │
│ 0x60 │
│ Params: [] │
│ Results: [f64] │
└─────────────────────────────────────────────────────────┘
類型編碼:
| 編碼 | 類型 |
|---|---|
0x7F | i32 |
0x7E | i64 |
0x7D | f32 |
0x7C | f64 |
0x70 | funcref |
0x6F | externref |
Import Section (ID: 2)
從宿主環境導入:
(import "console" "log" (func $log (param i32)))
(import "js" "memory" (memory 1))
Import Entry:
module_name: UTF-8 字串(長度前綴)
field_name: UTF-8 字串
kind: 0x00=func, 0x01=table, 0x02=memory, 0x03=global
type_index: 相應類型索引
Memory Section (ID: 5)
線性記憶體定義:
Memory Entry:
flags: 0x00=無上限, 0x01=有上限
initial: 初始頁數(每頁 64 KiB)
maximum: 最大頁數(可選)
flowchart LR
subgraph Memory["線性記憶體"]
direction TB
P0["Page 0<br/>64 KiB"]
P1["Page 1<br/>64 KiB"]
P2["Page 2<br/>64 KiB"]
PN["..."]
end
ADDR["地址 0"] --> P0
P0 --> P1 --> P2 --> PN
Code Section (ID: 10)
函數體的實際代碼:
Code Entry:
body_size: 函數體大小
local_count: 局部變數組數
local_decls: [(count, type), ...]
expr: 指令序列
0x0B: end 標記
3. 指令集架構
3.1 操作碼概覽
WASM 使用單字節操作碼(少數例外):
| 類別 | 操作碼範圍 | 範例 |
|---|---|---|
| 控制流 | 0x00-0x11 | block, loop, if, br, call |
| 參數化 | 0x1A-0x1C | drop, select |
| 變數操作 | 0x20-0x24 | local.get, local.set, global.get |
| 記憶體 | 0x28-0x3E | i32.load, i32.store |
| 數值運算 | 0x41-0xC4 | i32.add, f64.mul, i32.eqz |
3.2 控制流指令
flowchart TB
subgraph Block["block ... end"]
B1["指令 1"]
B2["指令 2"]
B3["br 0 (跳出區塊)"]
end
subgraph Loop["loop ... end"]
L1["指令 1"]
L2["指令 2"]
L3["br 0 (回到開頭)"]
L3 --> L1
end
subgraph If["if ... else ... end"]
COND["條件值"]
THEN["then 分支"]
ELSE["else 分支"]
COND -->|非零| THEN
COND -->|零| ELSE
end
分支指令:
| 指令 | 操作碼 | 描述 |
|---|---|---|
br | 0x0C | 無條件分支 |
br_if | 0x0D | 條件分支 |
br_table | 0x0E | 跳轉表 |
return | 0x0F | 返回 |
3.3 數值指令
i32 運算指令:
| 指令 | 操作碼 | 堆疊效果 |
|---|---|---|
i32.const n | 0x41 | → i32 |
i32.add | 0x6A | i32, i32 → i32 |
i32.sub | 0x6B | i32, i32 → i32 |
i32.mul | 0x6C | i32, i32 → i32 |
i32.div_s | 0x6D | i32, i32 → i32 |
i32.div_u | 0x6E | i32, i32 → i32 |
i32.and | 0x71 | i32, i32 → i32 |
i32.or | 0x72 | i32, i32 → i32 |
i32.xor | 0x73 | i32, i32 → i32 |
i32.shl | 0x74 | i32, i32 → i32 |
i32.eqz | 0x45 | i32 → i32 |
i32.eq | 0x46 | i32, i32 → i32 |
3.4 記憶體指令
;; 載入與存儲
i32.load offset=0 align=2 ;; 從記憶體載入 i32
i32.store offset=0 align=2 ;; 存儲 i32 到記憶體
;; 記憶體操作
memory.size ;; 獲取頁數
memory.grow ;; 增加記憶體
memory.copy ;; 複製(Wasm 2.0)
memory.fill ;; 填充(Wasm 2.0)
載入/存儲變體:
| 指令 | 描述 |
|---|---|
i32.load8_s | 載入 8 位有符號擴展 |
i32.load8_u | 載入 8 位無符號擴展 |
i32.load16_s | 載入 16 位有符號擴展 |
i32.load16_u | 載入 16 位無符號擴展 |
i32.store8 | 存儲低 8 位 |
i32.store16 | 存儲低 16 位 |
4. 堆疊機執行模型
4.1 堆疊結構
WASM 使用操作數堆疊進行計算:
flowchart TB
subgraph Example["執行 (a + b) * c"]
direction LR
S1["初始<br/>空堆疊"]
S2["local.get a<br/>[a]"]
S3["local.get b<br/>[a, b]"]
S4["i32.add<br/>[a+b]"]
S5["local.get c<br/>[a+b, c]"]
S6["i32.mul<br/>[(a+b)*c]"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6
end
4.2 執行上下文
flowchart TB
subgraph Store["全域存儲 (Store)"]
FUNCS["函數實例"]
TABLES["表實例"]
MEMS["記憶體實例"]
GLOBALS["全域變數"]
end
subgraph Frame["呼叫幀 (Frame)"]
LOCALS["局部變數"]
MODULE["模組實例"]
end
subgraph Stack["堆疊"]
VALUES["操作數堆疊<br/>[i32, f64, ...]"]
LABELS["標籤堆疊<br/>控制流"]
FRAMES["呼叫幀堆疊"]
end
Frame --> Store
Stack --> Frame
4.3 函數呼叫
sequenceDiagram
participant Caller as 呼叫者
participant Stack as 堆疊
participant Callee as 被呼叫函數
Caller->>Stack: 推入參數 [arg1, arg2]
Caller->>Stack: call func_idx
Stack->>Stack: 創建新呼叫幀
Stack->>Callee: 參數成為局部變數 0, 1
Callee->>Stack: 執行函數體
Callee->>Stack: 推入返回值
Stack->>Stack: 銷毀呼叫幀
Stack->>Caller: 返回值在堆疊頂部
4.4 結構化控制流
WASM 的控制流是 結構化的,不允許任意跳轉:
(block $outer
(block $inner
br $inner ;; 跳出 inner
unreachable ;; 永遠不執行
)
;; br $inner 到這裡
br $outer ;; 跳出 outer
)
;; br $outer 到這裡
5. 類型系統與驗證
5.1 類型分類
flowchart TB
subgraph ValueTypes["值類型"]
NUM["數值類型<br/>i32, i64, f32, f64"]
VEC["向量類型<br/>v128"]
REF["參考類型<br/>funcref, externref"]
end
subgraph FuncTypes["函數類型"]
FT["[t1*, ...] → [t2*, ...]"]
end
subgraph BlockTypes["區塊類型"]
BT["typeidx | valtype?"]
end
5.2 驗證算法
WASM 驗證是 單遍、線性時間 的:
# 簡化的驗證算法骨架
class Validator:
def __init__(self):
self.value_stack = [] # 值堆疊
self.control_stack = [] # 控制堆疊
def validate_instruction(self, opcode):
if opcode == 0x6A: # i32.add
self.pop_operand("i32")
self.pop_operand("i32")
self.push_operand("i32")
elif opcode == 0x02: # block
block_type = self.read_blocktype()
self.push_ctrl(
opcode="block",
start_types=[],
end_types=block_type.results
)
elif opcode == 0x0B: # end
frame = self.pop_ctrl()
self.push_operands(frame.end_types)
def pop_operand(self, expected_type):
if len(self.value_stack) == self.ctrl_frame.height:
if self.ctrl_frame.unreachable:
return "unknown"
raise ValidationError("stack underflow")
actual = self.value_stack.pop()
if expected_type != actual and actual != "unknown":
raise ValidationError("type mismatch")
return actual
def push_operand(self, type_):
self.value_stack.append(type_)
5.3 控制幀
class ControlFrame:
opcode: str # block, loop, if
start_types: list # 區塊開始時的堆疊類型
end_types: list # 區塊結束時的預期類型
height: int # 進入區塊時的堆疊高度
unreachable: bool # 是否已執行 br/return/unreachable
驗證流程圖:
flowchart TB
START["開始驗證"]
READ["讀取操作碼"]
CHECK["檢查堆疊類型"]
VALID{"類型<br/>匹配?"}
UPDATE["更新堆疊"]
NEXT{"還有<br/>指令?"}
PASS["驗證通過"]
FAIL["驗證失敗"]
START --> READ
READ --> CHECK
CHECK --> VALID
VALID -->|是| UPDATE
VALID -->|否| FAIL
UPDATE --> NEXT
NEXT -->|是| READ
NEXT -->|否| PASS
6. 記憶體模型
6.1 線性記憶體
WASM 使用連續的位元組陣列作為記憶體:
flowchart LR
subgraph LinearMemory["線性記憶體"]
direction TB
A0["地址 0x0000"]
A1["地址 0x0001"]
A2["地址 0x0002"]
AN["..."]
AMAX["地址 max"]
A0 --- A1 --- A2 --- AN --- AMAX
end
subgraph Access["存取方式"]
LOAD["i32.load addr"]
STORE["i32.store addr value"]
end
Access --> LinearMemory
6.2 記憶體安全
越界檢查:
有效地址 = offset + base_address
if 有效地址 + 存取大小 > 記憶體大小:
trap! # 運行時錯誤
沙盒隔離:
flowchart TB
subgraph WASM["WASM 模組"]
MEM["線性記憶體<br/>(沙盒內)"]
end
subgraph Host["宿主環境"]
HMEM["宿主記憶體"]
SYS["系統資源"]
end
MEM -.->|"無法直接存取"| HMEM
MEM -.->|"無法直接存取"| SYS
WASM -->|"只能通過導入"| Host
6.3 Wasm 3.0:64 位地址空間
;; Wasm 3.0 64 位記憶體
(memory i64 1) ;; 使用 i64 作為地址類型
i64.const 0x100000000 ;; 超過 4GB 的地址
i32.load ;; 從該地址載入
7. 模組系統
7.1 導入與導出
flowchart LR
subgraph Host["宿主環境 (JavaScript)"]
JSF["JavaScript 函數"]
JSM["JavaScript 記憶體"]
end
subgraph WASM["WASM 模組"]
IMP["Imports<br/>導入"]
CODE["Code<br/>函數實現"]
EXP["Exports<br/>導出"]
IMP --> CODE --> EXP
end
subgraph Consumer["使用者"]
CALL["呼叫導出函數"]
end
JSF --> IMP
JSM --> IMP
EXP --> CALL
7.2 JavaScript 整合
// 載入和實例化 WASM 模組
async function loadWasm() {
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const importObject = {
console: {
log: (x) => console.log(x)
},
js: {
memory: new WebAssembly.Memory({ initial: 1 })
}
};
const { instance, module } = await WebAssembly.instantiate(
bytes,
importObject
);
// 呼叫導出函數
const result = instance.exports.add(1, 2);
console.log(result); // 3
}
7.3 表與間接呼叫
;; 函數表用於間接呼叫
(table $t 2 funcref)
;; 元素段初始化表
(elem (i32.const 0) $func_a $func_b)
;; 間接呼叫
i32.const 0 ;; 表索引
call_indirect (type $sig)
flowchart TB
subgraph Table["Function Table"]
T0["Index 0 → func_a"]
T1["Index 1 → func_b"]
T2["Index 2 → null"]
end
CALL["call_indirect<br/>index=0"]
FUNC["func_a 執行"]
CALL -->|查表| T0
T0 --> FUNC
8. WASI:系統介面
8.1 WASI 概述
WASI (WebAssembly System Interface) 讓 WASM 能在非瀏覽器環境運行:
flowchart TB
subgraph WASM["WASM 模組"]
CODE["應用程式碼"]
end
subgraph WASI["WASI 介面"]
FD["fd_read / fd_write"]
PATH["path_open"]
CLOCK["clock_time_get"]
RANDOM["random_get"]
end
subgraph OS["作業系統"]
FS["檔案系統"]
NET["網路"]
TIME["系統時鐘"]
end
CODE --> WASI
WASI -->|"能力沙盒"| OS
8.2 WASI 能力模型
# 在 Python 中使用 wasmtime 運行 WASI 程式
from wasmtime import Store, Module, WasiConfig, Linker
store = Store()
wasi_config = WasiConfig()
wasi_config.inherit_stdout()
wasi_config.preopen_dir("/tmp", "/sandbox") # 沙盒目錄
store.set_wasi(wasi_config)
linker = Linker(store.engine)
linker.define_wasi()
module = Module.from_file(store.engine, "program.wasm")
instance = linker.instantiate(store, module)
instance.exports["_start"](store)
9. 編譯與優化
9.1 編譯流水線
flowchart LR
subgraph Source["源語言"]
C["C/C++"]
RUST["Rust"]
GO["Go"]
end
subgraph IR["中間表示"]
LLVM["LLVM IR"]
end
subgraph WASM["WebAssembly"]
WAT["WAT (文本)"]
WASM_BIN[".wasm (二進制)"]
end
subgraph Runtime["運行時"]
INT["解釋器"]
JIT["JIT 編譯"]
AOT["AOT 編譯"]
end
C --> LLVM
RUST --> LLVM
LLVM --> WASM_BIN
GO --> WASM_BIN
WASM_BIN --> WAT
WASM_BIN --> INT
WASM_BIN --> JIT
WASM_BIN --> AOT
9.2 常見編譯器
| 工具 | 源語言 | 特點 |
|---|---|---|
| Emscripten | C/C++ | 完整的 POSIX 模擬 |
| rustc | Rust | 原生 WASM 支援 |
| TinyGo | Go | 適合嵌入式 |
| AssemblyScript | TypeScript | 低學習曲線 |
| wasm-pack | Rust | NPM 整合 |
9.3 優化等級
# Emscripten 優化
emcc -O3 source.c -o output.wasm
# -O0: 無優化,用於調試
# -O1: 基本優化
# -O2: 更多優化
# -O3: 激進優化
# -Os: 優化大小
# -Oz: 激進優化大小
9.4 常見優化技術
| 技術 | 描述 |
|---|---|
| DCE | 死碼消除 |
| Inlining | 函數內聯 |
| Constant Folding | 常量折疊 |
| Loop Optimization | 迴圈優化 |
| Memory Coalescing | 記憶體存取合併 |
| SIMD | 向量化 |
10. 實際應用案例
10.1 應用領域
mindmap
root((WebAssembly))
瀏覽器
遊戲引擎
音視頻處理
圖像編輯
CAD/3D
伺服器
邊緣計算
無伺服器函數
插件系統
嵌入式
物聯網
智能合約
工具鏈
編譯器
程式碼分析
10.2 知名項目
| 項目 | 描述 |
|---|---|
| Figma | 協作設計工具,核心渲染引擎用 WASM |
| AutoCAD Web | 瀏覽器版 CAD |
| Google Earth | 3D 地球瀏覽 |
| TensorFlow.js | WASM 後端加速 |
| SQLite WASM | 瀏覽器內資料庫 |
| ffmpeg.wasm | 瀏覽器影音處理 |
11. 運行時實現
11.1 主流運行時
| 運行時 | 語言 | 特點 |
|---|---|---|
| V8 | C++ | Chrome/Node.js |
| SpiderMonkey | C++/Rust | Firefox |
| Wasmtime | Rust | 生產級獨立運行時 |
| Wasmer | Rust | 多後端、多語言整合 |
| wasm3 | C | 解釋器,極小體積 |
| WAMR | C | Intel,嵌入式優化 |
11.2 執行策略
flowchart TB
WASM[".wasm 模組"]
subgraph Interpreter["解釋器"]
INT["逐指令執行<br/>啟動快,運行慢"]
end
subgraph JIT["JIT 編譯"]
BASELINE["Baseline JIT<br/>快速編譯"]
OPT["優化 JIT<br/>慢編譯,快執行"]
end
subgraph AOT["AOT 編譯"]
NATIVE["原生碼<br/>最快執行"]
end
WASM --> Interpreter
WASM --> JIT
WASM --> AOT
BASELINE -->|"熱點偵測"| OPT
12. 總結
WebAssembly 核心優勢
| 特性 | 說明 |
|---|---|
| 效能 | 接近原生速度,比 JavaScript 快 10-100 倍 |
| 安全 | 沙盒執行,記憶體安全 |
| 可攜 | 一次編譯,到處運行 |
| 語言無關 | 支援 C/C++/Rust/Go/AssemblyScript 等 |
| 標準化 | W3C 標準,所有主流瀏覽器支援 |