---
title: "Emscripten-C++编译流程"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/emscripten-cpp-compile-workflow/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/WebGL/Emscripten-C++编译流程.md"
content_hash: 3e5c7f75a8305916db146684d64187822f8273deef2f3d156a732944232baebb
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# Emscripten-C++编译流程

[C++ 编译流程](https://www.pystone.net/notes/cpp-compilation-pipeline/)


## Emscripten-C++ 编译流水线

> **目标**：将 C/C++ 源文件构建为可在浏览器或 Node.js 中运行的 **WebAssembly (.wasm)**。
> 传统 C++ 流程的 **预处理、AST 生成、IR 优化** 与此处完全一致，以下仅作一行提及；其余 **与原生 ELF/PE 流程不同的环节** 逐步展开。

---

### 0 · 驱动入口

```bash
emcc hello.cpp -O2 -sSTANDALONE_WASM=1 -o hello.wasm
```

`emcc` 是官方 Python 封装脚本，内部调用 **Clang → wasm-ld → Binaryen** 这条 LLVM-系工具链。流程如下。

---

### 1 · 预处理（与传统一致，简述）

```bash
emcc -E hello.cpp -o hello.i
```

* 展开宏与头文件，得到 `hello.i`（纯文本）。
* 与 GCC/Clang 原生流程完全相同，故不再赘述。

---

### 2 · 前端解析 → LLVM IR（与传统一致，简述）

```bash
emcc -emit-llvm -S hello.cpp -o hello.ll
```

* Clang 生成 **LLVM IR**（文本 `.ll` 或二进制 `.bc`）。
* 若启用 `-flto`，Bitcode 在稍后链接阶段参与全局优化。

```cpp
; ModuleID = 'hello.cpp'
source_filename = "hello.cpp"
target datalayout = "..."
target triple = "x86_64-unknown-linux-gnu"

@_ZSt4cout = external dso_local global %class.std::ostream, align 8

; 定义 square
define dso_local i32 @_Z6squarei(i32 %x) #0 {
entry:
  %mul = mul nsw i32 %x, %x
  ret i32 %mul
}

; 定义 main
define dso_local i32 @_Z4main() #0 {
entry:
  ; 调用 square(3)
  %call = call i32 @_Z6squarei(i32 3)
  ; 打印
  %0 = call %class.std::ostream* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKc(
            %class.std::ostream* @_ZSt4cout, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str, i32 0, i32 0))
  ; ...
  ret i32 0
}

```

---

### 3 · LLVM IR → **Wasm Relocatable Object**（差异重点）

```bash
emcc -c hello.cpp -o hello.o          # 默认 -sWASM_OBJECT_FILES=1
```

**目标后端切换**
   * LLVM 选用 `wasm32-unknown-emscripten` 后端，将 SSA-IR 翻译为 **WebAssembly 指令流**，而非 x86/AArch64 机器码。
**封装格式**
   * 产物 `hello.o` 是 **Wasm Relocatable Object**（非 ELF）：

     * 含 `.text` (函数)、`.data` (静态数据) 等节区；
     * 保留重定位表，供后续链接器合并。
     * 内部仍是 LLVM IR bitcode，但用 Wasm 格式封装。

*调试查看

```bash
llvm-objdump -d hello.o        # 反汇编 Wasm 指令
```

> LLVM Wasm 后端自 3.37+ 版本起已默认稳定，可直接生成可链接对象文件。([github.com][1])

---

### 4 · 归档静态库（与传统基本一致，简述）

```bash
emar rcs libhello.a hello.o
```

* `emar` 行为等同 GNU `ar`，成员可为 `.o` 或 `.bc`。
* 若希望在链接阶段做 **ThinLTO**，可直接归档 `.bc` 文件而非 `.o`。

* **Bitcode Archive**（Emscripten 默认）：当 bitcode 构建开启时，`libhello.a` 的成员是 LLVM IR bitcode 模块（`.bc` 或 `.o` 内含 bitcode）。
- 用 `ar t libhello.a` 可以列出成员文件；`file <member>` 能区分是 ELF 还是 LLVM bitcode。

```text
llvm-objdump --headers xxx.a
```

```python
Idx Name                Size     VMA      Type
  0 TYPE                00000073 00000000
  1 IMPORT              000009d7 00000000
  2 FUNCTION            00000627 00000000
  3 ELEM                000000c2 00000000
  4 DATACOUNT           00000002 00000000
  5 CODE                000430ba 00000000 TEXT
  6 DATA                00001221 00000000 DATA
  7 .debug_loc          000000ae 00000000
  8 .debug_abbrev       00000b32 00000000
  9 .debug_info         00034bde 00000000
 10 .debug_ranges       000031d8 00000000
 11 .debug_str          00058a86 00000000
 12 .debug_line         00016596 00000000
 13 linking             000471de 00000000
 14 reloc.CODE          0000a78f 00000000
 15 reloc.DATA          00000543 00000000
 16 reloc..debug_loc    0000001d 00000000
 17 reloc..debug_info   0001ee19 00000000
 18 reloc..debug_ranges 00004e9f 00000000
 19 reloc..debug_line   000029d2 00000000
 20 producers           00000086 00000000
 21 target_features     0000000d 00000000
```

### 5 · 链接：**wasm-ld + Binaryen**（差异重点）

```bash
emcc main.o libhello.a -O2 -sSTANDALONE_WASM=1 -o hello.wasm
```

1. **wasm-ld**
   * 解析符号、执行 LTO（若输入为 `.bc`）。
   * 完成地址布局与重定位，输出初步 `a.out.wasm`。([stackoverflow.com][2])
1. **wasm-opt（Binaryen）**
   * 根据 `-O0/1/2/3/s/z` 级别进行 Wasm-SSA 优化、栈化、死码删除、SIMD 展开等。
   * 体积与性能的最后一跳压缩。([github.com][1], [web.dev][3])

生成结果：
- `hello.wasm` —— 纯二进制 WebAssembly。不包含它那一套运行时 glue（文件系统、SDL、堆内存管理等 JS 接口），只输出一个自洽的 `.wasm` 模块。
- 适合在你自己的加载器（或 Node.js、Unity、嵌入式）里直接用 `WebAssembly.instantiate`。

### 6 · Glue 代码与打包（差异重点）

```bash
# Emscripten-C++编译流程
emcc hello_wasm.o \
     -O2 \
     --emit-symbol-map \
     -o hello.html
```

* **输出名决定打包形式**

| `-o` 目标      | 生成文件                      | 说明                           |
| :--------------- | :--------------------- | :-------------------------------- |
| `hello.wasm` | 仅 `.wasm`                 | 纯 Wasm，适合自行加载                |
| `hello.js`   | `.wasm` + `.js`           | JS 加载器（含 Emscripten runtime） |
| `hello.html` | `.wasm` + `.js` + `.html` | 可即点即开的测试页                    |

- `.js` 文件里包含了 Emscripten runtime（堆管理、文件 I/O、C 库接口）
- `.html` 只是给你一个“点它就能跑”的 demo 页面

* **运行示例（纯 Wasm）**

```bash
node --experimental-wasi-unstable-preview1 -e "const fs=require('fs');const wabt=require('wabt')"
## 或在浏览器中通过 `<script type="module">...</script>` 手动 fetch & instantiate
```

---

### 7 · 整体回顾

1. **相同步骤（仅简述）**
   预处理 → AST/IR 生成 → IR 优化 → （可选）静态库归档。
2. **关键差异（已详细）**

   * LLVM wasm 后端输出 **Wasm Relocatable .o**；
   * 链接器换成 **wasm-ld**，随后由 **Binaryen wasm-opt** 做指令级压缩与改写；
   * 产物不再是 ELF，可选择纯 `.wasm` 或附带 JS/HTML Glue。

按照以上流程，即可从标准 C/C++ 代码生成满足 Web 平台运行约束、并经过专用优化的 WebAssembly 文件。

[1]: https://github.com/WebAssembly/binaryen?utm_source=chatgpt.com "GitHub - WebAssembly/binaryen: Optimizer and compiler/toolchain library ..."
[2]: https://stackoverflow.com/questions/72894208/error-linking-o-file-to-wasm-with-wasm-ld?utm_source=chatgpt.com "Error linking .o file to .wasm with wasm-ld - Stack Overflow"
[3]: https://web.dev/articles/binaryen?utm_source=chatgpt.com "Compiling to and optimizing Wasm with Binaryen - web.dev"
[4]: https://emscripten.org/docs/compiling/WebAssembly.html?utm_source=chatgpt.com "Building to WebAssembly — Emscripten 4.0.11-git (dev) documentation"


## WebAssembly程序的一些特点

### 内存模型简述

* WebAssembly 只有一块 **线性内存**，按页（64 KB）扩展。
* 分段：静态区 + 栈 + 堆；JS 侧通过 `HEAP8/HEAP32/...` 视图直接访问。
* 想让堆自动增长：`-sALLOW_MEMORY_GROWTH=1`。

---

### 虚拟文件系统

* 内置 **MEMFS**（纯内存）、**IDBFS**（落盘到 IndexedDB）、**WORKERFS** 等。
* 静态打包：`--preload-file` / `--embed-file`。
* 运行期挂载：`FS.mount(FS.filesystems.IDBFS, {}, '/data')`。

---

### 多线程

* 通过 **PThreads ↔ Web Workers** 转译。
* 关键开关：`-sUSE_PTHREADS=1`（同时要在目标环境启用 SharedArrayBuffer）。
* Unity WebGL 构建需在 Player Settings 勾选 “Threads Support”。


### 压缩与体积优化

* Emscripten 本身不压缩输出。
* Unity WebGL 会额外生成 `.wasm.br / .wasm.gz` 并在启动脚本中自解压。
* 部分小程序平台（如微信小游戏）不支持自解压启动，需要关闭 Unity 的压缩或自行在服务器侧压缩后手动解压。

---
