WebAssemblyとは
スタックベースのインストラクションセットで、Webブラウザ内でネイティブバイナリに近い速度で動作することを目的としている。Mozillaが主導していたようだが、基本的にすべての近代的なブラウザで動作するようだ。RustやC言語のバックエンドとして使うことができる。
watとwasm
WebAssemblyにはテキスト表現とバイナリ表現があり、それぞれwatとwasmを拡張子として用いる。多くのコンパイラはバイナリ表現を直接吐くようだ。テキスト表現からバイナリ表現にコンパイル?するツールがあるのだが、シンボル名が[最近変わった]
(https://github.com/WebAssembly/spec/issues/884)らしく、Web上にあるコードがコンパイルできなくて混乱した。
wabt
コード変換やネイティブコードへの変換を実現するツールチェーンがこれ。
cmake
で普通にビルドできるが、m1 macのバイナリフォーマットがサポートされておらず、ネイティブコンパイラはビルドできなかった。が、watからwasmに変換するコードはm1でもビルドできたので気にしない。
コード例
フィボナッチ数を計算するコードを書いてみた。
WebAssemblyはスタックマシンなのだけど、純粋なスタックマシンではなく、例えば関数呼び出しの引数は、localと呼ばれる領域に用意されるし、スタック以外にローカルな変数領域を持つことができるらしい。そのあたりのことは下のページに書かれている。
とはいえば基本的な演算の考え方はごく普通のスタックマシンで、スタックトップに対して演算が行われ、結果がスタックトップに置かれる。関数呼び出しを行う際も、スタック上に引数をプッシュしてから呼び出す。にもかかわらず、呼び出された側ではローカルに入って見えるというのはちょっと不思議?
このコードのように、引数を何度も使う場合には、何度も複製しなくて済むので楽ではある。
;; if n < 2 return 1
;; else return fib(n-1) + fib(n-2)
(module
(func $fib (param f64) (result f64)
local.get 0 ; 引数nをスタックに積む
f64.const 2 ; 定数2をスタックに積む
f64.lt ; 比較する n < 2
if (result f64)
f64.const 1 ; 返り値をスタックに積む
else
local.get 0 ; 引数nをスタックに積む
f64.const 1 ; 定数1をスタックに積む
f64.sub ; 引く. n - 1
call $fib. ; n-1を引数にしてfibを再帰呼び出し。結果はスタックトップに置かれる。
local.get 0 ; 引数nをスタックに積む
f64.const 2 ; 定数2をスタックに積む
f64.sub ; 引く. n - 2
call $fib. ; n-2を引数にしてfibを再帰呼び出し。結果はスタックトップに置かれる。
f64.add ; fib(n-1) と fib(n-2)の結果がスタック上にあるのでそれらを加算。
end)
(export "fib" (func $fib)))
コンパイル
wabt のwat2wasm
を使ってコンパイルする。
% ../wabt/build/wat2wasm fib.wat
% od -x fib.wasm
0000000 6100 6d73 0001 0000 0601 6001 7c01 7c01
0000020 0203 0001 0707 0301 6966 0062 0a00 013a
0000040 0038 0020 0044 0000 0000 0000 6340 7c04
0000060 0044 0000 0000 f000 053f 0020 0044 0000
0000100 0000 f000 a13f 0010 0020 0044 0000 0000
0000120 0000 a140 0010 0ba0 000b
0000131
実行
Webブラウザで実行するには、JavaScriptから呼び出してやる。ごく普通の関数として利用できる。
<script type="text/javascript">
WebAssembly.instantiateStreaming(fetch('fib.wasm'))
.then(obj => {
or (let i = 1; i < 20; i++) {
console.log('fib '+ i +' = '+ obj.instance.exports.fib(i));
}
});
</script>
実行すると下のように出力がブラウザのコーソルに出る。
(index):5 fib 1 = 1
(index):5 fib 2 = 2
:
(index):5 fib 17 = 2584
(index):5 fib 18 = 4181
(index):5 fib 19 = 6765
所感
スタックベースのVMをブラウザに埋め込む、というのはJavaと何が違うのか、と考えてみたり。まあ、色々違うわけですが。