🧩
WebAssemblyさくっと試すメモ
準備
brew install wabt
brew install wasmer
gem install wasmer
-
wabt は MS-DOS 時代を彷彿とさせる変換コマンド類の詰め合わせ
- wat2wasm と wasm-objdump をよく使う
- GitHub から clone してビルドする手順がよく紹介されているけど brew で入った
-
wasmer は WASM をコマンドラインで実行できるやつ
- ブラウザで実行しようとしたら難しすぎたのでこれでいい
-
wasmer gem は wasmer の Ruby バインディング(?)
- インストールに5分以上かかる
- フリーズしたのかと思って何度か止めた
- brew で入れてんのに wasmer 本体を独自にビルドしているっぽい
サンプルコード
a.wat
(module
(func (export "foo") (result i32)
(i32.const 1)))
- foo を呼んだら 1 を返す
-
(i32.const 1)
は 1 をスタックに push -
(result i32)
はスタックの直近の値を戻値にする - Emacs Lisp で
(defun foo () 1)
に相当する
-
- 拡張子 wat は WebAssembly Text format のこと
- どこから実行される?
- 単に上から実行とはならないらしい
- エントリーポイントも決まってないらしい
- 名前はなんでもいいので外から呼ぶために export が必要らしい
- module で囲まなかったら勝手に囲まれた
バイナリー変換
wat2wasm a.wat
a.wasm が出来上がる。
実行
$ wasmer a.wasm --invoke foo
# 1
export "foo"
としてあったから foo を呼べる。
wasm に変換するのがめんどい
$ wasmer a.wat --invoke foo
# 1
別に wasm に変換する必要なかった。wasmer が wat2wasm 相当をやってくれていた。wabt いらんかった。
wasm バイナリーのコードの意味を知りたい
$ wat2wasm -v a.wat 2>&1
# 0000000: 0061 736d ; WASM_BINARY_MAGIC
# 0000004: 0100 0000 ; WASM_BINARY_VERSION
# ; section "Type" (1)
# 0000008: 01 ; section code
# 0000009: 00 ; section size (guess)
# 000000a: 01 ; num types
# ; func type 0
# 000000b: 60 ; func
# 000000c: 00 ; num params
# 000000d: 01 ; num results
# 000000e: 7f ; i32
# 0000009: 05 ; FIXUP section size
# ; section "Function" (3)
# 000000f: 03 ; section code
# 0000010: 00 ; section size (guess)
# 0000011: 01 ; num functions
# 0000012: 00 ; function 0 signature index
# 0000010: 02 ; FIXUP section size
# ; section "Export" (7)
# 0000013: 07 ; section code
# 0000014: 00 ; section size (guess)
# 0000015: 01 ; num exports
# 0000016: 03 ; string length
# 0000017: 666f 6f foo ; export name
# 000001a: 00 ; export kind
# 000001b: 00 ; export func index
# 0000014: 07 ; FIXUP section size
# ; section "Code" (10)
# 000001c: 0a ; section code
# 000001d: 00 ; section size (guess)
# 000001e: 01 ; num functions
# ; function body 0
# 000001f: 00 ; func body size (guess)
# 0000020: 00 ; local decl count
# 0000021: 41 ; i32.const
# 0000022: 01 ; i32 literal
# 0000023: 0b ; end
# 000001f: 04 ; FIXUP func body size
# 000001d: 06 ; FIXUP section size
-
1
を返すだけでこんなに? - wasm からじゃなくて wat からわかる
- やっぱり wabt いる
- なぜかエラー出力するので標準出力に戻している
簡潔に表示する
$ wasm-objdump -d a.wasm
#
# a.wasm: file format wasm 0x1
#
# Code Disassembly:
#
# 000020 func[0] <foo>:
# 000021: 41 01 | i32.const 1
# 000023: 0b | end
- 今度は wasm からじゃないとだめ
- お約束のコードを除いて表示してくれる
単にダンプする
$ od -t x1 -c a.wasm
# 0000000 00 61 73 6d 01 00 00 00 01 05 01 60 00 01 7f 03
# \0 a s m 001 \0 \0 \0 001 005 001 ` \0 001 177 003
# 0000020 02 01 00 07 07 01 03 66 6f 6f 00 00 0a 06 01 04
# 002 001 \0 \a \a 001 003 f o o \0 \0 \n 006 001 004
# 0000040 00 41 01 0b
# \0 A 001 \v
# 0000044
0 'a' 's' 'm'
から始まるルールらしいことがわかる。
wasm から wat を復元する
$ wasm2wat a.wasm
# (module
# (type (;0;) (func (result i32)))
# (func (;0;) (type 0) (result i32)
# i32.const 1)
# (export "foo" (func 0)))
-
(;0;)
の顔文字はどゆこと? - 元のコードと違うし
- 対応する wat は上書きされない
TODO: 一行ずつ実行するには?
- デバッガーはない?
-
wasm-interp a.wasm
としても何も起きない - 結構調べたけどわかんない
- あとでわかったら書く
引数を取るコード
(module
(func (export "foo") (param i32 i32) (result i32)
(i32.add
(local.get 0)
(local.get 1))))
Code | 意味 |
---|---|
(param i32 i32) | 2つの引数を受けとる |
(local.get 0) | 1つ目の引数を push する |
(local.get 1) | 2つ目の引数を push する |
(i32.add a b) | スタックの上2つを足して1つ残す。引数があるのはなんで? |
(result i32) | スタックの直近1つを戻値とする |
-
local.get
をget_local
と書いてあるサンプルが多くてはまった- 昔は命令の名前が違ってた?
Ruby からバイナリを実行する
require "wasmer"
bin = [0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127, 3, 2, 1, 0, 7, 7, 1, 3, 102, 111, 111, 0, 0, 10, 6, 1, 4, 0, 65, 1, 11]
bin = bin.pack("C*")
store = Wasmer::Store.new
mod = Wasmer::Module.new(store, bin)
instance = Wasmer::Instance.new(mod, nil)
foo = instance.exports.foo
foo.call # => 1
Ruby からさくっと確認するための雛形
require "wasmer"
require "pathname"
wat_src = <<~EOT
(module
(func (export "foo") (param i32 i32) (result i32)
(i32.add
(local.get 0)
(local.get 1))))
EOT
Pathname("a.wat").write(wat_src)
puts `wat2wasm a.wat`
puts `wat2wasm -v a.wat 2>&1`
puts `wasm2wat a.wasm`
puts `wasm-objdump -d a.wasm`
puts `od -t x1 -c a.wasm`
puts `wat2wasm --invoke foo 1 2`
bin = IO.binread("a.wasm")
bin.unpack("C*") # => [0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 7, 7, 1, 3, 102, 111, 111, 0, 0, 10, 9, 1, 7, 0, 32, 0, 32, 1, 106, 11]
store = Wasmer::Store.new
mod = Wasmer::Module.new(store, bin)
instance = Wasmer::Instance.new(mod, nil)
foo = instance.exports.foo
foo.call(1, 2) # => 3
Discussion