LuaでもWebAssemblyがしたかった(した)
そろそろWebAssemblyやっていくぞと思って手を付け始めたんですが、もう既に色んな言語からwasmにコンパイルできるようになってるんですね。 Goがwasm対応したと聞いたのでGoでやろうと思っていたんですが、なぜかLuaでやり始めました。
作ったのはこちら。まだPoCで全然機能はないけど、Hello Worldはできました:
なお、既に参考実装にwasm_luaというのがあるんですが、これは入力したLua Scriptをwasm上で実行して結果を得る、というもので、これだと自分で書いたスクリプトをwasmにするのとはちょっと違うなーってことで、参考にしつつイチから作りました。
Lua -> C -> emscripten
やってることはほぼ同じで、バンドル対象のLuaファイルのコードをバイナリにして埋め込みつつ、emscriptenでコンパイルできるCのファイルを組み立ててコンパイル、という手順を取っています。 LuaはCから変数操作などわりとやりたい放題っぽくて、Lua manualみながらガチャガチャやってたらいけました。
コンパイル用のCファイルの組み立てはテンプレーティングみたいな感じだったのですぐできたんですが、LuaのRuntimeを混ぜつつコンパイルするのにちょっと手間取りました。
Luaを普通にソースからインストールのは簡単なんですが、emscriptenでコンパイルするときは generic でコンパイルしないといけなくて、これもwasm_luaに方法が書いてあったんですが、
make generic CC="emcc -s WASM=1"
としてemscriptenようにコンパイルしたものを使わないとダメでした。で、実際にwasmを作るときにコンパイル済みの liblua.a をリンクして、ヘッダを読み込ませることでコンパイルが通ります。具体的には、
emcc -I/path/to/lua/src generated.c /path/to/lua/src/liblua.a -s WASM=1
みたいな感じでコンパイルするとLuaの動くwasmが生成されます。C力低すぎ。。。
Hello Worldするサンプルは examplesに置いたのでお試しください。 あと、上記の諸々の環境を手元で作るの結構大変なので、構築済みのDocker Imageも置きました。
Docker Hub - ysugimoto/webassembly-lua
このコンテナ内でやると、手元の環境を汚さずに済みます:
docker pull ysugimoto/webassembly-lua
docker run --rm -v $PWD:/src ysugimoto/webassembly-lua emcc-lua hello_world.lua
詳細はREADMEを見てください。
戻り値・引数の型問題
現状はまだ文字列、つまりconst char*のやりとりしかできません。引数も取れません。 これはWebAssembly側、つまりJavaScript側からも関数呼び出し時には型指定をするようになっていて、
const wasmFunction = Module.cwrap('関数名'undefined '戻り値の型'undefined '引数の型リスト');
const ret = wasmFunction(...);
console.log(ret)
のような呼び出し方法を取ります(直接コールすることもできますが、HEAPに入れたり出したりしないといけないのでこっちのほうが楽かな)。 一方でLuaには型がないので、それをwasmの関数としてCファイルを生成するのにどうすればいいのかな、と考えています。
生成自体は簡単ですが、どうやって型情報をつけるか…TypeScriptみたいな定義ファイルを一緒に食わせるとか、そういう感じになりそうですが、何か良い案があればぜひ教えてください。 あと、別モジュールに分けてrequireしてるものや、luarocksなどでインストールしたライブラリも今はバンドルできないので、これも対応したいと思います。
…実際Luaでwasmやる人がいるのかどうかはわかりませんが…
現場からは以上です。