编译 c 到 wasm
2021年05月01日 09:27GMT+8
编译 c 代码到 wasm, 不使用 emscripten sdk.
参考
https://surma.dev/things/c-to-webassembly/
wasm 指令集 显示了 wasm 所能执行的操作(但似乎并不完整)
一些需要用到的工具库, 使用 7zip 打开安装包或压缩档进行提取.
-
clang.exe
(编译器),wasm-ld.exe
(链接器),llvm-ar.exe
(归档工具) : 提取自 LLVM -
wasm-opt.exe
: 优化器, 提取自 WebAssembly/binaryen 用于 wasm 文件 -
wasm2wat.exe
: (可选), 因为使用wasm-opt.exe --print NAME.wasm
也能查看
我用的是 cygwin, 主要是因为我用 Makefile 但由于只是这些 exe 工具只接受 windows 路径, 因此传递路径时需要转换
wasm 内存模型
可以使用 -z stack-size=<bytes>
指定 stack 的大小
或者使用 --initial-memory=<bytes>
指定的是整个内存的大小.
例如在 Makefile 中, -z stack-size=$$((1024*64*10))
将指定 640K 大小的 stack.
可以使用 --stack-first
将 stack 移到第一个区块, 这样能更好地监测到堆栈溢出.
// |__memory_base
// | |__global_base
// | | |__data_end |__heap_base
// --------------------------------------------------
// | 空 | | | |
// | 白 | Data | Stack <--- | ---> Heap |
// | 处 | | | |
// --------------------------------------------------
// wasm 有内置几个变量, 可以使用 & 的形式访问, 如
extern int __data_end;
extern int __heap_base;
printf("data end: %d, heap base: %d, stack size: %d\n",
&__data_end, &__heap_base, &__heap_base - &__data_end
);
// 当使用 --export-all --allow-undefined 链接时, js 端看到这几个内置变量, 如下:
__global_base : Global // 数据段的起始点(我猜的), 这个值一般是 1024
__data_end : Global // 数据段的结束点, 即 stack 的最顶端
__heap_base : Global // 堆的起始点
__memory_base : Global // 这个值一直是 0, 似乎表示的是整个内存的起始点?
__table_base : Global // 未知.
__dso_handle : Global // 出现在 emscripten_tls_init.h 中, 似乎与多线程有关
__wasm_call_ctors : fn // 未知, 这个函数也许是 wasm 用于做某些初使化,
如果要实现自己的 malloc/free, 有几个需要注意的变量, 以及内置函数,
-
&__heap_base
: 你的内存管理程序应该将这个值作为 “起始值” -
int __builtin_wasm_memory_size(int index);
: 返回指定”内存块”的页面数, 每页为 64K 大小.注: 参数 index 目前为 0, 因为 wasm 目前只使用了 1 个内存块.
-
void __builtin_wasm_memory_grow(int index, int pages);
: 扩展指定的”内存块”到指定的页面数
我自己实现了一个 200 行左右的 malloc/free
指令集里还有个 __builtin_memcpy_inline, 我猜测应该是 clang 提供的一个宏, 用于复制已知大小的小结构
__builtin_memcpy_inline(dst, src, const int);
交互
wasm 默认使用 env 字段用于传递 js 数据到 c,
#define EM_IMPORT(NAME) __attribute__((import_module("env"), import_name(#NAME)))
// 如果没有 import_module, 则默认为 "env"
// 这个宏说明了我们可以自定义 module name.
当有编译参数: --import-memory
时, 需要由 js 提供 Memory 并传递给 env.memory.
var mem = new Memory({initial: 10}); // 64K * 10
var lib = new Instance(mod, {
env : {
memory : mem,
}
});
还有一个用于导出 c 函数到 js 的宏为:
#define EM_EXPORT(name) __attribute__((used, export_name(#name)))
// e.g:
EM_EXPORT(square) int arbitrary_name(int n) {
return n * n;
}
数据类型转换:
wasm 只允行将 i32, f32, f64 这 3 种类型与 js 交互