La recolección de elementos no utilizados de WebAssembly (WasmGC) ahora está habilitada de forma predeterminada en Chrome

Existen dos tipos de lenguajes de programación: los que tienen recolección de basura y los que requieren administración manual de la memoria. Entre muchos otros, Kotlin, PHP o Java son ejemplos del primer tipo. Algunos ejemplos de este último son C, C++ o Rust. Como regla general, es más probable que los lenguajes de programación de nivel superior tengan la recolección de basura como una función estándar. En esta entrada de blog, nos enfocaremos en esos lenguajes de programación con recolección de elementos no utilizados y cómo se pueden compilar en WebAssembly (Wasm). Pero, ¿qué es la recolección de basura (a menudo denominada GC)?

Browser Support

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

Recolección de elementos no utilizados

En términos sencillos, la idea de la recolección de elementos no utilizados es intentar recuperar la memoria que asignó el programa, pero a la que ya no se hace referencia. Esa memoria se denomina basura. Existen muchas estrategias para implementar la recolección de basura. Una de ellas es el recuento de referencias, en el que el objetivo es contar la cantidad de referencias a objetos en la memoria. Cuando ya no hay referencias a un objeto, se puede marcar como que ya no se usa y, por lo tanto, está listo para la recolección de elementos no utilizados. El recolector de basura de PHP usa el recuento de referencias, y el uso de la función xdebug_debug_zval() de la extensión Xdebug te permite echar un vistazo a su funcionamiento interno. Considera el siguiente programa en PHP.

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

El programa asigna un número aleatorio convertido en una cadena a una nueva variable llamada a. Luego, crea dos variables nuevas, b y c, y les asigna el valor de a. Después, reasigna b al número 42 y, luego, anula c. Por último, establece el valor de a en null. Si anotas cada paso del programa con xdebug_debug_zval(), puedes ver el contador de referencias del recolector de basura en acción.

<?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');
?>

En el ejemplo anterior, se generarán los siguientes registros, en los que se muestra cómo disminuye la cantidad de referencias al valor de la variable a después de cada paso, lo que tiene sentido dada la secuencia de código. (Por supuesto, tu número aleatorio será diferente).

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

Existen otros desafíos con la recolección de basura, como la detección de ciclos, pero, para este artículo, es suficiente con tener un nivel básico de comprensión del conteo de referencias.

Los lenguajes de programación se implementan en otros lenguajes de programación

Puede parecer una paradoja, pero los lenguajes de programación se implementan en otros lenguajes de programación. Por ejemplo, el tiempo de ejecución de PHP se implementa principalmente en C. Puedes consultar el código fuente de PHP en GitHub. El código de recolección de basura de PHP se encuentra principalmente en el archivo zend_gc.c. La mayoría de los desarrolladores instalarán PHP a través del administrador de paquetes de su sistema operativo. Sin embargo, los desarrolladores también pueden compilar PHP a partir del código fuente. Por ejemplo, en un entorno de Linux, los pasos ./buildconf && ./configure && make compilarían PHP para el entorno de ejecución de Linux. Sin embargo, esto también significa que el entorno de ejecución de PHP se puede compilar para otros entornos de ejecución, como, lo adivinaste, Wasm.

Métodos tradicionales para portar lenguajes al entorno de ejecución de Wasm

Independientemente de la plataforma en la que se ejecute PHP, los secuencias de comandos de PHP se compilan en el mismo código de bytes y se ejecutan con el Zend Engine. Zend Engine es un compilador y un entorno de ejecución para el lenguaje de secuencias de comandos PHP. Consta de la máquina virtual (VM) de Zend, que se compone del compilador de Zend y el ejecutor de Zend. Los lenguajes como PHP, que se implementan en otros lenguajes de alto nivel como C, suelen tener optimizaciones que se orientan a arquitecturas específicas, como Intel o ARM, y requieren un backend diferente para cada arquitectura. En este contexto, Wasm representa una nueva arquitectura. Si la VM tiene código específico de la arquitectura, como la compilación justo a tiempo (JIT) o anticipada (AOT), el desarrollador también implementa un backend para JIT/AOT para la nueva arquitectura. Este enfoque tiene mucho sentido, ya que, a menudo, la parte principal de la base de código se puede volver a compilar para cada arquitectura nueva.

Dado lo bajo que es el nivel de Wasm, es natural probar el mismo enfoque allí: recompilar el código principal de la VM con su analizador, compatibilidad con bibliotecas, recolección de basura y optimizador en Wasm, y, si es necesario, implementar un backend de JIT o AOT para Wasm. Esto es posible desde el MVP de Wasm y funciona bien en la práctica en muchos casos. De hecho, PHP compilado en Wasm es lo que impulsa WordPress Playground. Obtén más información sobre el proyecto en el artículo Crea experiencias de WordPress en el navegador con WordPress Playground y WebAssembly.

Sin embargo, PHP Wasm se ejecuta en el navegador en el contexto del lenguaje host JavaScript. En Chrome, JavaScript y Wasm se ejecutan en V8, el motor de JavaScript de código abierto de Google que implementa ECMAScript según se especifica en ECMA-262. Además, V8 ya tiene un recolector de basura. Esto significa que los desarrolladores que usan, por ejemplo, PHP compilado en Wasm, terminan enviando una implementación de recolector de elementos no usados del lenguaje portado (PHP) al navegador que ya tiene un recolector de elementos no usados, lo que es tan ineficiente como suena. Aquí es donde entra en juego WasmGC.

El otro problema del enfoque anterior de permitir que los módulos de Wasm compilen su propio GC sobre la memoria lineal de Wasm es que no hay interacción entre el recolector de basura propio de Wasm y el recolector de basura compilado sobre el lenguaje compilado en Wasm, lo que suele causar problemas como pérdidas de memoria y intentos de recolección ineficientes. Permitir que los módulos de Wasm reutilicen el recolector de basura integrado existente evita estos problemas.

Cómo portar lenguajes de programación a nuevos tiempos de ejecución con WasmGC

WasmGC es una propuesta del WebAssembly Community Group. La implementación actual del MVP de Wasm solo puede procesar números, es decir, números enteros y de punto flotante, en la memoria lineal. Con la propuesta de tipos de referencia que se lanzó, Wasm también puede conservar referencias externas. WasmGC ahora agrega tipos de montón de struct y array, lo que significa que admite la asignación de memoria no lineal. Cada objeto de WasmGC tiene un tipo y una estructura fijos, lo que facilita que las VMs generen código eficiente para acceder a sus campos sin el riesgo de desoptimización que tienen los lenguajes dinámicos, como JavaScript. De esta manera, la propuesta agrega compatibilidad eficiente para lenguajes administrados de alto nivel a WebAssembly, a través de tipos de montón de struct y array que permiten que los compiladores de lenguajes que se orientan a Wasm se integren con un recolector de basura en la VM host. En términos sencillos, esto significa que, con WasmGC, portar un lenguaje de programación a Wasm implica que el recolector de basura del lenguaje de programación ya no necesita formar parte del port, sino que se puede usar el recolector de basura existente.

Para verificar el impacto real de esta mejora, el equipo de Wasm de Chrome compiló versiones de la comparativa de Fannkuch (que asigna estructuras de datos a medida que funciona) de C, Rust y Java. Los archivos binarios de C y Rust pueden tener un tamaño de entre 6.1 K y 9.6 K según las distintas marcas del compilador, mientras que la versión de Java es mucho más pequeña, con solo 2.3 K. C y Rust no incluyen un recolector de basura, pero sí incluyen malloc/free para administrar la memoria. El motivo por el que Java es más pequeño aquí es porque no necesita incluir ningún código de administración de memoria. Este es solo un ejemplo específico, pero muestra que los archivos binarios de WasmGC tienen el potencial de ser muy pequeños, y esto es incluso antes de cualquier trabajo significativo en la optimización del tamaño.

Ver un lenguaje de programación portado a WasmGC en acción

Kotlin/Wasm

Uno de los primeros lenguajes de programación que se portó a Wasm gracias a WasmGC es Kotlin en forma de Kotlin/Wasm. En la siguiente lista, se muestra la demostración, con el código fuente cortesía del equipo de 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"

Ahora te preguntarás cuál es el punto, ya que el código de Kotlin anterior básicamente consiste en las APIs del OM de JavaScript convertidas a Kotlin. Comienza a tener más sentido en combinación con Compose Multiplatform, que permite a los desarrolladores crear sobre la IU que ya crearon para sus apps de Kotlin para Android. Puedes ver una exploración inicial de esto con el visor de imágenes de Kotlin/Wasm, también cortesía del equipo de Kotlin.

Es una demostración del visor de imágenes de Kotlin/Wasm.

Dart y Flutter

Los equipos de Dart y Flutter de Google también están preparando la compatibilidad con WasmGC. El trabajo de compilación de Dart a Wasm está casi completo, y el equipo está trabajando en la compatibilidad con herramientas para entregar aplicaciones web de Flutter compiladas en WebAssembly. Puedes leer sobre el estado actual del trabajo en la documentación de Flutter. La siguiente demostración es la versión preliminar de Flutter WasmGC.

Más información sobre WasmGC

En esta entrada de blog, apenas se abordó el tema y se proporcionó una descripción general de alto nivel de WasmGC. Para obtener más información sobre la función, consulta estos vínculos:

Agradecimientos

Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler y Rachel Andrew revisaron este artículo.