现在,Chrome 会默认启用 WebAssembly 垃圾回收 (WasmGC)

编程语言分为两种类型:垃圾回收编程语言和需要手动内存管理的编程语言。前一种语言的示例包括 Kotlin、PHP 或 Java。后者的示例包括 C、C++ 或 Rust。一般来说,较高级别的编程语言更有可能将垃圾回收作为标准功能。本博文将重点介绍此类垃圾回收编程语言,以及如何将其编译为 WebAssembly (Wasm)。但是,要从什么开始进行垃圾回收(通常称为 GC)呢?

浏览器支持

  • Chrome:119。 <ph type="x-smartling-placeholder">
  • Edge:119。 <ph type="x-smartling-placeholder">
  • Firefox:120。 <ph type="x-smartling-placeholder">
  • Safari:不支持。 <ph type="x-smartling-placeholder">

垃圾回收

简单地说,垃圾回收的理念就是尝试回收由程序分配但不再引用的内存。此类内存称为垃圾。实现垃圾回收的策略有很多。其中一种是“引用计数”,其目标是计算对内存中对象的引用数量。当某个对象没有更多引用时,您可以将其标记为不再使用,从而准备好进行垃圾回收。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 代表一种新的架构。如果虚拟机具有特定于架构的代码,例如即时 (JIT) 或预先 (AOT) 编译,那么开发者还可以为新架构实现 JIT/AOT 后端。这种方法非常有用,因为通常可以针对每个新架构重新编译代码库的主要部分。

鉴于 Wasm 的低层级,我们自然而然可以尝试使用相同的方法:使用其解析器、库支持、垃圾回收和优化器将主虚拟机代码重新编译为 Wasm,并根据需要为 Wasm 实现 JIT 或 AOT 后端。自从 Wasm MVP 发布以来,这已经成为可能,并且在许多情况下都运行良好。事实上,编译到 Wasm 的 PHPWordPress 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 对象都有固定的类型和结构,这使得虚拟机可以轻松生成高效的代码来访问其字段,而不会面临 JavaScript 等动态语言进行去优化的风险。此方案通过结构体和数组堆类型为 WebAssembly 添加了对高级托管语言的高效支持,使针对 Wasm 的语言编译器能够与主机虚拟机中的垃圾回收器集成。简单来说,这意味着使用 WasmGC 将编程语言移植到 Wasm 意味着该编程语言的垃圾回收器不再需要作为端口的一部分,而是可以使用现有的垃圾回收器。

为了验证这项改进的实际影响,Chrome 的 Wasm 团队编译了 CRustJavaFannkuch 基准测试版本(该基准测试会分配数据结构)。C 和 Rust 二进制文件可能介于 6.1 K9.6 K 之间,具体取决于各种编译器标志,而 Java 版本则小得多,只有 2.3 K!C 和 Rust 不包含垃圾回收器,但它们仍会捆绑 malloc/free 来管理内存,而 Java 之所以小,是因为它完全不需要捆绑任何内存管理代码。这只是一个具体示例,但它表明,WasmGC 二进制文件有可能非常小巧,而这甚至早于对大小进行优化的任何重要工作。

了解使用 WasmGC 移植的编程语言的实际应用

Kotlin Wasm

得益于 WasmGC,最早移植到 Wasm 的编程语言之一就是 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 Multiplatform 结合使用,这变得更有意义,让开发者能够基于他们可能已为其 Android Kotlin 应用创建的界面进行构建。欢迎使用 Kotlin/Wasm 图像查看器演示抢先了解相关功能,并探索其源代码(同样由 Kotlin 团队提供)。

Dart 和 Flutter

Google 的 Dart 和 Flutter 团队也在准备对 WasmGC 的支持。Dart-to-Wasm 编译工作即将完成,该团队正在努力提供工具支持,以便提供编译为 WebAssembly 的 Flutter Web 应用。您可以参阅 Flutter 文档,了解相关工作的当前状态。以下演示是 Flutter WasmGC 预览版

详细了解 WasmGC

这篇博文几乎只是触及了皮毛,大多提供了 WasmGC 的简要概览。如需详细了解该功能,请访问以下链接:

致谢

Gary ChanUnsplash 网站上提供的主打图片。本文由 Matthias LiedtkeAdam KleinJoshua BellAlon ZakaiJakob KummerowClemens BackesEmanuel ZieglerRachel Andrew 审核