Chrome 現已預設啟用 WebAssembly 垃圾收集 (WasmGC)

程式設計語言分為兩種類型:垃圾收集程式設計語言,以及需要手動管理記憶體的程式設計語言。舉例來說,Kotlin、PHP 或 Java 都是這類語言。後者包括 C、C++ 或 Rust。一般來說,高階程式設計語言較有可能將垃圾收集功能設為標準功能。這篇網誌文章著重於這類垃圾收集程式設計語言,以及如何將其編譯為 WebAssembly (Wasm)。但首先,什麼是垃圾收集 (通常稱為 GC)?

Browser Support

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: 18.2.

垃圾收集

簡單來說,垃圾收集的概念是嘗試回收程式分配但不再參照的記憶體。這類記憶體稱為垃圾。實作垃圾收集的策略有很多種,其中一種是參照計數,目的是計算記憶體中物件的參照數量。當物件沒有其他參照時,可以標示為不再使用,因此可供垃圾收集。PHP 的垃圾收集器使用參照計數,而使用 Xdebug 擴充功能的 xdebug_debug_zval() 函式,可讓您查看其內部運作方式。請參考下列 PHP 程式。

<?php
  $a= (string) rand();
  $c = $b = $a;
  $b = 42;
  unset($c);
  $a = null;
?>

程式會將隨機數字 (轉換為字串) 指派給名為 a 的新變數。接著建立兩個新變數 bc,並指派 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 虛擬機器 (VM),該機器由 Zend 編譯器和 Zend 執行器組成。以 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 是 Google 的開放原始碼 JavaScript 引擎,會根據 ECMA-262 規定實作 ECMAScript。而且 V8 已經有垃圾收集器。也就是說,如果開發人員使用編譯為 Wasm 的 PHP,最終會將移植語言 (PHP) 的垃圾收集器實作項目,傳送至已有垃圾收集器的瀏覽器,這聽起來很浪費資源。這時 WasmGC 就能派上用場。

舊方法是讓 Wasm 模組在 Wasm 的線性記憶體上建構自己的 GC,但這樣會產生另一個問題:Wasm 自己的垃圾收集器與編譯為 Wasm 的語言所建構的垃圾收集器之間沒有互動,這往往會導致記憶體洩漏和收集嘗試效率不彰等問題。讓 Wasm 模組重複使用現有的內建 GC,即可避免這些問題。

使用 WasmGC 將程式設計語言移植到新的執行階段

WasmGC 是 WebAssembly 社群群組提案。目前的 Wasm MVP 實作只能處理線性記憶體中的數字 (即整數和浮點數),而隨著參照型別提案的發布,Wasm 還可以保留外部參照。WasmGC 現在會新增結構體和陣列堆積類型,也就是支援非線性記憶體配置。每個 WasmGC 物件都有固定的型別和結構,因此 VM 能夠輕鬆產生有效率的程式碼來存取欄位,不會像 JavaScript 等動態語言一樣有最佳化風險。因此,這項提案透過結構體和陣列堆積類型,為 WebAssembly 增添高階管理語言的有效支援,讓以 Wasm 為目標的語言編譯器與主機 VM 中的垃圾收集器整合。簡單來說,這表示使用 WasmGC 將程式設計語言移植到 Wasm 時,程式設計語言的垃圾收集器不再需要成為移植作業的一部分,而是可以使用現有的垃圾收集器。

為驗證這項改善措施在現實世界中的影響,Chrome 的 Wasm 團隊已編譯 Fannkuch 基準 (會在運作時分配資料結構) 的 CRustJava 版本。C 和 Rust 二進位檔的大小可能介於 6.1 K9.6 K 之間 (視各種編譯器標記而定),而 Java 版本的大小則小得多,只有 2.3 K!C 和 Rust 不含垃圾收集器,但仍會綁定 malloc/free 來管理記憶體,而 Java 較小是因為完全不需要綁定任何記憶體管理程式碼。這只是一個具體範例,但已足以說明 WasmGC 二進位檔的潛在大小非常小,而且這還是未針對大小進行任何重大最佳化作業的情況。

查看 WasmGC 移植的程式設計語言實際運作情形

Kotlin Wasm

WasmGC 支援的第一批程式設計語言之一,就是以 Kotlin/Wasm 形式移植到 Wasm 的 Kotlin。以下列出 demo,並附上 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 Multiplatform 使用時,這項功能會更有意義,因為開發人員可以運用已為 Android Kotlin 應用程式建立的 UI。如要搶先瞭解這項技術,請參閱 Kotlin/Wasm 圖片檢視器,同樣由 Kotlin 團隊提供。

Kotlin/Wasm 圖片檢視器示範。

Dart 和 Flutter

Google 的 Dart 和 Flutter 團隊也正在準備支援 WasmGC。Dart 到 Wasm 的編譯工作即將完成,團隊目前正努力提供工具支援,以便交付編譯為 WebAssembly 的 Flutter 網頁應用程式。如要瞭解目前的工作狀態,請參閱 Flutter 說明文件。以下是 Flutter WasmGC 預先發布版的試用版。

進一步瞭解 WasmGC

這篇網誌文章僅觸及 WasmGC 的表面,主要提供概要總覽。如要進一步瞭解這項功能,請參閱下列連結:

特別銘謝

本文由 Matthias LiedtkeAdam KleinJoshua BellAlon ZakaiJakob KummerowClemens BackesEmanuel ZieglerRachel Andrew 審查。