2019.12.14
- 人×技術
ハンド Web アセンブル
この記事は FORK Advent Calendar 2019 14日目の記事です。
WebAssembly
WebAssembly は、Web ブラウザで実行可能なバイナリ形式のアセンブリ風言語。遅い JavaScript を補完する役割を担い、主要な Web ブラウザでは問題なく利用できる。
通常は C/C++ のコードから emscripten で LLVM を経由して WASM バイナリを生成するケースが多いが、今回はハンドアセンブルによる実装例を示す。題材は、単純な加算関数 i32 add(i32, i32)
とした。
WASM バイナリ
00000000 01100001 01110011 01101101
00000001 00000000 00000000 00000000
先頭 4 byte はマジックナンバーの \0asm
であり、後続 4 byte はバージョン番号にあたる。次のようにブラウザで読み込めば、問題ないことが確認できる。
binary = [
0b00000000, 0b01100001, 0b01110011, 0b01101101,
0b00000001, 0b00000000, 0b00000000, 0b00000000
]
new WebAssembly.Module(Uint8Array.from(binary))
フォーマットに問題がある場合は、ブラウザのコンソールでエラーが確認できる。
Error: WebAssembly.Module doesn't parse at byte 0: modules doesn't start with '\0asm' (evaluating 'new WebAssembly.Module(Uint8Array.from(binary))')
Type Section
Type Section には関数のシグネチャを記述する。
00000001 00000000
Type を示すセクションコードは 0x01
。ペイロード長は 0x00
としておく。
00000001 01100000 00000010 01111111
01111111 00000001 01111111
0x01
個の関数を func 型 0x60
で定義。0x02
個の引数を i32 型 0x7f
で定義。0x01
個の返値を i32 型 0x7f
で定義。これでペイロード長が 0x07
に確定するため、最終的には次のバイナリになる。
00000001 00000111 00000001 01100000
00000010 01111111 01111111 00000001
01111111
Function Section
Function Section には関数の宣言を記述する。
00000011 00000000
Function を示すセクションコードは 0x03
。ペイロード長は 0x00
としておく。
00000001 00000000
0x01
個の関数をシグネチャインデックス 0x00
で定義。これでペイロード長が 0x02
に確定するため、最終的には次のバイナリになる。
00000011 00000010 00000001 00000000
Export Section
Export Section にはモジュールのエクスポートを記述する。
00000111 00000000
Export を示すセクションコードは 0x07
。ペイロード長は 0x00
としておく。
00000001 00000011 01100001 01100100
01100100 00000000 00000000
0x01
個のモジュールを 0x03
文字の add (0x61
0x64
0x64
) で定義。これでペイロード長が 0x07
に確定するため、最終的には次のバイナリになる。
00000111 00000111 00000001 00000011
01100001 01100100 01100100 00000000
00000000
Code Section
Code Section には関数の中身を記述する。
00001010 00001001
Code を示すセクションコードは 0x0a
。ペイロード長は 0x00
としておく。
00000001 00000000
0x01
個の関数をペイロード長 0x00
と仮定して進め、
00000000 00100000 00000000 00100000
00000001 01101010 00001011
0x00
個のローカル変数を定義。get_local のオペコード 0x20
でインデックス 0x00
とインデックス 0x01
の引数を読み込む。i32.add のオペコード 0x6a
で加算。関数を 0x0b
で閉じる。これで関数のペイロード長が 0x07
、セクションのペイロード長が 0x09
に確定するため、最終的には次のバイナリになる。
00001010 00001001 00000001 00000111
00000000 00100000 00000000 00100000
00000001 01101010 00001011
実行結果
binary = [
0b00000000, 0b01100001, 0b01110011, 0b01101101,
0b00000001, 0b00000000, 0b00000000, 0b00000000,
0b00000001, 0b00000111, 0b00000001, 0b01100000,
0b00000010, 0b01111111, 0b01111111, 0b00000001,
0b01111111, 0b00000011, 0b00000010, 0b00000001,
0b00000000, 0b00000111, 0b00000111, 0b00000001,
0b00000011, 0b01100001, 0b01100100, 0b01100100,
0b00000000, 0b00000000, 0b00001010, 0b00001001,
0b00000001, 0b00000111, 0b00000000, 0b00100000,
0b00000000, 0b00100000, 0b00000001, 0b01101010,
0b00001011
]
module = new WebAssembly.Module(Uint8Array.from(binary))
instance = new WebAssembly.Instance(module)
// 4009
instance.exports.add(4000, 9)