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 で返ってくるアドレスの確認や、確保した領域への読み書きのテストをしてみました。

コード1: test.c

C 標準ライブラリのインクルードパスと「/arch/emscripten」へのパスを通し、コンパイルした C 標準ライブラリにリンクするようにして、wasm へコンパイルします。ファイルサイズは、11KB とまぁまぁですね。Emscripten を使うと、これだけでかなり巨大になってしまいますので、ちょっとしたライブラリを使う用途としては良さそうです。

作った wasm ファイルを JavaScript から実行します。

コード2: index.html

実行結果は以下のとおりです。

仕組みはよくわかりませんが、__heap_base が 72928 になっているので、まぁ、OK でしょう。

まとめ

今回で、動的メモリ確保と C 標準ライブラリが使えるようになりました。とはいえ、printf などは機能していないはずで、もちろんファイル IO も動きませんが、このあたりはおいおい考えるようにします。

Emscripten と decodeIO/WebAssembly を見たことで、だいぶ勉強になりました。特に、decodeIO/WebAssembly はコードがシンプルなので、勉強になります(dist/webassembly.js がなるほどという感じです)。

次は、C++ を使えるようにしたいと思います。Emscripten に libcxx と libcxxabi が含まれているので、この辺コンパイルすれば行けそうです。

書いていませんでしたが、目標は、Emscripten を使わずに、bullet を使えるようにすることです。