Skip to content

Emscripten

是什么

Emscripten 是一个将 C/C++ 代码编译为 WebAssembly(或 JavaScript)的完整工具链,由 LLVM/Clang 编译器、系统库和 glue code 生成器组成。它是最主流的 C/C++ 到 Web 的编译方案,被广泛用于将桌面应用和游戏移植到浏览器中。

重要说明:pokeemerald-wasm 项目没有使用 Emscripten。 项目选择直接使用 Clang + wasm-ld 作为编译工具链。这里介绍 Emscripten 是为了解释为什么不使用它,以及这种设计决策的原因。

为什么需要

在讨论将 C 代码编译为 WebAssembly 时,Emscripten 通常是最先被考虑的方案。了解 Emscripten 的特点有助于理解 pokeemerald-wasm 选择"不用 Emscripten"这一设计决策的合理性。

原理与实现

Emscripten 的工作方式

Emscripten 的核心组件:

  • emcc:替代 gcc 的编译器驱动程序,内部调用 Clang 进行编译。
  • emcc(链接):emcc 同时负责编译和链接,内部调用 wasm-ld 生成 .wasm.js 文件,不需要单独的链接器。
  • 系统库:提供了 POSIX 标准库、libc、OpenGL(通过 WebGL)等的 Web 实现。
  • Glue Code:自动生成的 JavaScript 桥接代码,处理内存管理、文件系统、模块加载等。
  • Runtime:一个较大的 JavaScript 运行时环境,提供文件系统模拟、网络、输入等抽象。

Emscripten 的优势

  • 开箱即用:对于标准 C/C++ 项目,emcc 通常可以直接编译。
  • 丰富的系统库:支持 POSIX API、OpenGL、SDL 等常用库。
  • 虚拟文件系统:提供 MEMFS/IDBFS 等文件系统抽象。
  • 自动 glue code:自动生成 JavaScript 绑定代码。

Emscripten 的劣势(在 GBA 场景下)

  • 运行时开销:Emscripten 生成的 JavaScript 运行时较大(通常几百 KB 到几 MB),对于 GBA 这种底层硬件操作场景是不必要的负担。
  • 过度抽象:GBA 代码直接操作硬件寄存器和内存,不需要 POSIX 文件系统、OpenGL 等抽象层。
  • 构建复杂性:Emscripten 引入了额外的依赖和构建步骤,增加了维护成本。
  • 灵活性受限:Emscripten 的自动化流程有时会与项目的特殊需求冲突,难以精细控制 Wasm 模块的行为。

在 pokeemerald-wasm 中的实现(设计决策)

pokeemerald-wasm 明确选择不使用 Emscripten,而是直接使用 Clang + wasm-ld,原因如下:

  1. GBA 代码的特殊性:pokeemerald 的源码是针对裸机硬件编写的,没有使用任何标准库。代码直接操作内存地址(VRAM、OAM 等),不依赖 libc、POSIX 或其他系统抽象。Emscripten 提供的系统库在这里完全无用。

  2. 精简构建:直接使用 Clang + wasm-ld 只生成纯净的 .wasm 文件,没有额外的 JavaScript 运行时。JavaScript 侧的 app.js 完全自定义,只包含渲染和输入处理的必要逻辑。

  3. 内存控制:通过 wasm-ld 的 --initial-memory=268435456 直接指定 256MB 内存,保持 GBA 原始的内存地址布局。Emscripten 的内存管理机制会干扰这种直接映射。

  4. 导出控制:通过 wasm-ld 的 --export=AgbMain --export=WasmRunFrame 精确控制导出接口。不需要 Emscripten 的 ccall/cwrap 等封装。

  5. 构建简洁:项目的 Wasm 构建只依赖 Clang 和 wasm-ld,无需安装 Emscripten SDK(本身约 300MB)。这使得构建环境更轻量、更易复现。

这种设计决策体现了"最小化依赖"的原则——只用必要的工具完成必要的功能。

相关概念

  • WebAssembly — Emscripten 的编译目标格式
  • wasm-ld — 项目实际使用的 WebAssembly 链接器