CatCoding

编译 WebAssembly 模块

2021-12-16

最近一年经常接触了 WebAssembly , 我把一些老的 C/C++ 代码通过 emcc 编译为 wasm 模块,也可以把 Rust 代码编译为 wasm。

这里做一个简单的总结,以及我在编译过程中碰到的问题。

WebAssembly 的优势

WebAsembly 定义了一个可移植、体积小、加载快的二进制格式作为编译结果。通过充分发挥通用硬件的能力(包括移动设备以及物联网),使其在大多数平台上能达到原生的执行效率。借助 wasi,WebAssembly 还可能运行在服务端。WebAssembly 的目标包括:

  1. 现有的 Web 平台完美结合并在其中运行:
    • 维护无版本、特性可测试、向后兼容的 Web 演变过程;
    • 和 JavaScript 执行在相同的语意环境中;
    • 允许和 JavaScript 相互的同步调用;
    • 严格遵守同源策略以及浏览器安全策略;
    • 和 JavaScript 一样,可以访问相同的 Web API 去调用浏览器的功能;以及
    • 定义一个可与二进制格式相互转化的人类可编辑的文本格式,并且支持查看源码的功能。
  2. 被设计为也可以支持非浏览器嵌入的运行形式,这样就可能在某些场景下替代 Docker。

C/C++ => wasm

首先需要安装 Emscripten SDK:
https://emscripten.org/docs/getting_started/downloads.html

移植一个 C/C++ 项目到 WebAssembly , 最简单的办法是把类似 gcc 命令换成 emcc,难点在于动态链接的第三方库,我们需要改成静态链接。一些常用的库已经被移植了,例如libc, libc++ and SDL,这些我们不需要手动处理。不在 emcc 预装里的库,我们只需要在编译的过程中加一些额外的参数,例如我下面这个项目就用到了 PNG,JPEG 这些库:

emcc -c dbgutil.c -o dbgutil.o
emcc -c qrtest.c -o qrtest.o
emcc -c decode.c -o decode.o
emcc -c identify.c -o identify.o
emcc -c quirc.c -o quirc.o
emcc -c version_db.c -o version_db.o
emcc -g -Oz --llvm-lto 1  -s STANDALONE_WASM *.o -o qrtest.wasm  -lm  -s USE_LIBJPEG -s USE_LIBPNG

另外 emcc 编译出来的 wasm 模块默认只能做纯计算,没有网络、系统文件等。如果有系统调用则需要运行在浏览器中,用浏览器的接口来模拟某些 C 函数调用,例如 C 语言中的系统调用 time 在 emcc 中被替成为了 JavaScript 代码:

clock: function() {
    if (_clock.start === undefined) _clock.start = Date.now();
    return ((Date.now() - _clock.start) * ({{{ cDefine('CLOCKS_PER_SEC') }}} / 1000))|0;
  }

Rust => wasm

Rust 是对 WebAssembly 支持得特别好的编程语言。我们可以使用 wasm-pack,或者安装 target:

rustup target add wasm32-wasi

然后在编译命令后面加参数:

cargo build --target wasm32-wasi

系统函数同样是个问题,有的第三方库可能会支持 wasm 格式,例如 getrandom - Rust (docs.rs)

参考

Main — Emscripten 3.0.1-git (dev) documentation
WASM Tutorial (marcoselvatici.github.io)
Introduction - Rust and WebAssembly (rustwasm.github.io)

公号同步更新,欢迎关注👻