この記事は FORK Advent Calendar 2019 14日目の記事です。

WebAssembly

WebAssembly は、Web ブラウザで実行可能なバイナリ形式のアセンブリ風言語。遅い JavaScript を補完する役割を担い、主要な Web ブラウザでは問題なく利用できる。

Can I use WebAssembly ?

通常は 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)

下記のボタンで WebAssembly の動作が確認できる。