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]                                       │
└─────────────────────────────────────────────────────────┘

類型編碼:

編碼類型
0x7Fi32
0x7Ei64
0x7Df32
0x7Cf64
0x70funcref
0x6Fexternref

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-0x11block, loop, if, br, call
參數化0x1A-0x1Cdrop, select
變數操作0x20-0x24local.get, local.set, global.get
記憶體0x28-0x3Ei32.load, i32.store
數值運算0x41-0xC4i32.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

分支指令:

指令操作碼描述
br0x0C無條件分支
br_if0x0D條件分支
br_table0x0E跳轉表
return0x0F返回

3.3 數值指令

i32 運算指令:

指令操作碼堆疊效果
i32.const n0x41→ i32
i32.add0x6Ai32, i32 → i32
i32.sub0x6Bi32, i32 → i32
i32.mul0x6Ci32, i32 → i32
i32.div_s0x6Di32, i32 → i32
i32.div_u0x6Ei32, i32 → i32
i32.and0x71i32, i32 → i32
i32.or0x72i32, i32 → i32
i32.xor0x73i32, i32 → i32
i32.shl0x74i32, i32 → i32
i32.eqz0x45i32 → i32
i32.eq0x46i32, 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 常見編譯器

工具源語言特點
EmscriptenC/C++完整的 POSIX 模擬
rustcRust原生 WASM 支援
TinyGoGo適合嵌入式
AssemblyScriptTypeScript低學習曲線
wasm-packRustNPM 整合

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 Earth3D 地球瀏覽
TensorFlow.jsWASM 後端加速
SQLite WASM瀏覽器內資料庫
ffmpeg.wasm瀏覽器影音處理

11. 運行時實現

11.1 主流運行時

運行時語言特點
V8C++Chrome/Node.js
SpiderMonkeyC++/RustFirefox
WasmtimeRust生產級獨立運行時
WasmerRust多後端、多語言整合
wasm3C解釋器,極小體積
WAMRCIntel,嵌入式優化

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 標準,所有主流瀏覽器支援

學習資源


參考資料