mallocを使えるようにする(libcのコンパイル)
Clang で WebAssembly を作れるようになったので、次は C 標準ライブラリを使えるようにします。Emscripten と decodeIO/WebAssembly を見て、使えるものを Clang でコンパイルしてライブラリ化しました。これで、malloc も使えるようになります。
Clang で WebAssembly を作れるようにはなりましたが、C の標準ライブラリすらないため、正直使い物にはなりません。ということで、まずは C のライブラリを使えるようにしたいと思います。
Emscripten に頼る
別に Emscripten を使うわけではありません。Emscripten SDK には、ちょこちょこいじられた C 標準ライブラリのソースコード(実際には musl)が含まれています。それを作った Clang でコンパイルすれば、きっとうまくいくという発想です。
いくつかコンパイルの通らないコードもありましたが、適宜外し、ポコポココンパイルして .a ファイルに固めます。コンパイルの際には、emscripten の「system/lib/libc/musl」を基準として、
- ./src/internal
- ../../../include/libc
- ./arch/emscripten
にインクルードパスを通しておきます。あとは、musl の src 以下のファイルをコンパイルします。大体うまく行きます。
また、これだけですと __addtf3 などの関数が足りないと言ってきます。そこで、これも Emscripten に含まれている「compiler-rt」をコンパイルして、これもリンクするようにします。(printf などが使うようです) compiler-rt は、割とすんなり行きました。
足りない関数を decodeIO/WebAssembly から持ってくる
Emscripten の musl だけでは、__syscall や malloc が足りません。このあたりは、JavaScript 側を呼ぶ仕様なのでしょう。__syscall はおそらく、JavaScript 側を呼ぶしかないと思いますので、ひとまずダミーの関数群を作って(「lib/libc/musl/arch/emscripte/syscall_arch.h」あたりを見る)リンクしておきます。今後作ります。
malloc なども自分でつくろうかと思っていたのですが、面倒だったので decodeIO/WebAssembly から引っ張ってきました。malloc 自体は dlmalloc のもののようです。(ライセンスは CC0 1.0 Universal のようで、使いやすそうです。)
malloc の実装は、decodeIO/WebAssembly の「lib/musl-wasm32/src/malloc」にあり、これをコンパイルします。この malloc は、sbrk (プログラムの一番最後を拡張するためのシステムコールらしい。要はヒープの拡張)を呼ぶのですが、これもないため、decodeIO/WebAssembly の「lib/musl-wasm32/src/linux/sbrk.c」を引っ張ってきてコンパイルします。が、ちょっとトラブりました…
sbrk.c のコンパイルでトラブル
sbrk.c では、Clang のビルトイン関数「__builtin_wasm_current_memory」と「__builtin_wasm_grow_memory」を呼んでいるのですが、自前で用意した Clang 9.0.0 では、どうやらこれらが見つからないようです。
調べてみると、どうも名前と仕様が変わったようで、以下のようになっていました。増えた第一引数には 0 を渡しておけば良さそうです。
- __builtin_wasm_grow_memory(int) → __builtin_wasm_memory_grow(int, int)
- __builtin_wasm_current_memory() → __builtin_wasm_memory_size(int)
sbrk.c を上記のように書き換えることで、すんなりコンパイルが通りました。
テストしてみる
malloc で返ってくるアドレスの確認や、確保した領域への読み書きのテストをしてみました。
C 標準ライブラリのインクルードパスと「/arch/emscripten」へのパスを通し、コンパイルした C 標準ライブラリにリンクするようにして、wasm へコンパイルします。ファイルサイズは、11KB とまぁまぁですね。Emscripten を使うと、これだけでかなり巨大になってしまいますので、ちょっとしたライブラリを使う用途としては良さそうです。
作った wasm ファイルを JavaScript から実行します。
実行結果は以下のとおりです。
仕組みはよくわかりませんが、__heap_base が 72928 になっているので、まぁ、OK でしょう。
まとめ
今回で、動的メモリ確保と C 標準ライブラリが使えるようになりました。とはいえ、printf などは機能していないはずで、もちろんファイル IO も動きませんが、このあたりはおいおい考えるようにします。
Emscripten と decodeIO/WebAssembly を見たことで、だいぶ勉強になりました。特に、decodeIO/WebAssembly はコードがシンプルなので、勉強になります(dist/webassembly.js がなるほどという感じです)。
次は、C++ を使えるようにしたいと思います。Emscripten に libcxx と libcxxabi が含まれているので、この辺コンパイルすれば行けそうです。
書いていませんでしたが、目標は、Emscripten を使わずに、bullet を使えるようにすることです。