Hay dos tipos de lenguajes de programación: los de recolección de elementos no utilizados y los que requieren la administración manual de la memoria. Algunos ejemplos de las primeras son Kotlin, PHP o Java. Algunos ejemplos de estas últimas 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 elementos no utilizados como función estándar. En esta entrada de blog, nos enfocamos en los lenguajes de programación que se usan para recolectar elementos no utilizados y cómo pueden compilarse en WebAssembly (Wasm). Pero ¿qué es la recolección de elementos no utilizados (a menudo conocida como GC) en primer lugar?
Navegadores compatibles
Recolección de elementos no utilizados
En términos simplificados, la idea de recolección de elementos no utilizados es el intento de reclamar memoria asignada por el programa, pero que ya no se hace referencia a ella. Esa memoria se llama basura. Existen muchas estrategias para implementar la recolección de elementos no utilizados. 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 no haya más referencias a un objeto, se puede marcar como que ya no se usa y, por lo tanto, estar listo para la recolección de elementos no utilizados. El recolector de elementos no utilizados 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. Considera el siguiente programa de PHP.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
El programa asigna un número al azar 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 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 referencia del recolector de elementos no utilizados en funcionamiento.
<?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 mostrarán 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 según 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 elementos no utilizados, como la detección de ciclos, pero para este artículo, tener un nivel básico de comprensión del recuento de referencias es suficiente.
Los lenguajes de programación se implementan en otros lenguajes de programación
Puede parecer un origen, 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 elementos no utilizados de PHP se encuentra principalmente en el archivo zend_gc.c
. La mayoría de los desarrolladores instalan PHP a través del administrador de paquetes de su sistema operativo. Sin embargo, los desarrolladores también pueden compilar PHP desde el código fuente. Por ejemplo, en un entorno de Linux, con los pasos ./buildconf && ./configure && make
, se compilaría PHP para el entorno de ejecución de Linux. Pero esto también significa que el entorno de ejecución de PHP puede compilarse para otros entornos de ejecución, como Wasm.
Métodos tradicionales de portabilidad de idiomas al tiempo de ejecución de Wasm
Independientemente de la plataforma en la que se ejecute PHP, las secuencias de comandos de PHP se compilan en el mismo código de bytes y se ejecutan en Zend Engine. Zend Engine es un compilador y entorno de ejecución para el lenguaje de programación 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 una 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 porque, a menudo, la parte principal de la base de código solo 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 de la VM principal 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 impulsa 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 elementos no utilizados. Esto significa que los desarrolladores que usan, por ejemplo, PHP compilado en Wasm, terminan enviando una implementación de recolector de elementos no usados (PHP) al navegador que ya tiene un recolector, lo que es un desperdicio tan desperdiciado como parece. Aquí es donde WasmGC entra en juego.
El otro problema del antiguo enfoque de permitir 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 y los intentos de recolección ineficientes. Si permites que los módulos de Wasm reutilicen el GC integrado existente, se evitan 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 del MVP de Wasm solo es capaz de manejar números, es decir, números enteros y flotantes, en la memoria lineal. 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 struct y array, lo que significa compatibilidad con 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 desoptimizaciones, que tienen los lenguajes dinámicos como JavaScript. Por lo tanto, esta propuesta agrega a WebAssembly una compatibilidad eficiente para lenguajes administrados de alto nivel, a través de tipos de montón de struct y array que permiten que los compiladores de lenguajes que apuntan a Wasm se integren con un recolector de elementos no utilizados en la VM host. En términos simplificados, esto significa que, con WasmGC, la portabilidad de un lenguaje de programación a Wasm significa que el recolector de elementos no utilizados del lenguaje de programación ya no necesita ser parte del puerto, sino que se puede usar el recolector de elementos no utilizados existente.
Para comprobar el impacto real de esta mejora, el equipo de Wasm de Chrome compiló versiones de la comparativa de Fannkuch (que asigna estructuras de datos mientras funciona) de C, Rust y Java. Los objetos binarios de C y Rust pueden ser de entre 6.1 K y 9.6 K según los diferentes indicadores del compilador, mientras que la versión de Java es mucho más pequeña (solo es de 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 compatible con WasmGC en acción
Wasm de Kotlin
Uno de los primeros lenguajes de programación que se transfirió 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 básicamente consiste en las APIs de JavaScript OM 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. Echa un vistazo a esta exploración anticipada 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 en 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 de herramientas para la entrega de 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 después del MVP
Agradecimientos
Imagen hero de Gary Chan en Unsplash. Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler y Rachel Andrew revisaron este artículo.