WABT: The WebAssembly Binary Toolkitを使ってみる

## WABTとは

[WABT: The WebAssembly Binary Toolkit](https://github.com/WebAssembly/wabt)とは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整数を足すだけの関数を定義してみます。内容は以下のようになります。

```add.wast
(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](http://webassembly.org/docs/js/)を参照してください。

```js
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)))
```

ポイントは以下の三行。いわゆる[逆ポーランド記法](https://ja.wikipedia.org/wiki/%E9%80%86%E3%83%9D%E3%83%BC%E3%83%A9%E3%83%B3%E3%83%89%E8%A8%98%E6%B3%95)というやつで、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を少し書き直しましょう。

```add2.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を以下のように書き直します。

```add3.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の中身は以下のようになります。

```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とかだと、まさにコレって感じでしょう。

## 参考

* [WABT: The WebAssembly Binary Toolkit](https://github.com/WebAssembly/wabt)
* [JavaScript API \- WebAssembly](http://webassembly.org/docs/js/)