All Articles

LuaをWebAssemblyにコンパイルして実行する話

LuaでもWebAssemblyがしたかった(した)

そろそろWebAssemblyやっていくぞと思って手を付け始めたんですが、もう既に色んな言語からwasmにコンパイルできるようになってるんですね。 Goがwasm対応したと聞いたのでGoでやろうと思っていたんですが、なぜかLuaでやり始めました。

作ったのはこちら。まだPoCで全然機能はないけど、Hello Worldはできました:

ysugimoto/webassembly-lua

なお、既に参考実装に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('関数名', '戻り値の型', '引数の型リスト');
const ret = wasmFunction(...);
console.log(ret)

のような呼び出し方法を取ります(直接コールすることもできますが、HEAPに入れたり出したりしないといけないのでこっちのほうが楽かな)。 一方でLuaには型がないので、それをwasmの関数としてCファイルを生成するのにどうすればいいのかな、と考えています。

生成自体は簡単ですが、どうやって型情報をつけるか…TypeScriptみたいな定義ファイルを一緒に食わせるとか、そういう感じになりそうですが、何か良い案があればぜひ教えてください。 あと、別モジュールに分けてrequireしてるものや、luarocksなどでインストールしたライブラリも今はバンドルできないので、これも対応したいと思います。

…実際Luaでwasmやる人がいるのかどうかはわかりませんが…

現場からは以上です。