プログラミング言語には、ガベージ コレクション プログラミング言語と手動でのメモリ管理が必要なプログラミング言語の 2 種類があります。前者の例として、Kotlin、PHP、Java などが数多くあります。後者の例は、C、C++、Rust です。原則として、上位プログラミング言語では、ガベージ コレクションが標準機能である可能性が高くなります。このブログ投稿では、そのようなガベージ コレクションの対象プログラミング言語と、それらを WebAssembly(Wasm)にコンパイルする方法に焦点を当てます。そもそも、ガベージ コレクション(GC とも呼ばれる)とは何でしょうか。
対応ブラウザ
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
- <ph type="x-smartling-placeholder">
ガベージ コレクション
簡単に言うと、ガベージ コレクションとは、プログラムによって割り当てられたメモリが参照されなくなったメモリを再利用することです。このようなメモリは「ガベージ」と呼ばれます。ガベージ コレクションの実装にはさまざまな戦略があります。そのうちの 1 つは参照カウントで、その目的はメモリ内のオブジェクトへの参照回数をカウントすることです。オブジェクトへの参照がなくなったときは、使用しなくなったものとしてマークすることで、ガベージ コレクションを行う準備が整います。PHP のガベージ コレクタは参照カウントを使用しており、Xdebug 拡張機能の xdebug_debug_zval()
関数を使用すると、内部をのぞくことができます。次の PHP プログラムについて考えてみましょう。
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
プログラムは、文字列にキャストされた乱数を a
という新しい変数に代入します。次に、2 つの新しい変数 b
と c
を作成し、a
の値を代入します。その後、b
を数値 42
に再割り当てし、c
の設定を解除します。最後に、a
の値を null
に設定します。プログラムの各ステップに xdebug_debug_zval()
アノテーションを付けると、ガベージ コレクタの参照カウンタが機能していることがわかります。
<?php
$a= (string) rand();
$c = $b = $a;
xdebug_debug_zval('a');
$b = 42;
xdebug_debug_zval('a');
unset($c);
xdebug_debug_zval('a');
$a = null;
xdebug_debug_zval('a');
?>
上記の例では、次のログが出力されます。各ステップの後に、変数 a
の値の参照数が減少することがわかります。これは、コード シーケンスを考えると合理的です。(乱数は異なります)。
a:
(refcount=3, is_ref=0)string '419796578' (length=9)
a:
(refcount=2, is_ref=0)string '419796578' (length=9)
a:
(refcount=1, is_ref=0)string '419796578' (length=9)
a:
(refcount=0, is_ref=0)null
ガベージ コレクションには、サイクルの検出など、他にも課題がありますが、この記事では、参照カウントの基本的なレベルで理解するだけで十分です。
プログラミング言語は他のプログラミング言語で実装される
最初から作られたと感じるかもしれませんが、プログラミング言語は他のプログラミング言語で実装されています。たとえば、PHP ランタイムは主に C で実装されています。GitHub にある PHP ソースコードを確認できます。PHP のガベージ コレクション コードは、主に zend_gc.c
ファイル内にあります。ほとんどのデベロッパーは、オペレーティング システムのパッケージ管理システムから PHP をインストールします。ただし、デベロッパーは ソースコードから PHP をビルドすることもできます。たとえば、Linux 環境では、ステップ ./buildconf && ./configure && make
で Linux ランタイム用の PHP をビルドします。しかしこれは、お気付きかもしれませんが Wasm のように、他のランタイム用に PHP ランタイムをコンパイルできることも意味します。
Wasm ランタイムに言語を移植する従来の方法
PHP スクリプトは、PHP が実行されているプラットフォームから独立して同じバイトコードにコンパイルされ、Zend Engine によって実行されます。Zend Engine は、PHP スクリプト言語のコンパイラおよびランタイム環境です。Zend コンパイラと Zend エグゼキュータで構成される Zend 仮想マシン(VM)で構成されています。C のような他の高水準言語で実装される PHP のような言語は、一般的に Intel や ARM などの特定のアーキテクチャをターゲットとする最適化を行うため、アーキテクチャごとに異なるバックエンドが必要になります。この文脈において、Wasm は新しいアーキテクチャを表しています。VM にジャストインタイム(JIT)コンパイルや事前(AOT)コンパイルなど、アーキテクチャ固有のコードがある場合、デベロッパーは新しいアーキテクチャ用に JIT/AOT のバックエンドも実装します。このアプローチはかなり理にかなっています。多くの場合、コードベースの主要部分は新しいアーキテクチャごとに再コンパイルするだけで済みます。
Wasm が低レベルであることを考慮すると、そこで同じアプローチを試すのは自然なことです。つまり、メインの VM コードをそのパーサー、ライブラリ サポート、ガベージ コレクション、オプティマイザーで Wasm に再コンパイルし、必要に応じて Wasm の JIT または AOT バックエンドを実装します。これは Wasm MVP の時点から可能で、多くのケースでうまく機能しています。実際、Wasm にコンパイルした PHP が WordPress Playground の原動力となっています。プロジェクトについて詳しくは、WordPress Playground と WebAssembly を使用してブラウザ内 WordPress エクスペリエンスを構築するの記事をご覧ください。
ただし、PHP Wasm はホスト言語の JavaScript のコンテキストでブラウザで実行されます。Chrome では、JavaScript と Wasm は V8 で実行されます。V8 は、ECMA-262 で指定されている ECMAScript を実装する Google のオープンソースの JavaScript エンジンです。また、V8 にはすでにガベージ コレクタがあります。たとえば、Wasm にコンパイルされた PHP を利用するデベロッパーは、ガベージ コレクタがすでに存在するブラウザに、移植言語(PHP)のガベージ コレクタ実装を送信することになりますが、これは言うまでもなく無駄です。ここで WasmGC の出番となります。
Wasm モジュールに Wasm の線形メモリ上に独自の GC をビルドさせる従来のアプローチのもう 1 つの問題は、Wasm 独自のガベージ コレクタと Wasm にコンパイルされた Wasm 言語に組み込まれたトップ ガベージ コレクタとの間に相互作用がなくなるため、メモリリークや非効率的な収集の試みなどの問題が発生しがちです。Wasm モジュールで既存の組み込み GC を再利用できるようにすることで、こうした問題を回避できます。
WasmGC を使用してプログラミング言語を新しいランタイムに移植する
WasmGC は WebAssembly Community Group の提案です。現在の Wasm MVP 実装では、リニアメモリ内の数値(整数と浮動小数点数)しか処理できませんが、参照型の提案が出荷されるため、Wasm はさらに外部参照を保持できます。WasmGC に構造体と配列のヒープタイプが追加され、非線形のメモリ割り当てがサポートされるようになりました。各 WasmGC オブジェクトは型と構造が固定されているため、VM は JavaScript のような動的言語のような最適化解除のリスクなしに、フィールドにアクセスするための効率的なコードを簡単に生成できます。この提案により、Wasm をターゲットにする言語コンパイラがホスト VM のガベージ コレクタと統合できるようにする構造体と配列のヒープタイプを介して、WebAssembly に高レベルのマネージド言語の効率的なサポートが追加されます。簡単に言うと、これは WasmGC でプログラミング言語を Wasm に移植すると、プログラミング言語のガベージ コレクタをポートの一部にする必要がなくなり、代わりに既存のガベージ コレクタを使用できることを意味します。
この改善の実際の影響を検証するために、Chrome の Wasm チームは、C、Rust、Java の Fannkuch ベンチマーク(動作時にデータ構造を割り当てる)のバージョンをコンパイルしました。C バイナリと Rust バイナリは、さまざまなコンパイラ フラグに応じて 6.1 K から 9.6 K のいずれかになりますが、Java のバージョンは 2.3 K とはるかに小さくなります。C と Rust にはガベージ コレクタは含まれていませんが、メモリ管理のために malloc/free
をバンドルしています。Java が小さいのは、メモリ管理コードをまったくバンドルする必要がないためです。これはほんの一例にすぎませんが、WasmGC バイナリは非常に小さくなる可能性があることを示しています。これは、サイズの最適化に多大な労力を費やす前でもあります。
WasmGC に移植されたプログラミング言語の実際の動作
Kotlin Wasm
WasmGC のおかげで Wasm に移植された最初のプログラミング言語の 1 つは、Kotlin/Wasm 形式の Kotlin です。Kotlin チーム提供のソースコードを使用したデモを次のリストに示します。
import kotlinx.browser.document
import kotlinx.dom.appendText
import org.w3c.dom.HTMLDivElement
fun main() {
(document.getElementById("warning") as HTMLDivElement).style.display = "none"
document.body?.appendText("Hello, ${greet()}!")
}
fun greet() = "world"
上記の Kotlin コードは基本的に Kotlin に変換された JavaScript OM API で構成されているため、何が重要なのか疑問に思うかもしれません。Compose マルチプラットフォームと組み合わせることで、より理にかなっています。これにより、デベロッパーは Android Kotlin アプリ用にすでに作成した UI を基にして開発できるようになります。Kotlin/Wasm 画像ビューアのデモでこれについて早期に確認し、同様に Kotlin チームの厚意によりそのソースコードをご確認ください。
Dart と Flutter
Google の Dart チームと Flutter チームも、WasmGC のサポート準備を進めています。Dart-to-Wasm のコンパイル作業はほぼ完了しており、チームは WebAssembly にコンパイルした Flutter ウェブ アプリケーションを配布するためのツールのサポートに取り組んでいます。処理の現在の状態については、Flutter のドキュメントをご覧ください。次のデモは、Flutter WasmGC プレビューです。
WasmGC の詳細
このブログ投稿は、そのほんの一部をかじった程度で、主に WasmGC の概要をまとめたものです。この機能について詳しくは、以下のリンクをご覧ください。
謝辞
ヒーロー画像作成: Gary Chan、Unsplashこの記事は、Matthias Liedtke、Adam Klein、Joshua Bell、Alon Zakai、Jakob Kummerow、Clemens Backes、Emanuel Ziegler、Rachel Andrew がレビューしました。