はじめに
少し前にwasm版 binaryen.jsを再ビルドしたときにオレオレ言語のコードジェネレータが動かなくなってハマった。原因はwasmのテキストフォーマットでのインストラクションの記述フォーマットが変わったためだった。
binaryen.js
オレオレ言語はwasmバイナリ出力をbinaryen.jsというライブラリに依存している。
binaryen.jsはbinaryenをemscriptenでJSにトランスパイルしたものである。Pure JSなのでブラウザでも使うことができる。
オレオレ言語ではbinaryen.jsを使って以下のステップでオレオレ言語ソースをwasmバイナリ化している。
- オレオレ言語ソースをトークン化
- パーサで解析してオレオレ言語のASTを作る
- ASTを解析しbinaryen IRモジュールに変換する。
- binaryen IRモジュールを最適化する
- binaryen IRモジュールからwasmバイナリを生成する
3.から5.までの処理はほぼbinaryen.jsの機能に頼っている。binaryen IRまで持ち込めば、最適化からバイナリ出力まではbinaryen.jsがやってくれるので、簡単にwasmバイナリを吐く言語を作ることができる。
binaryen.jsのwasmモジュール化
binaryen.jsはbinaryenをemscriptenでJSにトランスパイルしたものである。しかしemscriptenの現バージョンはc/c++ソースを直接wasmモジュールとして出力することが可能となっている。binaryen.jsもビルド・スクリプトにwasm化するためのパラメータが記載されているが、若干問題がありコメントアウトされている。
https://github.com/AssemblyScript/binaryen.js/blob/master/scripts/build.js#L140
// NOT YET FUNCTIONAL, but possible. Requires some changes to post.js
// console.log("\nCompiling binaryen-wasm.js ...\n");
// compileJs({
// post: path.join(sourceDirectory, "js", "binaryen.js-post.js"),
// out: "binaryen-wasm.js"
// });
まずコメントアウトされているソースコードに誤りがある。compileJS()
ではなくcompileWasm()
が正しい。
そしてcompileWasm()
は以下にある。
https://github.com/AssemblyScript/binaryen.js/blob/master/scripts/build.js#L109
/** Compiles the WebAssembly target. */
function compileWasm(options) {
run("python", [
path.join(emscriptenDirectory, "em++"),
"shared.bc"
].concat(commonOptions).concat([
"--post-js", options.post,
"--closure", "1",
"-s", "EXPORTED_FUNCTIONS=[" + exportedFunctionsArg + "]",
"-s", "ALLOW_MEMORY_GROWTH=1",
"-s", "BINARYEN=1",
"-s", "BINARYEN_METHOD=\"native-wasm\"",
"-s", "MODULARIZE_INSTANCE=1",
"-s", "EXPORT_NAME=\"Binaryen\"",
"-o", options.out,
"-Oz"
]));
}
上記を修正し、さらにbinaryen.jsのサポートコードを少しいじればwasm化することができるのではないかと思って取り組んだ結果、とりあえずwasm化することができた。その内容は以下に書いた。
でこれを使ってオレオレ言語を作ってたのだけれども、なぜか2018年の12月くらいに最新のbinaryen.jsでビルドしなおすとコードジェネレータが動かなくなってしまう不具合が発生した。
原因
動かなくなった原因はbinaryen.jsにあるのではなく、wasmのインストラクション記法に変更があったからだった。 wasmにはwasmのS式をwasmモジュールにする機能があるので、以下のwasmモジュールをソースコードで読み込んでコードジェネレータ実行時にwasmバイナリ化していたが、このコードがbinaryen.jsでバイナリ化できなくなってしまったのである。
(module
(export "i32tof32" $i32tof32)
(export "i64tof64" $i64tof64)
(memory $memory 1)
(export "memory" (memory $memory))
;; IEE754 float32のビットパターンを持つ32ビット整数値をf32に変換する
(func $i32tof32 (param $i i32) (param $minus i32) (result f32)
(f32.reinterpret/i32
(i32.xor
(get_local $i)
(get_local $minus)
)
)
)
;; IEEE754 float64のビットパターンを持つ2つの32ビット値(high,low)を元にして、64bit floatを返す
(func $i64tof64 (param $low i32) (param $high i32) (param $minus i32) (result f64)
(f64.reinterpret/i64
(i64.xor
(i64.or
(i64.shl
(i64.extend_u(get_local $high))
(i64.const 32)
)
(i64.extend_u (get_local $low))
)
(i64.shl
(i64.extend_u(get_local $minus))
(i64.const 32)
)
)
)
)
)
エラーを追っていくと、インストラクションの記法が2018年の終わりから2019年頭にかけて変わったことを知った。
https://github.com/WebAssembly/spec/issues/884#issuecomment-426433329
これに基づいてソースコードを修正するとコードジェネレータは動くようになった。ちなみに修正後のコードはこれ。
(module
(export "i32tof32" (func $i32tof32))
(export "i64tof64" (func $i64tof64))
(export "i64Neg" (func $i64Neg))
(memory $memory 1)
(export "memory" (memory $memory))
;; IEE754 float32のビットパターンを持つ32ビット整数値をf32に変換する
(func $i32tof32 (param $i i32) (param $minus i32) (result f32)
(f32.reinterpret_i32
(i32.xor
(local.get $i)
(local.get $minus)
)
)
)
;; IEEE754 float64のビットパターンを持つ2つの32ビット値(high,low)を元にして、64bit floatを返す
(func $i64tof64 (param $low i32) (param $high i32) (param $minus i32) (result f64)
(f64.reinterpret_i64
(i64.xor
(i64.or
(i64.shl
(i64.extend_i32_u (local.get $high))
(i64.const 32)
)
(i64.extend_i32_u (local.get $low))
)
(i64.shl
(i64.extend_i32_u (local.get $minus))
(i64.const 32)
)
)
)
)
;; 整数値の2の補数をとる
(func $i64Neg (param $low i32) (param $high i32)
(i64.store
(i32.const 0)
(i64.add
(i64.xor
(i64.or
(i64.extend_i32_u (local.get $low))
(i64.shl
(i64.extend_i32_u (local.get $high))
(i64.const 32)
)
)
(i64.const 0xffffffffffffffff)
)
(i64.const 1)
)
)
)
)
確かにまあ、変更前は'\/'がインストラクションに入ってるのが違和感あったもんなあ。。
ちなみに
今回Textフォーマットをbinaryen.jsでバイナリ化したけど、wabt(WebAssembly Binary Toolkit)にあるwat2wasmでもバイナリにすることができる。
binaryen.jsだとJS中でテキストフォーマットをバイナリにすることができる。wat2wasmだとS式だけではなくてリニア形式でもバイナリ化できたと思う。