WABTとは
WABT: The WebAssembly Binary ToolkitとはWebAssemblyのテキスト表現とバイナリ表現(以降wast、wasm)関連のツール群です。cliとして提供されてます。
ツール群概要
ツール名 | 説明 |
---|---|
wast2wasm | wastからwasmに変換する |
wasm2wast | wasmからwastに変換する |
wasm-interp | wasmのインタプリタ |
wast-desugar | S式、flat syntax、もしくはどちらも組み合わせて書かれたwastを簡潔なflat syntaxなwastに変換する(後述) |
準備
cloneしてmakeするだけです。各ツールはoutディレクトリに生成されます。
$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make
wast2wasm
wastからwasmに変換します。
試しに2つの32bit整数を足すだけの関数を定義してみます。内容は以下のようになります。
(module
(func (export "add") (param i32 i32) (result i32)
(i32.add (get_local 0) (get_local 1))))
wasmに変換。
$ wast2wasm add.wast -o add.wasm
これだけだと、だからどうしたとしか言い様がないのですが、生成されたwasmはブラウザで実行することができます(Firefox Nightly 53.0aで動作確認、Chrome Canary 57.0.2952.0では動作しなかったです)。ブラウザで使えるWebAssemblyのAPIはJavaScript API - WebAssemblyを参照してください。
fetch("add.wasm")
.then(res => res.arrayBuffer())
.then(WebAssembly.instantiate)
.then(({module, instance}) => {
console.log(instance.exports.add(1, 2));
});
wast-desugar
s式で書かれたwastをflat syntaxに変換します。これなんなのっていうのは、具体的な例をみてみるとわかりやすいかと思います。試しにadd.wastを変換してみると以下のようになります。
$ wast-desugar add.wast
(module
(func (;0;) (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(type (;0;) (func (param i32 i32) (result i32)))
(export "add" (func 0)))
ポイントは以下の三行。いわゆる逆ポーランド記法というやつで、wasm内でのバイナリ表現に対応しています。
get_local 0
get_local 1
i32.add
wast2wasm -v
で確認してみると
$ wast2wasm -v add.wast
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0d00 0000 ; WASM_BINARY_VERSION
; section "TYPE" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 01 ; num types
; type 0
000000b: 60 ; func
000000c: 02 ; num params
000000d: 7f ; i32
000000e: 7f ; i32
000000f: 01 ; num results
0000010: 7f ; i32
0000009: 07 ; FIXUP section size
; section "FUNCTION" (3)
0000011: 03 ; section code
0000012: 00 ; section size (guess)
0000013: 01 ; num functions
0000014: 00 ; function 0 signature index
0000012: 02 ; FIXUP section size
; section "EXPORT" (7)
0000015: 07 ; section code
0000016: 00 ; section size (guess)
0000017: 01 ; num exports
0000018: 03 ; string length
0000019: 6164 64 add ; export name
000001c: 00 ; export kind
000001d: 00 ; export func index
0000016: 07 ; FIXUP section size
; section "CODE" (10)
000001e: 0a ; section code
000001f: 00 ; section size (guess)
0000020: 01 ; num functions
; function body 0
0000021: 00 ; func body size (guess)
0000022: 00 ; local decl count
0000023: 20 ; get_local
0000024: 00 ; local index
0000025: 20 ; get_local
0000026: 01 ; local index
0000027: 6a ; i32.add
0000028: 0b ; end
0000021: 07 ; FIXUP func body size
000001f: 09 ; FIXUP section size
以下の部分ですね。
0000023: 20 ; get_local
0000024: 00 ; local index
0000025: 20 ; get_local
0000026: 01 ; local index
0000027: 6a ; i32.add
wasm2wast
wasmからwastに変換します。試しにadd.wasmを変換してみます。見たらわかると思いますが、wast-desugar
したものと大体一致してます。
$ wasm2wast add.wasm
(module
(type (;0;) (func (param i32 i32) (result i32)))
(func (;0;) (type 0) (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add)
(export "add" (func 0)))
wasm-interp
ざっくり言っていまうとwasmのインタプリタです。試しにadd.wasmを実行してみると
$ wasm-interp add.wasm
何も起きてない風ですね。-v
オプションをつけて詳細を見てみます。
$ wasm-interp add.wasm -v
begin_module(13)
begin_signature_section
on_signature_count(1)
on_signature(index: 0, params: [i32, i32], results: [i32])
end_signature_section
begin_function_signatures_section
on_function_signatures_count(1)
on_function_signature(index: 0, sig_index: 0)
end_function_signatures_section
begin_export_section
on_export_count(1)
on_export(index: 0, kind: func, item_index: 0, name: "add")
end_export_section
begin_function_bodies_section
on_function_bodies_count(1)
begin_function_body_pass(index: 0, pass: 0)
begin_function_body(0)
on_local_decl_count(0)
on_get_local_expr(index: 0)
on_get_local_expr(index: 1)
on_binary_expr("i32.add" (106))
end_function_body(0)
end_function_body_pass(index: 0, pass: 0)
end_function_bodies_section
begin_custom_section: 'name' size=10
end_custom_section
end_module
0| get_local $2
5| get_local $2
10| i32.add %[-2], %[-1]
11| drop_keep $2 $1
17| return
それっぽいのが出てきましたが、定義したadd関数が呼ばれているわけではないです。--run-all-exports
オプションを付けるとexportされている関数を全て実行してくれるそうなので試してみます。
$ wasm-interp add.wasm --run-all-exports
add() => error: argument type mismatch
各関数は引数がなしで実行されます。add関数は32bit整数の引数を2つとるため、引数の型が間違っていると怒られました。とりあえず引数なしで実行できるようadd.wastを少し書き直しましょう。
(module
(func (param i32 i32) (result i32)
(i32.add (get_local 0) (get_local 1)))
;; 1+2する関数をexport
(func (export "add_1_2") (result i32)
i32.const 1
i32.const 2
call 0))
wasmに変換したものを実行してみると無事表示されました。
$ wasm-interp add2.wasm --run-all-exports
add_1_2() => i32:3
面倒じゃんと思った方、実はwast2wasm
には--spec
オプションがあって、それを使うとテスト用のファイルを吐き出してくれます。まずは、add.wastを以下のように書き直します。
(module
(func (export "add") (param i32 i32) (result i32)
(i32.add (get_local 0) (get_local 1))))
(assert_return (invoke "add" (i32.const 1) (i32.const 2)) (i32.const 3))
(assert_return (invoke "add" (i32.const 61) (i32.const 34)) (i32.const 95))
テスト用のファイルを生成します。spec-test.0.wasmとspec-test.jsonの2つが作られます。
$ wast2wasm --spec add3.wast -o spec-test.json
spec-test.jsonの中身は以下のようになります。
{"source_filename": "add3.wast",
"commands": [
{"type": "module", "line": 1, "filename": "spec-test.0.wasm"},
{"type": "assert_return", "line": 4, "action": {"type": "invoke", "field": "add", "args": [{"type": "i32", "value": "1"}, {"type": "i32", "value": "2"}]}, "expected": [{"type": "i32", "value": "3"}]},
{"type": "assert_return", "line": 5, "action": {"type": "invoke", "field": "add", "args": [{"type": "i32", "value": "61"}, {"type": "i32", "value": "34"}]}, "expected": [{"type": "i32", "value": "95"}]}
]}
wasm-interp
に--spec
オプションを付けてテストしてみます。
$ wasm-interp --spec spec-test.json
2/2 tests passed.
テストできました。さらに--trace
オプションを付けると実行をトレースしてくれていい感じになります。
$ wasm-interp --spec --trace spec-test.json
>>> running export "add":
#0. 0: V:2 | get_local $2
#0. 5: V:3 | get_local $2
#0. 10: V:4 | i32.add 1, 2
#0. 11: V:3 | drop_keep $2 $1
#0. 17: V:1 | return
>>> running export "add":
#0. 0: V:2 | get_local $2
#0. 5: V:3 | get_local $2
#0. 10: V:4 | i32.add 61, 34
#0. 11: V:3 | drop_keep $2 $1
#0. 17: V:1 | return
2/2 tests passed.
wastでの開発が捗りますね(多分誰もやらないだろうが)。
まとめ
WABTを使ってみました。あんまりwastをそのまま書く人はいないと思うのですが、wasm2wast
で戻したものは一応読めるので一般ユーザーでも使うことあるのかな。実際のところ、オレオレ言語 -> wast -> wasmのように変換するために使うのが本来の使い方で、Elmとかだと、まさにコレって感じでしょう。