はじめに
WebAssemblyが熱くなって来ている感があります。
時代の流れに乗っかるべく、早速WebAssemblyを軽く触ってみたのですが、ググって出てくるチュートリアルはWebAssemblyというより、RustやC言語を動作させるEmscripten&LLVMのチュートリアルが多いことが分かったので、やったことをまとめ直し、アセンブリテキストを書いて動かす最小構成をチュートリアルとして公開します。なお初心者なので間違っている点も多いかと思いますのでご承知おき下さい(ご指摘いただけますと大変ありがたいです!)。
なお本稿は以下の環境でのみ動作確認をしています。
- Ubuntu 16.04
- Google Chrome 62.0
WebAssemblyについて
今回の内容を理解するために必要な最低限の知識です。
- WebAssemblyとはブラウザが実行可能なバイナリコードです(アセンブリ言語ではない)。
- WebAssemblyを、人間が読みやすくしたもの(≒アセンブリ言語)をWebAssemblyテキストフォーマットと呼びます。
- ブラウザは、C言語やRustで書かれたプログラムを、バイナリに変換することはできません。
- この他言語からバイナリへのコンパイルは、Emscriptenが担います。
-
ブラウザは、WebAssemblyテキストフォーマットも、バイナリに変換することはできません。
- このバイナリとテキストフォーマットの相互変換は、wabtが担います。
公式デモ
以上の前提から、テキストフォーマットで書いたプログラムは、wabtを用いてブラウザの外でアセンブルしておく必要がありそうです。しかし、実は**wabt(C++のプログラム)そのものが、Emscriptenによってブラウザで実行可能な形式にコンパイルできます。**これを用いてオンラインでテキストフォーマットを書いて実行できるデモがwabt公式より公開されています。
デモではデフォルトで「2引数を足す関数」を持った「モジュール」が定義されています。どうやらWebAssemblyはモジュール形式のプログラムのようです。
(module
(func $addTwo (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(export "addTwo" (func $addTwo)))
以下のチュートリアルは、この「公式デモ」の動作を自分で実装する、というものになります。
チュートリアル
ローカルでアセンブル & ブラウザで実行
ローカルでアセンブル
ローカルでアセンブルをするためにはwabtをコンパイルする必要があります。Ubuntu 16.04の環境では、公式の案内通り以下のコマンドでコンパイルできました。時間はそこまで要しません。リポジトリクローン時の--recursive
オプションを忘れないようにしてください。
$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make gcc-release
先ほどのデモのサンプルをコンパイルしてみましょう。まず、プログラムをaddTwo.watという名前で保存します。出力ファイルの拡張子.wat
はWebAssemblyTextの略です。
(module
(func $addTwo (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(export "addTwo" (func $addTwo)))
コンパイルは以下のコマンドで行います。wabtツール群の一つで、WebAssemblyテキストフォーマットからバイナリに変換するwat2wasm
を利用します。WebAssemblyを意味する.wasm
という拡張子のファイルに出力します。
$ ./out/gcc/Release/wat2wasm addTwo.wat -o addTwo.wasm
ブラウザで実行
WebAssebmlyは、JavaScript上でfetch
などを利用して動的にロードする必要があります。ローカルのHTMLファイルを開くとfetch
が利用できないので、今回はinput
タグを用います(分かる方は、HTTPサーバーで配信し、fetchでロードしてもOKです)。以下のファイルを保存し、ブラウザで開き、ファイル選択から先ほどのaddTwo.wasm
を選択します。
<input type="file" onChange="loadWasm()">
<script>
const loadWasm = async () => {
const readerEvent = await new Promise(resolve => {
const file = document.querySelector('input').files[0];
const reader = new FileReader();
reader.addEventListener('load', resolve);
reader.readAsArrayBuffer(file);
});
const buffer = readerEvent.target.result;
window.module = WebAssembly.Module(buffer);
window.instance = WebAssembly.Instance(module, {});
console.log(instance.exports, module, instance);
}
</script>
ブラウザのコンソール画面を開くと、instance.exports
にaddTwo
という関数が生えていることが分かるかと思います。WebAssebmlyはモジュールであり、インスタンス化することで実行され、exports
が得られるようです。instance.exports.addTwo
に引数を与え、正しく動くことを確認してみましょう。
なおMDNではWebAssembly.Module
やWebAssembly.Instance
による同期実行は高コストのため非推奨とされています。以下のようにWebAssembly.compile
とWebAssembly.instantiate
を用いることで非同期に実行可能です(バイナリを何にcompile
しているのでしょうか)。
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(mod);
console.log(instance.exports, module, instance);
また、モジュールを得る前のバッファをそのままinstantiate
に喰わせることも可能です。この場合はmodule
とinstance
をプロパティに持ったオブジェクトが得られるようです。
const {module, instance} = await WebAssembly.instantiate(buffer);
console.log(instance.exports, module, instance);
詳しくはMDNによる解説をご参照ください。
Emscriptenでwabtをコンパイル & ブラウザでアセンブル・実行
公式デモのところでも述べましたが、ローカルで実行していたwabt(C++のプログラム)そのものが、Emscriptenによってブラウザで実行可能な形式にコンパイル可能です。makeが全てやってくれるので大した内容ではありませんが、自分の環境でサクッとWebAssemblyテキストを実行できると嬉しいので、やってみます。
Emscriptenでwabtをコンパイル
先ほどクローンしたwabt
のディレクトリ直下でEmscriptenをクローンします。そしてmake
をします。それだけです。(emscriptenを別の場所に導入している場合は、クローンせずに環境変数EMSCRIPTEN_DIR
を指定することで代用可能です。)
$ git clone https://github.com/kripken/emscripten
$ make emscripten-release
./out/emscripten/Release/libwabt.js
にブラウザで実行可能なwabtが出力されました!なお注意事項として、Emscriptenを利用していますが、残念ながらここではWebAssemblyではなく素のJavaScriptファイルを出力するようです。
ブラウザでアセンブル・実行
出力されたlibwabt.js
を読み込んで、ブラウザ上でWebAssemblyテキストをアセンブルしてみましょう。スクリプトを読み込むと、wabtというオブジェクが利用可能になります。
<textarea></textarea><button onClick="run()">RUN</button>
<script src="./out/emscripten/Release/libwabt.js"></script>
<script>
const run = () => {
assembleAndExecute(document.querySelector('textarea').value);
}
const assembleAndExecute = async source => {
const sourceBuffer = (new TextEncoder('utf-8')).encode(source).buffer;
const parsed = wabt.parseWat('test', sourceBuffer);
const buffer = parsed.toBinary({log: true}).buffer;
window.module = await WebAssembly.compile(buffer);
window.instance = await WebAssembly.instantiate(module);
console.log(instance.exports, module, instance);
}
</script>
テキストエリアに、先ほどの(addTwoの)WebAssebmlyテキストを貼り付け実行してください!コンソールを見ると…addTwo
が得られました!足し算ができます!!
締め
emccビルドのwasmを用いると、ブラウザ上でアセンブルできるので、サクッと編集して動作確認をすることができます(公式デモでも可能ですが…)。MDNの解説を見ながら、ぜひ色々と試行錯誤してみてください。最後までお読み頂きありがとうございました!