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: lenguajes de programación con recolección de basura y lenguajes de programación que requieren administración manual de la memoria. Entre los ejemplos de los primeros, se incluyen Kotlin, PHP o Java. Algunos ejemplos de los últimos son C, C++ o Rust. Como regla general, los lenguajes de programación de nivel superior tienen más probabilidades de tener la recolección de basura como una función estándar. En esta entrada de blog, se enfoca 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 elementos no utilizados (a menudo denominada GC)?

Navegadores compatibles

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: No se admite.

Recolección de elementos no utilizados

En términos simplificados, la idea de la recolección de elementos no utilizados es el intento de 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 elementos no usados. Uno de ellos es el recuento de referencias, cuyo objetivo es contar la cantidad de referencias a objetos en la memoria. Cuando no hay más 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 usar la función xdebug_debug_zval() de la extensión Xdebug te permite ver debajo de la superficie. Considera el siguiente programa PHP.

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

El programa asigna un número aleatorio convertido a una cadena a una variable nueva 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');
?>

El ejemplo anterior mostrará los siguientes registros, en los que verás 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 detectar ciclos, pero para este artículo, basta con tener un nivel básico de comprensión del recuento de referencias.

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

Puede parecer el principio, pero los lenguajes de programación se implementan en otros lenguajes de programación. Por ejemplo, el entorno 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. Pero esto también significa que el entorno de ejecución de PHP se puede compilar para otros entornos de ejecución, como Wasm.

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

Independientemente de la plataforma en la que se ejecuta PHP, las secuencias de comandos de PHP se compilan en el mismo código de bytes y las ejecuta el motor Zend. Zend Engine es un compilador y un entorno de ejecución para el lenguaje de secuencias de comandos PHP. Consiste en la máquina virtual (VM) de Zend, que se compone del compilador 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 arquitectura nueva. Si la VM tiene código específico de la arquitectura, como la compilación just-in-time (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 nivel que es Wasm, es natural probar el mismo enfoque allí: volver a compilar el código principal de la VM con su analizador, compatibilidad con bibliotecas, recolección de basura y optimizador a Wasm, y, si es necesario, implementar un backend 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 potencia 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 como 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 a Wasm, terminan enviando una implementación del recolector de elementos no usados del lenguaje portabilizado (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 propio recolector de basura de Wasm y el recolector de basura integrado en el lenguaje compilado en Wasm, lo que suele causar problemas, como fugas de memoria y intentos de recolección ineficientes. Permitir que los módulos de Wasm reutilicen la GC integrada existente evita estos problemas.

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

WasmGC es una propuesta del grupo comunitario de WebAssembly. La implementación actual de MVP de Wasm solo puede procesar números, es decir, números enteros y de punto flotante, en memoria lineal. Además, con la propuesta de tipos de referencia que se envía, Wasm también puede conservar referencias externas. WasmGC ahora agrega tipos de montón de array y struct, lo que significa que admite la asignación de memoria no lineal. Cada objeto 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 deoptimizaciones que tienen los lenguajes dinámicos como JavaScript. Por lo tanto, esta propuesta agrega compatibilidad eficiente con lenguajes administrados de alto nivel a WebAssembly, a través de tipos de montón de array y struct que permiten que los compiladores de lenguajes orientados a Wasm se integren con un recolector de basura en la VM host. En términos simplificados, esto significa que, con WasmGC, portar un lenguaje de programación a Wasm significa que el recolector de basura del lenguaje de programación ya no necesita ser parte del puerto, 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) desde C, Rust y Java. Los objetos binarios de C y Rust pueden variar entre 6.1 K y 9.6 K, según las diversas 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 aún agrupan malloc/free para administrar la memoria. La razón por la que Java es más pequeño aquí es porque no necesita agrupar ningún código de administración de memoria. Este es solo un ejemplo específico, pero muestra que los objetos binarios de WasmGC tienen el potencial de ser muy pequeños, incluso antes de realizar cualquier trabajo significativo para optimizar el tamaño.

Cómo ver un lenguaje de programación portabilizado por WasmGC en acción

Kotlin Wasm

Uno de los primeros lenguajes de programación que se portaron a Wasm gracias a WasmGC es Kotlin en forma de Kotlin/Wasm. La demo, con código fuente cortesía del equipo de Kotlin, se muestra en la siguiente lista.

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"

Es posible que te preguntes cuál es el punto, ya que el código de Kotlin anterior consiste básicamente en las APIs de OM de JavaScript convertidas a Kotlin. Comienza a tener más sentido en combinación con Compose multiplataforma, que permite a los desarrolladores aprovechar la IU que ya podrían haber creado para sus apps para Android de Kotlin. Consulta una exploración temprana de esto con la demostración del visualizador de imágenes de Kotlin/Wasm y explora su código fuente, también cortesía del equipo de Kotlin.

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 obtener información sobre el estado actual del trabajo en la documentación de Flutter. La siguiente demostración es la versión preliminar de WasmGC de Flutter.

Más información sobre WasmGC

Esta entrada de blog apenas ha arañado la superficie y, en su mayoría, 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

Este artículo fue revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler y Rachel Andrew.