目的
これは WebAssembly Advent Calendar 2017 23日目の記事です。
昨年、haskellのインタプリタのhugsをemscriptenを用いさまざまな困難がありましたが、printfデバッグで無事javascript(web版, nodejs版)にしてきました。
その後、firefox quantumによって上記のjavascriptは申し分ないほど高速化されたのですが、WebAssemblyによってさらに高速化できないか確認したくなり、今回asm.jsからWebAssemblyへの移行を行いました。
asm.jsからWebAssemblyへの移行とその問題
これがasm.jsからWebAssemblyへの移行に必要だった変更(PR)でした。
まとめると下記の5つです。
1, dlopenで読み込むshared libraryがjsからwasmになっている。
2, Module['locateFile']でwasmの読み込むパスを指定する。
3, wasmファイルのなかではEM_ASMできない。
4, wasmのロードではすべてのシンボルが解決できてないといけない。
5, emscriptenの吐き出すhtml(wasmをロードする関数が入っている)がasm.js版と違うので更新が必要。
dlopenで読み込むshared libraryがjsからwasmに
asm.jsからwasmになり、関数テーブルのフォーマットが変わりました。
関数テーブルはWebAssemblyオブジェクトのtableになり、従来のsignature(関数の型のようなもの)ごとに異なる配列であった関数テーブルよりスリムになりました。
内部の関数の構造がことなり、従来のshared libraryと互換性がなくなり、asm.jsとwasmは両立はできません。
wasmの形式のshared libraryに切り替えましょう。
wasmを読み込むパスの管理
shared libraryを使う側のファイルを-s WASM=1
をつけてコンパイルするとjsとwasmの両方のファイルが出力されます。しかし、node "jsの置いてあるディレクトリ"/hoge.js
のように実行してもwasmファイルを読み込んでくれません。
APIリファレンスにありますが、Module['locateFile']を設定しましょう。
shared libraryでEM_ASMが使えない
EM_ASMはC言語の中に埋め込まれたjsを呼び出す関数ですが、wasmの中では外部のjsの関数は呼べますがjsは評価できないので使えません。shared libraryなのにemscriptenの世界のアセンブラであるjsが呼べないという何とも拡張性のないものになってしました。
必要に応じて外部のjsで下記のようなEM_ASMをエミュレートする関数を用意しました。
実行時にevalされるので速度はもとのEM_ASMより遅いと予想されます。
function em_asm_0(exp){
eval(Pointer_stringify(exp));
}
function em_asm_1(exp,arg0){
var $0=arg0;
eval(Pointer_stringify(exp));
}
wasmのシンボル解決
asm.jsのときは実行時にシンボル解決して、使ってない関数のシンボルが解決できなくても問題ありませんでした。しかし、wasmになってwasmファイルをロードするときにすべてのシンボルが解決できるかチェックするようになりました。実行する前にチェックするのでデバッグしやすくなるため悪くない動作ですが、asm.jsのときとは違うのでシンボル解決のデバッグを今回行いました。
初期化方法
webでemscriptenのファイルを読む方法がasm.jsとwasmで違いますが、生成されたhtmlをみて直しましょう。
ベンチマーク
web版はfirefox quantumで起動時間を見ました。chromeだとwebassembly.compileを使っていないのにエラーがでて起動できませんでした。起動時間はwasmにすると遅くなりました。
アセットのほうが多いためwasmにした効果がまったく出てないです。
# asm.js版
https://junjihashimoto.github.io/hugs-js/
Finishまで7.61s
# wasm版
https://junjihashimoto.github.io/hugs-js/wasm/
Finishまで9.03s
nodejs版はasm.jsとwasmでインタプリタの起動時間を見てみました。
wasmの方は半分の時間で起動できました。
# asm.js版
$ time ./node_modules/.bin/runhugs ../shyougi-app/Shyougi.hs
123
real 0m9.799s
user 0m11.281s
sys 0m2.203s
# wasm版
$ time ../hugs-js/bin/runhugs ../shyougi-app/Shyougi.hs
123
real 0m5.789s
user 0m6.141s
sys 0m1.688s
まとめ
haskellのインタプリタであるhugsをasm.jsで作っていたものをwasmへ移行しました。
shared libraryに関する問題がありましたが、無事解決できました。
ブラウザでは移行後起動が遅くなりましたが、nodejsでは半分の時間で起動できるようになりました。