Edited at

WebAssemblyはじめの一歩

More than 1 year has passed since last update.


はじめに

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の略です。


addTwo.wat

(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を選択します。


index.html

<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.exportsaddTwoという関数が生えていることが分かるかと思います。WebAssebmlyはモジュールであり、インスタンス化することで実行され、exportsが得られるようです。instance.exports.addTwoに引数を与え、正しく動くことを確認してみましょう。

なおMDNではWebAssembly.ModuleWebAssembly.Instanceによる同期実行は高コストのため非推奨とされています。以下のようにWebAssembly.compileWebAssembly.instantiateを用いることで非同期に実行可能です(バイナリを何にcompileしているのでしょうか)。

const module = await WebAssembly.compile(buffer);

const instance = await WebAssembly.instantiate(mod);
console.log(instance.exports, module, instance);

また、モジュールを得る前のバッファをそのままinstantiateに喰わせることも可能です。この場合はmoduleinstanceをプロパティに持ったオブジェクトが得られるようです。

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というオブジェクが利用可能になります。


index.html

<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の解説を見ながら、ぜひ色々と試行錯誤してみてください。最後までお読み頂きありがとうございました!