Appearance
WASM 编译管线 — 代码走读
Makefile WASM 构建目标
项目根目录的 Makefile 中定义了 WASM 构建的相关目标。下面逐段解读。
构建流程总览
编译器与标志定义
makefile
# WASM 专用编译器设置
WASM_CC := clang
WASM_TARGET := --target=wasm32-unknown-unknown
WASM_CFLAGS := -O2 -fno-builtin -ffreestanding \
-DMODERN=1 -DWASM=1 \
$(INCLUDES)逐行说明:
WASM_CC := clang— 使用 LLVM Clang 编译器,因为它原生支持 WebAssembly 目标WASM_TARGET := --target=wasm32-unknown-unknown— 指定 WASM 32位目标平台WASM_CFLAGS中的各个标志:-O2:二级优化,在编译速度和运行性能间取得平衡-fno-builtin:防止编译器用内置实现替换 GBA 自定义的内存操作函数-ffreestanding:告诉编译器不依赖标准库运行时-DMODERN=1 -DWASM=1:启用 WASM 兼容代码路径
源文件收集与编译规则
makefile
# 收集所有需要编译的 C 源文件
WASM_SRCS := $(filter-out $(EXCLUDE_SRCS), $(ALL_SRCS))
WASM_OBJS := $(WASM_SRCS:.c=.wasm.o)
# 模式规则:将 .c 编译为 .wasm.o
%.wasm.o: %.c
$(WASM_CC) $(WASM_TARGET) $(WASM_CFLAGS) -c $< -o $@关键细节:
EXCLUDE_SRCS排除不需要编译到 WASM 的文件(如 ARM 汇编文件)- 每个源文件独立编译,最终链接时合并
-c标志告诉编译器只编译不链接
链接规则
makefile
# WASM 链接
WASM_LDFLAGS := --no-entry \
--allow-undefined \
--initial-memory=268435456 \
--export=AgbMain \
--export=WasmRunFrame \
--export-all
output.wasm: $(WASM_OBJS)
wasm-ld $(WASM_LDFLAGS) -o $@ $^链接过程:
- 所有
.wasm.o文件作为输入传给wasm-ld --no-entry表明没有标准入口点--allow-undefined允许未解析的符号(GBA BIOS 函数)--initial-memory=268435456分配 256MB 初始内存--export=AgbMain和--export=WasmRunFrame显式导出两个关键函数--export-all额外导出所有全局符号
源码目录结构
关键函数索引
| 函数名 | 源文件位置 | 说明 |
|---|---|---|
AgbMain | src/main.c | 游戏主入口,初始化所有子系统 |
WasmRunFrame | src/main.c | 逐帧执行函数,由 JS requestAnimationFrame 回调 |
RunTasks | src/task.c | 执行所有活跃任务(协程调度器) |
VBlankIntrWait | src/gba/bg.c | 等待垂直空白中断 |
LZ77UnCompVram | src/util.c | LZ77 解压到 VRAM |
LoadPalette | src/palette.c | 加载调色板到 Palette RAM |
CopyToBgTilemapBuffer | src/bg.c | 复制图块映射到背景缓冲区 |
CreateSprite | src/sprite.c | 创建精灵,写入 OAM 数据 |
构建产物分析
产物大小分析:
- 单个
.wasm.o文件通常几 KB 到几十 KB - 最终
.wasm模块约 10-20MB,包含:- 编译后的游戏逻辑代码
- 通过 资源转换管线 嵌入的图形数据
- 字符串常量和查找表
- 函数索引和类型信息等 WASM 元数据
与 JS 运行时的接口
WASM 模块导出的函数通过以下方式与 JavaScript 交互:
javascript
// 简化的加载和调用流程
const wasmBytes = await fetch('output.wasm');
const { instance } = await WebAssembly.instantiate(wasmBytes, {
env: {
// JS 提供给 WASM 的导入函数(用于模拟 GBA BIOS)
// 这些符号因为 --allow-undefined 在链接时未解析
}
});
// 调用游戏入口
instance.exports.AgbMain();
// 帧循环(渲染在 WASM 内,宿主只搬运 RGBA)
function gameLoop() {
instance.exports.WasmRunFrame();
instance.exports.WasmRenderFrame(); // WASM 内部合成 RGBA 帧
ctx.putImageData(image, 0, 0); // 把 RGBA 搬到 Canvas
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);相关阅读:
- GBA 硬件模拟代码走读 — 了解
app.js如何初始化 WASM 内存与输入/存档(渲染在 WASM 内) - 资源转换管线代码走读 — 了解图形资源如何被编译进 WASM 模块