WebAssembly ファイル形式
コンパイル済みの WebAssembly バイナリモジュール。
- MIME タイプ
- application/wasm
- 拡張子
- .wasm
- カテゴリ
- 実行ファイル・バイトコード
マジックナンバー(ファイル署名)
| 0x00000000 | 0061736D | ·asm | 0x00 'asm' |
WebAssembly 形式について
WebAssembly のモジュールは、このカタログの中で最も規則的なバイナリ形式です。8 バイトの前置きの後ろにセクションが並び、各セクションは 1 バイトの id、LEB128 の長さ、その長さぶんのペイロードという形をしています。8 バイト目より先に固定オフセットは 1 つもありませんが、必要もありません。読み手はセクションを順に歩き、興味のないものは読み飛ばせます。モジュールの残りをダウンロードしながらブラウザがコンパイルを始められるのはこの性質のおかげです。既知のセクションは決まった順序で現れなければならないので、検証器は 2 度読みせずに不正なモジュールを弾けます。
ヘッダーの構造(バイト単位)
| オフセット | 長さ | フィールド | 意味 |
|---|---|---|---|
| 0 | 4 | magic | 00 61 73 6D — NUL バイト 1 つに ASCII の asm が続きます。先頭の NUL は意図的で、このファイルが妥当なテキストにならないことを保証します。文字エンコーディングに手を出す何かを経由したモジュールは、予測できない場所ではなく 1 バイト目で失敗します。 |
| 4 | 4 | version | 32 ビットのリトルエンディアンのバージョンで、現在は 01 00 00 00(バージョン 1)。形式が世に出てから変わっていません。以降の拡張はこの番号を上げるのではなく、すべて後方互換の形で行われてきたからです。 |
| 8 | 1 | First section id | セクションの種類を示す 1 バイト。0 が custom、1 が type、2 が import、3 が function、4 が table、5 が memory、6 が global、7 が export、8 が start、9 が element、10 が code、11 が data、12 が data count。既知のセクションは昇順に、しかもそれぞれ最大 1 回しか現れないので、前置きの直後のバイトはたいてい 1(type)か、ツールチェーンが前に置いた 0(custom)です。 |
| 9 | variable | Section size (LEB128) | セクション本体のバイト数を符号なし LEB128 で。1〜5 バイトで、各バイトが 7 ビットの値を運び、最上位ビットが継続フラグです。この形式で唯一の長さフィールドであり、セクションの境界はすべてこれを足して求めます。オフセット 8 より後ろに固定アドレスが存在しないのはそのためです。 |
| variable | variable | Section body | エントリのベクタが並び、そのベクタ自体も LEB128 の個数が前に付きます。次のセクション id は、サイズフィールドが主張した最後のバイトの直後から始まるので、id とサイズを足していくとちょうどファイル末尾に着地するはずです。この形式に組み込まれた切り詰め検出です。 |
| variable | variable | Custom sections (id 0) | 意味論を持たない名前付きのブロブです。スタックトレース用の関数名を持つ 'name' セクション、DWARF のデバッグ情報、生成元のメタデータ、ターゲット機能の記録など。他のセクションの間ならどこにでも置けて、リリースビルドがモジュールを小さくするためにやっているのは、まさにこれを剥がすことです。 |
似た形式との見分け方
| 紛らわしい形式 | 見分け方 |
|---|---|
| WAT text | 同じモジュールのテキスト形式は、バイナリの変種ではなくソースです。開き括弧と module という語で始まり、全体が UTF-8 です。00 61 73 6D で始まるものはバイナリ形式で、ブラウザがインスタンス化できるのはそちらだけです。 |
| GZIP | 実務上は一番よくある混同です。.wasm はほぼ必ず圧縮して配信されるからです。1F 8B で始まるファイルは gzip ストリームで、wasm のマジックナンバーは展開して初めて現れます。ネットワークタブからそのまま保存したビルド成果物は、.wasm という名前の gzip であることがよくあります。 |
| ELF 実行ファイル | どちらも仮想または実在のマシン向けのコンパイル出力で、emscripten や wasi-sdk のビルドディレクトリには両方が入っています。.o ファイルは ELF で、wasm なのは最終的なモジュールだけです。オフセット 0 の 7F 45 4C 46 と 00 61 73 6D が、曖昧さなく両者を分けます。 |
| asm.js | 祖先にあたるもので、古いバンドルの中に今も見つかります。asm.js は JavaScript であり、'use asm' ディレクティブの付いたプレーンテキストのファイルが .js として配られます。これを WebAssembly.instantiate に渡せばマジックナンバーで失敗します。そもそもバイナリモジュールがどこにも入っていないからです。 |
よくある壊れ方
- 途中切れ。セクションのサイズは中身より先に宣言されるので、途中までのダウンロードでも、あるセクションが残りより多くのバイトを主張するところまでは正しくパースできます。デコーダは 'unexpected end of section or function' と報告します。このカタログのどの形式よりも明快な切り詰めのメッセージです。
- gzip 圧縮のまま配信・保存された。Content-Encoding が gzip の .wasm をそのままディスクに書くとファイルは 1F 8B で始まり、WebAssembly.instantiate はマジックナンバーで拒否します。バイト自体は正しく、間違っているのは層だけです。
- セクション本体に紛れ込んだ 1 バイト。長さフィールドさえ無傷ならセクションの走査は正しい境界に着地し続けるので、モジュールはたいてい読み込まれ、実行時にトラップしたり挙動がおかしくなったりします。ヘッダで失敗するより、ずっと診断しにくい形です。
- テキストモード転送、あるいはエディタが末尾に改行を足した。末尾に 1 バイト増えるだけで、セクションの走査がファイル末尾の 1 バイト手前で終わるモジュールができあがり、厳格なデコーダは受け入れずに余分なバイトがあると報告します。
この形式の現在地
WebAssembly は、既存のネイティブコードをブラウザへ持ち込むための手段です。動画や画像のコーデック、PDF や CAD のエンジン、SQLite、Python や Ruby のランタイム、エミュレータ、ブラウザ内アーカイブツールの裏にある圧縮ライブラリ。どれも .wasm モジュールとして配られます。JavaScript の代替ではありませんし、UI に使うものでもありません(自前の DOM アクセスを持ちません)。ただし、利用者のマシン上で走らせる必要がある計算量の多い処理には既定の答えであり、アップロード不要でブラウザだけで完結するツールが成立するのもこれのおかげです。ブラウザの外でも、可搬なサンドボックスとして第二の人生を得ました。WASI のランタイム、エッジコンピューティングの基盤、プラグインの仕組みが同じバイナリ形式を使っているので、ウェブサイト向けにビルドしたモジュールがサーバー上でそのまま動くこともよくあります。
WebAssembly ファイルをこのサイトで開く
どれもブラウザ内で動きます。ファイルが端末から出ることはありません。
関連する形式
WebAssembly と混同しやすい形式です。先頭バイトを見比べてください。
| 形式 | 先頭バイト | 拡張子 |
|---|---|---|
| ELF 実行ファイル | 7F 45 4C 46 | .elf |
| Java class ファイル | CA FE BA BE | .class |