从我首次深入 WebAssembly 中汲取的经验教训

2025 年 4 月 4 日,从解决水排序谜题的程序开始,类似英国方块求解器,添加 SDL2 用户界面后可在桌面运行,妻子希望在手机上玩,于是需将其重写为 JavaScript 或使用 WebAssembly(WASM),最终成功,游戏及 u-config 克隆都能在浏览器中运行且二进制文件小。

  • WASM 介绍:是定义抽象栈机及相关格式的规范,有 i32、i64、f32、f64 四种类型,线性可寻址内存从 0 开始,无对齐限制,有 32 位和 64 位版本,适合 64 位主机小指针,浏览器中它类似 JavaScript 之于 Java。
  • 相关组件

    • WASM 模块:编译链接的图像,包含代码等部分,出口表列出入口点,可选 start 部分指示初始化函数,只能通过导入函数影响外部世界。
    • WASM 运行时:加载模块,链接导入表,类型检查,执行 start 函数及入口点。
    • WASM 编译器:将高级语言编译为低级 WASM,需应用二进制接口(ABI),编译时函数索引未知需链接器修补。
    • WASM 链接器:确定 WASM 模块形状并链接编译器生成的函数,LLVM 的 wasm-ld 与 Clang 配合。
    • 语言运行时:高级语言通常有标准库,需映射到标准化的导入集(如 WASI),本文直接使用 raw WASI 避免语言运行时。
  • 示例程序

    • 简单 C 函数编译为 WASM:float norm(float x, float y),Clang 将float映射为f32,生成$norm函数。
    • 带有导入和导出的 C 函数:void f(int *);void example(int x),展示import_nameexport_name属性的作用及 Clang ABI 中指针的映射等。
    • 处理 null 指针的函数:int get(int *p),使用-fsanitize=undefined -fsanitize-trap检测 null 指针,运行时陷阱处理。
    • 包含memsetmemmove的函数:void clear(void *buf, long len),使用memory.fill指令。
    • 处理结构:结构通过地址传递,返回结构时需在 callee 的地址空间分配空间。
  • 水排序游戏:游戏仅导出三个函数game_initgame_rendergame_update,使用 IMGUI 风格渲染,在 SDL 和 web 版本中表现相同,先开发 SDL 版本对提高开发效率很关键,游戏有 10000 个种子按难度排序。
  • WASM 系统接口(WASI)

    • Hello World 程序:导出传统_start入口点,使用fd_write函数输出,处理返回的errno值,还可通过proc_exit退出程序,获取命令行参数需调用args_sizes_getargs_get,打开文件更复杂,需处理预打开等。
    • u-config WASM 端口:可下载pkg-config.wasm模块在 WASI 兼容的运行时中运行,通过-mount选项映射文件系统路径。
阅读 13
0 条评论