Appearance
wasm-ld
是什么
wasm-ld 是 LLVM 项目中 lld 链接器的 WebAssembly 后端,专门用于将编译后的目标文件(.o)链接为最终的 WebAssembly 模块(.wasm)。它是 Clang 编译 Wasm 代码时使用的默认链接器。
为什么需要
在 pokeemerald-wasm 项目中,游戏的 C 源码被拆分为数百个源文件,每个文件由 Clang 独立编译为 .o 目标文件。wasm-ld 负责将这些分散的目标文件合并为一个可被浏览器加载执行的 .wasm 模块。它的关键职责包括:
- 符号解析:将不同目标文件中相互引用的函数和变量进行链接。
- 内存布局:配置 Wasm 模块的初始内存大小和内存布局。
- 接口导出:将指定的函数导出为 JavaScript 可调用的接口。
- 未定义符号处理:处理那些在 Wasm 模块内部没有实现的符号引用。
原理与实现
wasm-ld 的常用选项
在 pokeemerald-wasm 项目中,wasm-ld 使用了以下关键链接选项:
| 选项 | 说明 |
|---|---|
--no-entry | 不要求入口点(main 函数)。Wasm 模块不需要标准的 main 入口,而是通过导出函数被 JavaScript 调用。 |
--allow-undefined | 允许未定义的符号。某些 GBA 硬件相关的函数在 Wasm 环境中不需要真实实现。 |
--initial-memory=268435456 | 设置初始内存为 256MB(256×1024×1024=268435456 字节)。这确保 Wasm 线性内存有足够空间容纳 GBA 的完整内存映射。 |
--export=AgbMain | 导出 AgbMain 函数,这是游戏的初始化入口。 |
--export=WasmRunFrame | 导出 WasmRunFrame 函数,这是逐帧执行的入口,JavaScript 每帧调用一次。 |
链接过程
wasm-ld 的链接过程大致如下:
- 输入解析:读取所有
.o目标文件和库文件,解析符号表和重定位信息。 - 符号合并:解析所有符号引用,将定义和引用进行匹配。
- 段合并:将所有目标文件的代码段(
.text)、只读数据段(.rodata)、数据段(.data)、BSS 段(.bss)合并到对应的位置。 - 内存分配:根据
--initial-memory分配线性内存,将数据段放入内存。 - 重定位:修正所有地址引用,使其指向正确的内存偏移。
- 输出生成:生成最终的
.wasm二进制模块文件。
与传统链接器的区别
wasm-ld 与传统的 ELF 链接器(如 ld、lld 的 ELF 后端)的主要区别:
- 输出格式为 WebAssembly 二进制格式(
.wasm),而非 ELF。 - 内存模型基于 Wasm 线性内存,而非操作系统的虚拟内存。
- 没有动态链接(所有内容静态链接到一个模块中)。
- 通过
--export显式导出函数,而非使用动态符号表。
在 pokeemerald-wasm 中的实现
在项目的 Makefile 中,wasm-ld 的调用方式大致如下:
bash
wasm-ld \
--no-entry \
--allow-undefined \
--initial-memory=268435456 \
--export=AgbMain \
--export=WasmRunFrame \
-o pokeemerald.wasm \
[所有 .o 文件...]链接完成后,生成的 pokeemerald.wasm 文件被 JavaScript 通过 WebAssembly.instantiate() 加载,然后通过导出的 AgbMain 和 WasmRunFrame 函数与游戏逻辑交互。
相关概念
- WebAssembly — wasm-ld 的输出目标格式