WebAssembly GCと参照型
線形メモリのコピー地獄から抜け出す仕組みがわかる。WasmGCが導入する構造体・配列型と参照型の原理、Kotlin/Dartなど言語コンパイラへの影響を解説します。
- 1.WasmGC(GC提案)はstruct/array型と参照型(ref/refnull)を追加し、GC管理オブジェクトをエンジンのヒープに直接確保できるようにする拡張。
- 2.型は名前的部分型付け(nominal subtyping)で階層化され、キャストはdown-cast命令で検証し、失敗時はトラップまたはnull分岐で安全に処理される。
- 3.Kotlin/Dartはこれまで線形メモリへ独自GCを実装していたが、WasmGCによりホストGCへ委譲でき、バイナリサイズと相互運用性が改善する。
線形メモリだけでは足りなかった理由
Wasmの実行モデルは、値を線形メモリ上のバイト列として表現し、オフセットと長さを数値でやり取りする設計でした。C/C++やRustのように、コンパイラが自前でメモリ管理を担う言語には向いています。しかしKotlin・Dart・Javaのように言語ランタイム自体がGCを前提にする場合は事情が変わります。オブジェクトを線形メモリ上の自作ヒープに配置し、マーク&スイープやコピーGCまで自前でWasmにコンパイルして持ち込む必要があり、ランタイムが数百KB〜MB単位に膨らみ、ブラウザ本体のGCと二重にヒープを抱える無駄も生じていました。
WasmGC提案は、エンジンのネイティブGCヒープにWasmから直接オブジェクトを確保できるようにすることでこれを解決します。構造体・配列・参照という新しい型カテゴリを追加し、生成・参照・回収をすべてホスト(ブラウザやランタイム)のGCに委ねます。
WasmGCは新しい言語ではなく、コア仕様への型拡張です。既存のi32等の数値型・線形メモリはそのまま残り、GC管理オブジェクトは線形メモリの外側にある別領域(GCヒープ)に確保されます。両者は共存し、必要に応じて参照を線形メモリへ橋渡しします。
構造体型と配列型:複合値の表現
WasmGCが追加する複合型は大きく2種類です。structは固定個数のフィールドを持つレコード型で、各フィールドは可変(mutable)か不変かを個別に指定できます。arrayは単一要素型の可変長配列で、長さは生成時に決まりますが要素は可変にできます。どちらも参照型としてのみ扱われ、値そのものをスタックに積むのではなく、ヒープ上のインスタンスへの参照が流通します。
;; 概念的な型定義(WAT記法)
(type $point (struct
(field $x (mut f64))
(field $y (mut f64))))
(type $point_array (array (mut (ref $point))))
生成命令はstruct.new(全フィールドを引数で初期化)やstruct.new_default(デフォルト値で初期化)、配列側はarray.new・array.new_default・array.new_fixedがあり、読み書きはstruct.get/struct.set、array.get/array.setで行います。フィールド名や型の整合性は検証時に静的にチェックされますが、配列の添字が範囲内かどうかは配列長が実行時に決まるため静的には保証できず、array.get/array.setは毎回境界チェックを行い、範囲外ならトラップします。実行時コストが伴わないのは型チェックの方だけで、境界チェックと後述のdown-castは実行時コストを伴います。
参照型:nullableと非nullable、そして名前的部分型
参照型は(ref $t)(非null、必ず$t型のインスタンスを指す)と(ref null $t)(null許容)の2系統に分かれます。C系言語の「ぬるぽ」を防ぐため、関数シグネチャやフィールド型でどちらを要求するかを明示でき、非null参照であれば検証時にnullチェックが不要になります。
型階層は**名前的部分型付け(nominal subtyping)**で定義されます。構造的な形が一致していても、明示的にsub $parentと宣言しない限り部分型関係は成立しません。
(type $animal (struct (field $name (ref extern))))
(type $dog (sub $animal (struct
(field $name (ref extern))
(field $breed (ref extern)))))
$dog型の参照は(ref $animal)が要求される場所へそのまま渡せます(アップキャストは無検証)。逆に$animal型の値を$dogとして使うにはdown-castが必要で、ref.cast(失敗時はトラップ)やref.test(真偽値を返す)、あるいは分岐と組み合わせたbr_on_cast系命令で明示的に検証します。この設計は、JSエンジンの型観測のような実行時プロファイリングとは対照的です。JSエンジンは形状(shape)を実行しながら学習しますが、WasmGCは型階層をモジュール読み込み時に静的に確定し、キャストの成否だけを実行時に判定します。
アップキャストは型情報の破棄に過ぎず実質無コストですが、down-cast(ref.castなど)は実行時にランタイム型情報(RTTI)相当の照合が走ります。頻出パスでdown-castを繰り返す設計は、線形メモリ版で境界を頻繁にまたぐのと同様、性能上の落とし穴になります。
GCヒープと線形メモリの共存
WasmGCのオブジェクトはWasmの線形メモリの中には置かれません。エンジンが管理する不透明なGCヒープに確保され、Wasmコード側からはポインタ演算で直接アドレスを触れない、参照(handle)としてのみ扱えます。これは安全性のための意図的な制約で、GCがオブジェクトを移動(コピーGCやコンパクション)してもWasm側のコードは参照を経由するため壊れません。
externrefは既存のホスト参照(JSオブジェクトなど)を不透明に保持する型で、WasmGCのstruct/arrayとは別の仕組みです。線形メモリを使う既存モジュール(C/C++/Rustのコンパイル成果物)と、struct/arrayを使う新しいモジュールは、同一ランタイム上に共存できます。画像デコードのようなバイト列処理は線形メモリ側に、アプリケーションのオブジェクトグラフはGCヒープ側に置く、という使い分けが可能です。ブラウザのGC内部動作で説明される世代別GCやインクリメンタルマーキングはWasmGCオブジェクトにもそのまま適用され、JSオブジェクトと同じ回収器・同じヒープを共有するため、JS↔Wasm間で参照を渡す際のコピーも不要になります。
| 方式 | 確保場所 | 回収方法 | JSとの受け渡し |
|---|---|---|---|
| 線形メモリ+自前GC | ArrayBuffer内の自作ヒープ | 言語ランタイムが自前実装 | オフセット+長さをコピー |
| WasmGC(struct/array) | エンジンのGCヒープ | ホストのGCがそのまま回収 | 参照をそのまま共有(コピー不要) |
言語コンパイラへの影響:KotlinとDartの事例
KotlinやDartは元々、Wasm向けバックエンドで独自GCを線形メモリ上に実装していました。オブジェクトのアロケータ、マーキング、コンパクションのすべてを言語ランタイム側でWasmにコンパイルして持ち込む必要があり、ランタイムサイズの肥大化と、ブラウザのGCとは別系統のヒープを持つことによるJS相互運用の複雑さ(値を渡すたびにシリアライズ/デシリアライズが必要)という2つの代償を伴いました。
WasmGCへ移行すると、コンパイラは自前のオブジェクト表現をstruct/array型宣言へ落とし込み、確保・回収をホストGCへ委譲できます。Kotlin/WasmやDartのWasmGCバックエンドはこの方式でランタイムのバイナリサイズを大幅に削減し、JSオブジェクトとの参照共有によって境界コストも下げています。ただし代償もあり、名前的部分型付けという設計は、構造的部分型やダックタイピングを持つ言語にとっては、型階層をコンパイル時にsub関係へ丁寧にマッピングし直す作業を要求します。既存の線形メモリベースのFFI(C言語ライブラリ呼び出しなど)とWasmGCの参照を橋渡しする場合も、明示的な変換コードが必要です。
WasmGC採用の恩恵はバイナリサイズ削減とJS相互運用の高速化に集中します。一方、down-castの実行時コストや、既存の線形メモリ前提のツールチェイン(デバッガ、プロファイラ、FFIブリッジ)との統合し直しは、移行コストとして残ります。数値計算中心でオブジェクトグラフを持たないC/C++/Rustのワークロードには、WasmGCの恩恵はほぼありません。
まとめ
WasmGCは、Wasmの数値型・線形メモリという既存モデルに、struct/array型と参照型という新しいカテゴリを追加する拡張です。型階層は名前的部分型付けで静的に確定し、実行時検証が必要なのはdown-castと配列の境界チェックに限られます。オブジェクトは線形メモリの外側にあるホストのGCヒープに確保されるため、ブラウザのGCがそのまま回収を担い、JSオブジェクトとの参照共有もコピーなしで行えます。KotlinやDartのような、GC前提のランタイムを持つ言語ほど恩恵が大きく、自前GCの実装・維持というコストを手放せます。線形メモリとGCヒープは排他ではなく共存する仕組みであり、Wasmの実行モデルを理解した上でこの拡張を捉えると、「Wasmはシステム言語専用」という理解が更新されます。
Web/フロントエンド Article
WebAssembly GCと参照型を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
WebAssembly
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
型は名前的部分型付け(nominal subtyping)で階層化され、キャストはdown-cast命令で検証し、失敗時はトラップまたはnull分岐で安全に処理される。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「WebAssembly / Wasm」に近いか確認する。
- 強みである「WasmGC(GC提案)はstruct/array型と参照型(ref/refnull)を追加し、GC管理オブジェクトをエンジンのヒープに直接確保できるようにする拡張。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。