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
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 al azar transmitido 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 de eso, reasigna b
al número 42
y, luego, desactiva 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. (Tu número aleatorio será diferente, por supuesto).
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. Consta de la máquina virtual (VM) de Zend, que está compuesta por el compilador 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 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 el bajo nivel de Wasm, es natural probar el mismo enfoque allí: volver a compilar el código principal de la VM con su analizador, compatibilidad de biblioteca, recolección de elementos no utilizados y optimizador para Wasm, e implementar un backend JIT o AOT para Wasm si es necesario. Esto ha sido posible desde que se creó el MVP de Wasm y, en muchos casos, funciona bien en la práctica. 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 Compila experiencias de WordPress integradas en el navegador con WordPress Playground y WebAssembly.
Sin embargo, PHP Wasm se ejecuta en el navegador en el contexto del lenguaje de 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 antiguo enfoque que permite que los módulos de Wasm compilen su propia recolección de elementos no utilizados sobre la memoria lineal de Wasm es que no hay interacción entre el recolector de elementos no utilizados de Wasm y el recolector de elementos no utilizados integrado del lenguaje compilado a Wasm, que tiende a causar problemas como fugas de memoria e intentos de recolección ineficientes. Permitir que los módulos de Wasm reutilicen la GC integrada existente evita estos problemas.
Cómo migrar 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 y, 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 elementos no utilizados, pero aun así agrupan malloc/free
para administrar la memoria. La razón por la que Java es más pequeño en este caso es porque no necesita empaquetar 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 que se realice cualquier trabajo importante en la optimización del tamaño.
Cómo ver un lenguaje de programación portabilizado por WasmGC en acción
Wasm de Kotlin
Uno de los primeros lenguajes de programación que se portaron 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"
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 si se combina con Compose multiplataforma, que permite a los desarrolladores compilar sobre la IU que quizás ya hayan creado para sus apps de Kotlin para Android. 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 leer sobre el estado actual del trabajo en la documentación de Flutter. La siguiente demostración es la vista previa de Flutter WasmGC.
Más información sobre WasmGC
Esta entrada de blog apenas es superficial y ofrece principalmente una descripción general de alto nivel de WasmGC. Para obtener más información sobre la función, consulta estos vínculos:
- Una nueva forma de incorporar eficazmente lenguajes de programación recolectados en WebAssembly
- Descripción general de WasmGC
- MVP de WasmGC
- WasmGC posterior al MVP
Agradecimientos
Este artículo fue revisado por Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler y Rachel Andrew.