Skip to content

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 的链接过程大致如下:

  1. 输入解析:读取所有 .o 目标文件和库文件,解析符号表和重定位信息。
  2. 符号合并:解析所有符号引用,将定义和引用进行匹配。
  3. 段合并:将所有目标文件的代码段(.text)、只读数据段(.rodata)、数据段(.data)、BSS 段(.bss)合并到对应的位置。
  4. 内存分配:根据 --initial-memory 分配线性内存,将数据段放入内存。
  5. 重定位:修正所有地址引用,使其指向正确的内存偏移。
  6. 输出生成:生成最终的 .wasm 二进制模块文件。

与传统链接器的区别

wasm-ld 与传统的 ELF 链接器(如 ldlld 的 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() 加载,然后通过导出的 AgbMainWasmRunFrame 函数与游戏逻辑交互。

相关概念