プログラミング言語には、ガベージ コレクションを使用するプログラミング言語と、手動のメモリ管理が必要なプログラミング言語の 2 種類があります。前者の例としては、Kotlin、PHP、Java などがあります。後者の例としては、C、C++、Rust などがあります。一般に、高水準プログラミング言語には、ガベージ コレクションが標準機能として含まれています。このブログ投稿では、このようなガベージ コレクションを行うプログラミング言語と、それらを WebAssembly(Wasm)にコンパイルする方法について説明します。ガベージ コレクション(GC)とは、そもそも何でしょうか。
対応ブラウザ
ガベージ コレクション
簡単に説明すると、ガベージ コレクションとは、プログラムによって割り当てられたが、参照されなくなったメモリの再利用を試みることです。このようなメモリはガベージと呼ばれます。ガベージ コレクションを実装する方法は数多くあります。そのうちの 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 がビルドされます。ただし、これは、PHP ランタイムを他のランタイム(Wasm など)用にコンパイルできることも意味します。
言語を 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 以降可能であり、多くの場合、実際にうまく機能します。実際、WordPress Playground は Wasm にコンパイルされた PHP で動作しています。このプロジェクトについて詳しくは、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 モジュールが既存の組み込み GC を再利用できるようにすると、これらの問題を回避できます。
WasmGC を使用したプログラミング言語の新しいランタイムへの移植
WasmGC は、WebAssembly コミュニティ グループの提案です。現在の Wasm MVP 実装では、リニアメモリ内の数値(整数と浮動小数点数)のみを処理できます。参照型の提案がリリースされると、Wasm は外部参照も保持できるようになります。WasmGC に構造体と配列のヒープ型が追加され、非線形メモリ割り当てがサポートされるようになりました。各 WasmGC オブジェクトには固定の型と構造があるため、VM は JavaScript などの動的言語で発生するデオプティマイゼーションのリスクなしで、フィールドにアクセスする効率的なコードを簡単に生成できます。この提案により、構造体と配列のヒープ型を介して、高レベルのマネージド言語を WebAssembly に効率的にサポートします。これにより、Wasm をターゲットとする言語コンパイラをホスト VM のガベージ コレクタと統合できます。簡単に説明すると、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 がここで小さい理由は、メモリ管理コードをバンドルする必要がまったくないためです。これは 1 つの具体的な例にすぎませんが、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 コードは基本的に、JavaScript OM API を Kotlin に変換したもので構成されているため、何の意味があるのかと疑問に思うかもしれません。Compose マルチプラットフォームと組み合わせると、デベロッパーは Android Kotlin アプリ用にすでに作成した UI を基にビルドできるため、さらに便利になります。Kotlin チームが提供した Kotlin/Wasm 画像ビューアのデモで、この機能の初期段階をご確認ください。また、ソースコードもご覧ください。
Dart と Flutter
Google の Dart チームと Flutter チームも、WasmGC のサポートを準備しています。Dart から Wasm へのコンパイルはほぼ完了しており、チームは WebAssembly にコンパイルされた Flutter ウェブ アプリケーションを配信するためのツールのサポートに取り組んでいます。作業の現在の状況については、Flutter のドキュメントをご覧ください。次のデモは Flutter WasmGC プレビューです。
WasmGC の詳細
このブログ投稿では、WasmGC の概要を説明しました。この機能について詳しくは、以下のリンクをご覧ください。
謝辞
この記事は、Matthias Liedtke、Adam Klein、Joshua Bell、Alon Zakai、Jakob Kummerow、Clemens Backes、Emanuel Ziegler、Rachel Andrew によるレビューを経て公開されました。