LoginSignup
14
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-15

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整数を足すだけの関数を定義してみます。内容は以下のようになります。

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を参照してください。

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を少し書き直しましょう。

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

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

参考

14
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
6