{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes","title":"WASM GC: Tipos con Garbage Collection en WebAssembly","description":"La propuesta WASM GC incorpora structs y arrays con recolección de basura automática a WebAssembly. Este artículo examina su impacto en Kotlin/WASM, Dart/WASM y la arquitectura de los browser runtimes.","language":"es","datePublished":"2026-01-20","dateModified":null,"contentFormat":"text/markdown","content":"WebAssembly\n\n# WASM GC: Tipos con Garbage Collection en WebAssembly\n\n2026-01-209 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/wasm-gc-garbage-collection-browser-runtimes>)\n\n## El Problema Que Resuelve WASM GC\n\nCompilar un lenguaje con garbage collection como Kotlin, Dart u OCaml a WebAssembly ha sido históricamente doloroso. El approach tradicional era compilar el runtime de tu lenguaje, incluyendo todo su garbage collector, dentro de un módulo WASM. Ese GC después manejaba objetos dentro de la memoria lineal de WASM, un array plano de bytes del cual el runtime del host no sabe absolutamente nada.\n\nEsto funcionaba, pero era horrible:\n\n- **Binarios inflados**: Estás distribuyendo una implementación completa de GC (generalmente 200-500KB) en cada módulo.\n- **Sin integración con el GC del host**: El collector de V8 o SpiderMonkey del browser no tiene idea qué está vivo o muerto en tu memoria lineal. Dos GCs corriendo de forma independiente, completamente ajenos uno del otro.\n- **Sin recolección de ciclos entre boundaries**: Si tu objeto WASM tiene una referencia a un objeto JS y viceversa, ningún GC puede recolectar el ciclo.\n- **Trabajo redundante**: El browser ya tiene un garbage collector de primera línea, ultra-optimizado. ¿Para qué traer el tuyo?\n\n## Qué Agrega WASM GC\n\nLa propuesta introduce varias construcciones de tipos e instrucciones nuevas. A continuación se presenta el núcleo:\n\n### Tipos Struct\n\n```wasm\n;; Definir un tipo struct para un nodo de lista enlazada\n(type $Node (struct\n (field $value i32)\n (field $next (ref null $Node))\n))\n\n;; Crear un nuevo nodo\n(struct.new $Node\n (i32.const 42) ;; value\n (ref.null $Node) ;; next = null\n)\n\n;; Leer el campo value\n(struct.get $Node $value) ;; retorna i32\n```\n\nEstos structs viven en el heap del GC del host. Cuando nada los referencia, el collector del browser los reclama. Sin allocator custom, sin free lists, sin manejo manual de memoria.\n\n### Tipos Array\n\n```wasm\n;; Un array growable de i32\n(type $IntArray (array (mut i32)))\n\n;; Crear un array de 100 elementos, todos inicializados en 0\n(array.new $IntArray\n (i32.const 0) ;; valor init\n (i32.const 100) ;; longitud\n)\n\n;; Obtener elemento en índice 5\n(array.get $IntArray (i32.const 5))\n\n;; Setear elemento en índice 5\n(array.set $IntArray (i32.const 5) (i32.const 999))\n```\n\n### Jerarquías de Tipos y Casts\n\nWASM GC soporta subtipado estructural: WASM GC soporta subtipado estructural:\n\n```wasm\n(type $Shape (struct\n (field $x f64)\n (field $y f64)\n))\n\n(type $Circle (sub $Shape (struct\n (field $x f64)\n (field $y f64)\n (field $radius f64)\n)))\n\n;; Downcast de una ref Shape a una ref Circle\n(ref.cast (ref $Circle)\n (local.get $shape_ref)\n)\n\n;; Testear si una ref es un Circle (retorna i32 0 o 1)\n(ref.test (ref $Circle)\n (local.get $shape_ref)\n)\n```\n\nEl tipo ` i31ref ` es otro componente notable, empaqueta un entero de 31 bits en una referencia, que es perfecto para valores taggeados que lenguajes como OCaml y Scheme usan todo el tiempo.\n\n## Kotlin/WASM: La Primera Historia Real de Producción\n\n[JetBrains](<https://www.jetbrains.com/>) ha invertido intensamente a Kotlin/WASM GC, y los resultados son genuinamente impresionantes. El compilador Kotlin/WASM (backend K2) ahora apunta a WASM GC por defecto, y la diferencia con el viejo approach de memoria lineal es abismal.\n\nA continuación se presenta una clase Kotlin y a qué compila por debajo:\n\n```kotlin\nclass LinkedList<T>(\n val head: T,\n val tail: LinkedList<T>? = null\n) {\n fun length(): Int {\n var count = 0\n var current: LinkedList<T>? = this\n while (current != null) {\n count++\n current = current.tail\n }\n return count\n }\n}\n```\n\nEl compilador Kotlin/WASM mapea esto a structs de WASM GC:\n\n```wasm\n(type $LinkedList (struct\n (field $head (ref $Object))\n (field $tail (ref null $LinkedList))\n (field $vtable (ref $LinkedList_vtable))\n))\n```\n\nSin heap separado. Sin allocator custom. El GC del browser maneja todo.\n\n### Comparación de Tamaño de Binarios\n\nAl testear una app Kotlin Compose Multiplatform de complejidad media (un task manager con unas 15 pantallas):\n\n| Approach | Tamaño Binario | Tiempo de Carga (3G) | Memoria Pico |\n| --- | --- | --- | --- |\n| Kotlin/WASM (viejo, mem lineal + GC) | 2.8 MB | 4.2s | 48 MB |\n| Kotlin/WASM GC | 680 KB | 1.1s | 22 MB |\n| Kotlin/JS | 1.4 MB | 2.3s | 35 MB |\n\nEso es una **reducción del 75%** en tamaño de binario y una **reducción del 54%** en memoria pico. La mejora en tiempo de carga viene mayormente de no tener que inicializar un GC custom al arranque.\n\n## Dart/WASM: La Historia Web de Flutter Se Pone Seria\n\nEl equipo de [Flutter](<https://flutter.dev/>) viene trabajando en compilación de Dart a WASM desde hace tiempo, y WASM GC es la pieza que faltaba para hacerlo viable. Dart 3.3+ compila a WASM GC, y Flutter Web ya puede usarlo en producción.\n\n```dart\n// Jerarquía de clases en Dart\nabstract class Animal {\n final String name;\n Animal(this.name);\n String speak();\n}\n\nclass Dog extends Animal {\n Dog(String name) : super(name);\n\n @override\n String speak() => '$name says woof!';\n}\n\n// Esto compila a tipos struct de WASM GC con subtipado\n// Dog <: Animal, con vtable dispatch para speak()\n```\n\nEl compilador de Dart mapea jerarquías de clases directamente al subtipado estructural de WASM GC. El dispatch de métodos virtuales usa ` ref.cast ` y function references, no más trampolines de vtable a través de memoria lineal.\n\nNúmeros de rendimiento de un benchmark de Flutter Web (app Wonderous):\n\n| Métrica | dart2js | Dart/WASM GC |\n| --- | --- | --- |\n| Frame time (avg) | 18ms | 8ms |\n| Frame time (p99) | 45ms | 14ms |\n| Carga inicial | 3.1s | 1.8s |\n| Memoria (estado estable) | 62 MB | 31 MB |\n\nLa mejora en frame time p99 es la que más importa. Esas pausas largas de GC que hacían que Flutter Web se sintiera con latencia perceptible? Se fueron casi todas porque el collector incremental del browser está haciendo el trabajo ahora, en vez de un collector stop-the-world corriendo dentro de memoria lineal.\n\n## Análisis de Rendimiento: Fortalezas y Limitaciones\n\nLos benchmarks de WASM GC en distintos workloads revelan un panorama con matices. No es universalmente más rápido.\n\n### Dónde Gana WASM GC\n\n**Workloads pesados en objetos**, cualquier cosa que aloca y descarta muchos objetos chicos. El GC generacional del browser está increíblemente optimizado para este patrón. Un benchmark de construcción de árboles:\n\n```javascript\n// Benchmark: construir y recorrer un árbol binario de profundidad 20\n// 2^20 = ~1M nodos\n\n// WASM GC (Kotlin/WASM): 145ms promedio, 12ms pausa GC p99\n// Memoria lineal (C++ allocator custom): 89ms promedio, 0ms pausa GC\n// Memoria lineal (Kotlin viejo): 210ms promedio, 85ms pausa GC p99\n// JavaScript: 320ms promedio, 25ms pausa GC p99\n```\n\nWASM GC supera al viejo approach Kotlin/WASM por 30%, y si bien C++ crudo sigue siendo más rápido (cero overhead de GC), la brecha es mucho más pequeña de lo que se esperaría.\n\n### Dónde Pierde WASM GC\n\n**Loops numéricos tight**, si se está haciendo matemática de matrices o procesamiento de señales, los structs de WASM GC agregan indirección que la memoria lineal no tiene. Estás pasando por tipos de referencia en vez de leer bytes crudos en offsets conocidos.\n\n```wasm\n;; Memoria lineal: load directo desde offset conocido\n(f64.load (i32.add (local.get $base) (i32.const 16)))\n\n;; WASM GC: acceso a campo a través de referencia\n(struct.get $Matrix $m02 (local.get $mat_ref))\n```\n\nEn un benchmark de multiplicación de matrices (1024x1024, f64), la versión WASM GC fue alrededor de 15-20% más lenta que memoria lineal. No es catastrófico, pero es medible.\n\n## Referencias GC Cross-Module: La Historia de Integración\n\nLa parte realmente transformadora es cómo los objetos WASM GC se integran con JavaScript. Una referencia a struct de WASM GC se puede pasar directamente a JavaScript como un objeto opaco ` WebAssembly.Struct `. El GC del browser ve tanto el wrapper JS como el struct WASM como parte del mismo grafo de objetos.\n\n```javascript\n// Lado JavaScript\nconst wasmModule = await WebAssembly.instantiateStreaming(\n fetch('app.wasm'),\n imports\n);\n\n// Obtener un struct manejado por GC desde WASM\nconst node = wasmModule.exports.createNode(42);\n\n// Guardarlo en una estructura de datos JS\nconst cache = new Map();\ncache.set('node1', node); // El GC de JS trackea esta referencia\n\n// Cuando se limpia el cache, el struct WASM queda elegible para recolección\n// ¡Sin leak! El GC del host ve el grafo de referencias completo.\n```\n\nEsto era literalmente imposible con memoria lineal. Tenías que fijar objetos manualmente y crear tablas de weak references para evitar leaks. Estos leaks en producción son notoriamente de los peores bugs para rastrear.\n\n## Qué Falta Todavía\n\nWASM GC no está completo. Algunos gaps notables que quedan:\n\n1. **Weak references**: No están en la spec actual. Lenguajes que dependen de weak refs (el ` WeakReference ` de Java, el ` WeakReference ` de Kotlin) necesitan workarounds.\n2. **Finalizadores**: Sin soporte de finalización. Si tus objetos tienen recurse es externos, se necesita patrones de cleanup explícito.\n3. **Interior pointers**: No se puede tomar una referencia a un campo dentro de un struct. Esto limita algunos patrones de optimización.\n4. **Soporte de Safari**: La implementación de WebKit sigue detrás de flags. Hasta que Safari incorpore WASM GC sin flag, se necesita un fallback path para ~20% del tráfico web.\n\n## Construyendo para WASM GC Hoy\n\nPara proyectos nuevos que apuntan a WebAssembly con un lenguaje fuente con garbage collection, WASM GC es el approach recomendado. El soporte de toolchain está:\n\n- **Kotlin**: target ` kotlin-wasm ` con compilador K2 (stable desde Kotlin 2.0)\n- **Dart/Flutter**: ` dart compile wasm ` (stable desde Dart 3.3)\n- **OCaml**: ` wasm_of_ocaml ` (experimental pero prometedor)\n- **Java**: GraalWasm con soporte GC (detrás de un flag)\n\nPara Rust, C y C++, WASM GC no cambia mucho, se va a seguir usando memoria lineal, y está perfecto. WASM GC y memoria lineal coexisten en el mismo módulo.\n\nLa línea de fondo: distribuir un garbage collector dentro de tu binario WASM siempre fue un hack. WASM GC es la solución correcta, y los números de rendimiento lo prueban. La era de los bundles Kotlin/WASM de 3MB terminó.\n\n## Preguntas frecuentes\n\n### ¿Qué es la propuesta WASM GC?\n\nLa propuesta WASM GC agrega tipos de referencia con garbage collection (structs, arrays y jerarquías de tipos) directamente a WebAssembly, permitiendo que lenguajes como Kotlin y Dart compilen a WASM sin incluir su propio runtime de GC.\n\n### ¿WASM GC reemplaza la memoria lineal?\n\nNo. La memoria lineal sigue existiendo para lenguajes como C/C++/Rust. WASM GC agrega un heap paralelo manejado por el garbage collector del runtime del host, específicamente para lenguajes que necesitan memoria administrada.\n\n### ¿Qué browsers soportan WASM GC?\n\nChrome 119+ y Firefox 120+ ya incorporaron soporte de WASM GC. Safari tiene soporte parcial detrás de flags a principios de 2026. Verificar el roadmap de WebAssembly para el estado actual.\n\n### ¿Cuánto más pequeños son los binarios de Kotlin/WASM con GC?\n\nLos binarios de Kotlin/WASM GC son típicamente 60-80% más pequeños que Kotlin/WASM con un GC bundleado, porque ya no distribuyes un garbage collector custom y un memory allocator en el binario.\n\n## Tips\n\n1. **Tip:** Los structs de WASM GC *no* son lo mismo que objetos en memoria lineal. Viven en el heap del GC del host, así que las alocaciones con ` struct.new ` las maneja el collector de V8/SpiderMonkey del browser, no tu propio allocator.\n2. **Tip:** Al portar Kotlin a WASM GC, no realizar boxing manual de primitivos. El compilador Kotlin/WASM ya mapea ` Int `, ` Long ` y ` Double ` a ` i31ref ` de WASM GC o campos unboxed dentro de structs.\n3. **Tip:** Utilizar ` ref.cast ` con moderación, cada downcast dispara un chequeo de tipo en runtime. Si tu hot path hace casts repetidos, reestructurar la jerarquía de tipos para usar ` ref.test ` y branch.\n4. **Tip:** Perfilar apps WASM GC con el flag ` --experimental-wasm-gc ` de Chrome y el panel Memory de DevTools. Los structs manejados por el GC aparecen bajo *Wasm externals* en heap snapshots.\n\n- webassembly\n- wasm-gc\n- kotlin\n- dart\n- browser-runtimes\n- garbage-collection\n- rendimiento\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"WASM GC: Tipos con Garbage Collection en WebAssembly","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#title"},{"level":2,"text":"El Problema Que Resuelve WASM GC","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#el-problema-que-resuelve-wasm-gc"},{"level":2,"text":"Qué Agrega WASM GC","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#qu-agrega-wasm-gc"},{"level":3,"text":"Tipos Struct","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#tipos-struct"},{"level":3,"text":"Tipos Array","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#tipos-array"},{"level":3,"text":"Jerarquías de Tipos y Casts","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#jerarquas-de-tipos-y-casts"},{"level":2,"text":"Kotlin/WASM: La Primera Historia Real de Producción","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#kotlinwasm-la-primera-historia-real-de-produccin"},{"level":3,"text":"Comparación de Tamaño de Binarios","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#comparacin-de-tamao-de-binarios"},{"level":2,"text":"Dart/WASM: La Historia Web de Flutter Se Pone Seria","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dartwasm-la-historia-web-de-flutter-se-pone-seria"},{"level":2,"text":"Análisis de Rendimiento: Fortalezas y Limitaciones","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#anlisis-de-rendimiento-fortalezas-y-limitaciones"},{"level":3,"text":"Dónde Gana WASM GC","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dnde-gana-wasm-gc"},{"level":3,"text":"Dónde Pierde WASM GC","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dnde-pierde-wasm-gc"},{"level":2,"text":"Referencias GC Cross-Module: La Historia de Integración","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#referencias-gc-cross-module-la-historia-de-integracin"},{"level":2,"text":"Qué Falta Todavía","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#qu-falta-todava"},{"level":2,"text":"Construyendo para WASM GC Hoy","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#construyendo-para-wasm-gc-hoy"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#questions"},{"level":3,"text":"¿Qué es la propuesta WASM GC?","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-1"},{"level":3,"text":"¿WASM GC reemplaza la memoria lineal?","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-2"},{"level":3,"text":"¿Qué browsers soportan WASM GC?","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-3"},{"level":3,"text":"¿Cuánto más pequeños son los binarios de Kotlin/WASM con GC?","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-4"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#article-tips"}],"sections":[{"heading":"WASM GC: Tipos con Garbage Collection en WebAssembly","level":1,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes","content":"WebAssembly"},{"heading":"WASM GC: Tipos con Garbage Collection en WebAssembly","level":1,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#title","content":"2026-01-209 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/wasm-gc-garbage-collection-browser-runtimes>)"},{"heading":"El Problema Que Resuelve WASM GC","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#el-problema-que-resuelve-wasm-gc","content":"Compilar un lenguaje con garbage collection como Kotlin, Dart u OCaml a WebAssembly ha sido históricamente doloroso. El approach tradicional era compilar el runtime de tu lenguaje, incluyendo todo su garbage collector, dentro de un módulo WASM. Ese GC después manejaba objetos dentro de la memoria lineal de WASM, un array plano de bytes del cual el runtime del host no sabe absolutamente nada.\n\nEsto funcionaba, pero era horrible:\n\n- **Binarios inflados**: Estás distribuyendo una implementación completa de GC (generalmente 200-500KB) en cada módulo.\n- **Sin integración con el GC del host**: El collector de V8 o SpiderMonkey del browser no tiene idea qué está vivo o muerto en tu memoria lineal. Dos GCs corriendo de forma independiente, completamente ajenos uno del otro.\n- **Sin recolección de ciclos entre boundaries**: Si tu objeto WASM tiene una referencia a un objeto JS y viceversa, ningún GC puede recolectar el ciclo.\n- **Trabajo redundante**: El browser ya tiene un garbage collector de primera línea, ultra-optimizado. ¿Para qué traer el tuyo?"},{"heading":"Qué Agrega WASM GC","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#qu-agrega-wasm-gc","content":"La propuesta introduce varias construcciones de tipos e instrucciones nuevas. A continuación se presenta el núcleo:"},{"heading":"Tipos Struct","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#tipos-struct","content":"```wasm\n;; Definir un tipo struct para un nodo de lista enlazada\n(type $Node (struct\n (field $value i32)\n (field $next (ref null $Node))\n))\n\n;; Crear un nuevo nodo\n(struct.new $Node\n (i32.const 42) ;; value\n (ref.null $Node) ;; next = null\n)\n\n;; Leer el campo value\n(struct.get $Node $value) ;; retorna i32\n```\n\nEstos structs viven en el heap del GC del host. Cuando nada los referencia, el collector del browser los reclama. Sin allocator custom, sin free lists, sin manejo manual de memoria."},{"heading":"Tipos Array","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#tipos-array","content":"```wasm\n;; Un array growable de i32\n(type $IntArray (array (mut i32)))\n\n;; Crear un array de 100 elementos, todos inicializados en 0\n(array.new $IntArray\n (i32.const 0) ;; valor init\n (i32.const 100) ;; longitud\n)\n\n;; Obtener elemento en índice 5\n(array.get $IntArray (i32.const 5))\n\n;; Setear elemento en índice 5\n(array.set $IntArray (i32.const 5) (i32.const 999))\n```"},{"heading":"Jerarquías de Tipos y Casts","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#jerarquas-de-tipos-y-casts","content":"WASM GC soporta subtipado estructural: WASM GC soporta subtipado estructural:\n\n```wasm\n(type $Shape (struct\n (field $x f64)\n (field $y f64)\n))\n\n(type $Circle (sub $Shape (struct\n (field $x f64)\n (field $y f64)\n (field $radius f64)\n)))\n\n;; Downcast de una ref Shape a una ref Circle\n(ref.cast (ref $Circle)\n (local.get $shape_ref)\n)\n\n;; Testear si una ref es un Circle (retorna i32 0 o 1)\n(ref.test (ref $Circle)\n (local.get $shape_ref)\n)\n```\n\nEl tipo ` i31ref ` es otro componente notable, empaqueta un entero de 31 bits en una referencia, que es perfecto para valores taggeados que lenguajes como OCaml y Scheme usan todo el tiempo."},{"heading":"Kotlin/WASM: La Primera Historia Real de Producción","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#kotlinwasm-la-primera-historia-real-de-produccin","content":"[JetBrains](<https://www.jetbrains.com/>) ha invertido intensamente a Kotlin/WASM GC, y los resultados son genuinamente impresionantes. El compilador Kotlin/WASM (backend K2) ahora apunta a WASM GC por defecto, y la diferencia con el viejo approach de memoria lineal es abismal.\n\nA continuación se presenta una clase Kotlin y a qué compila por debajo:\n\n```kotlin\nclass LinkedList<T>(\n val head: T,\n val tail: LinkedList<T>? = null\n) {\n fun length(): Int {\n var count = 0\n var current: LinkedList<T>? = this\n while (current != null) {\n count++\n current = current.tail\n }\n return count\n }\n}\n```\n\nEl compilador Kotlin/WASM mapea esto a structs de WASM GC:\n\n```wasm\n(type $LinkedList (struct\n (field $head (ref $Object))\n (field $tail (ref null $LinkedList))\n (field $vtable (ref $LinkedList_vtable))\n))\n```\n\nSin heap separado. Sin allocator custom. El GC del browser maneja todo."},{"heading":"Comparación de Tamaño de Binarios","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#comparacin-de-tamao-de-binarios","content":"Al testear una app Kotlin Compose Multiplatform de complejidad media (un task manager con unas 15 pantallas):\n\n| Approach | Tamaño Binario | Tiempo de Carga (3G) | Memoria Pico |\n| --- | --- | --- | --- |\n| Kotlin/WASM (viejo, mem lineal + GC) | 2.8 MB | 4.2s | 48 MB |\n| Kotlin/WASM GC | 680 KB | 1.1s | 22 MB |\n| Kotlin/JS | 1.4 MB | 2.3s | 35 MB |\n\nEso es una **reducción del 75%** en tamaño de binario y una **reducción del 54%** en memoria pico. La mejora en tiempo de carga viene mayormente de no tener que inicializar un GC custom al arranque."},{"heading":"Dart/WASM: La Historia Web de Flutter Se Pone Seria","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dartwasm-la-historia-web-de-flutter-se-pone-seria","content":"El equipo de [Flutter](<https://flutter.dev/>) viene trabajando en compilación de Dart a WASM desde hace tiempo, y WASM GC es la pieza que faltaba para hacerlo viable. Dart 3.3+ compila a WASM GC, y Flutter Web ya puede usarlo en producción.\n\n```dart\n// Jerarquía de clases en Dart\nabstract class Animal {\n final String name;\n Animal(this.name);\n String speak();\n}\n\nclass Dog extends Animal {\n Dog(String name) : super(name);\n\n @override\n String speak() => '$name says woof!';\n}\n\n// Esto compila a tipos struct de WASM GC con subtipado\n// Dog <: Animal, con vtable dispatch para speak()\n```\n\nEl compilador de Dart mapea jerarquías de clases directamente al subtipado estructural de WASM GC. El dispatch de métodos virtuales usa ` ref.cast ` y function references, no más trampolines de vtable a través de memoria lineal.\n\nNúmeros de rendimiento de un benchmark de Flutter Web (app Wonderous):\n\n| Métrica | dart2js | Dart/WASM GC |\n| --- | --- | --- |\n| Frame time (avg) | 18ms | 8ms |\n| Frame time (p99) | 45ms | 14ms |\n| Carga inicial | 3.1s | 1.8s |\n| Memoria (estado estable) | 62 MB | 31 MB |\n\nLa mejora en frame time p99 es la que más importa. Esas pausas largas de GC que hacían que Flutter Web se sintiera con latencia perceptible? Se fueron casi todas porque el collector incremental del browser está haciendo el trabajo ahora, en vez de un collector stop-the-world corriendo dentro de memoria lineal."},{"heading":"Análisis de Rendimiento: Fortalezas y Limitaciones","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#anlisis-de-rendimiento-fortalezas-y-limitaciones","content":"Los benchmarks de WASM GC en distintos workloads revelan un panorama con matices. No es universalmente más rápido."},{"heading":"Dónde Gana WASM GC","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dnde-gana-wasm-gc","content":"**Workloads pesados en objetos**, cualquier cosa que aloca y descarta muchos objetos chicos. El GC generacional del browser está increíblemente optimizado para este patrón. Un benchmark de construcción de árboles:\n\n```javascript\n// Benchmark: construir y recorrer un árbol binario de profundidad 20\n// 2^20 = ~1M nodos\n\n// WASM GC (Kotlin/WASM): 145ms promedio, 12ms pausa GC p99\n// Memoria lineal (C++ allocator custom): 89ms promedio, 0ms pausa GC\n// Memoria lineal (Kotlin viejo): 210ms promedio, 85ms pausa GC p99\n// JavaScript: 320ms promedio, 25ms pausa GC p99\n```\n\nWASM GC supera al viejo approach Kotlin/WASM por 30%, y si bien C++ crudo sigue siendo más rápido (cero overhead de GC), la brecha es mucho más pequeña de lo que se esperaría."},{"heading":"Dónde Pierde WASM GC","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#dnde-pierde-wasm-gc","content":"**Loops numéricos tight**, si se está haciendo matemática de matrices o procesamiento de señales, los structs de WASM GC agregan indirección que la memoria lineal no tiene. Estás pasando por tipos de referencia en vez de leer bytes crudos en offsets conocidos.\n\n```wasm\n;; Memoria lineal: load directo desde offset conocido\n(f64.load (i32.add (local.get $base) (i32.const 16)))\n\n;; WASM GC: acceso a campo a través de referencia\n(struct.get $Matrix $m02 (local.get $mat_ref))\n```\n\nEn un benchmark de multiplicación de matrices (1024x1024, f64), la versión WASM GC fue alrededor de 15-20% más lenta que memoria lineal. No es catastrófico, pero es medible."},{"heading":"Referencias GC Cross-Module: La Historia de Integración","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#referencias-gc-cross-module-la-historia-de-integracin","content":"La parte realmente transformadora es cómo los objetos WASM GC se integran con JavaScript. Una referencia a struct de WASM GC se puede pasar directamente a JavaScript como un objeto opaco ` WebAssembly.Struct `. El GC del browser ve tanto el wrapper JS como el struct WASM como parte del mismo grafo de objetos.\n\n```javascript\n// Lado JavaScript\nconst wasmModule = await WebAssembly.instantiateStreaming(\n fetch('app.wasm'),\n imports\n);\n\n// Obtener un struct manejado por GC desde WASM\nconst node = wasmModule.exports.createNode(42);\n\n// Guardarlo en una estructura de datos JS\nconst cache = new Map();\ncache.set('node1', node); // El GC de JS trackea esta referencia\n\n// Cuando se limpia el cache, el struct WASM queda elegible para recolección\n// ¡Sin leak! El GC del host ve el grafo de referencias completo.\n```\n\nEsto era literalmente imposible con memoria lineal. Tenías que fijar objetos manualmente y crear tablas de weak references para evitar leaks. Estos leaks en producción son notoriamente de los peores bugs para rastrear."},{"heading":"Qué Falta Todavía","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#qu-falta-todava","content":"WASM GC no está completo. Algunos gaps notables que quedan:\n\n1. **Weak references**: No están en la spec actual. Lenguajes que dependen de weak refs (el ` WeakReference ` de Java, el ` WeakReference ` de Kotlin) necesitan workarounds.\n2. **Finalizadores**: Sin soporte de finalización. Si tus objetos tienen recurse es externos, se necesita patrones de cleanup explícito.\n3. **Interior pointers**: No se puede tomar una referencia a un campo dentro de un struct. Esto limita algunos patrones de optimización.\n4. **Soporte de Safari**: La implementación de WebKit sigue detrás de flags. Hasta que Safari incorpore WASM GC sin flag, se necesita un fallback path para ~20% del tráfico web."},{"heading":"Construyendo para WASM GC Hoy","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#construyendo-para-wasm-gc-hoy","content":"Para proyectos nuevos que apuntan a WebAssembly con un lenguaje fuente con garbage collection, WASM GC es el approach recomendado. El soporte de toolchain está:\n\n- **Kotlin**: target ` kotlin-wasm ` con compilador K2 (stable desde Kotlin 2.0)\n- **Dart/Flutter**: ` dart compile wasm ` (stable desde Dart 3.3)\n- **OCaml**: ` wasm_of_ocaml ` (experimental pero prometedor)\n- **Java**: GraalWasm con soporte GC (detrás de un flag)\n\nPara Rust, C y C++, WASM GC no cambia mucho, se va a seguir usando memoria lineal, y está perfecto. WASM GC y memoria lineal coexisten en el mismo módulo.\n\nLa línea de fondo: distribuir un garbage collector dentro de tu binario WASM siempre fue un hack. WASM GC es la solución correcta, y los números de rendimiento lo prueban. La era de los bundles Kotlin/WASM de 3MB terminó."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#questions","content":""},{"heading":"¿Qué es la propuesta WASM GC?","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-1","content":"La propuesta WASM GC agrega tipos de referencia con garbage collection (structs, arrays y jerarquías de tipos) directamente a WebAssembly, permitiendo que lenguajes como Kotlin y Dart compilen a WASM sin incluir su propio runtime de GC."},{"heading":"¿WASM GC reemplaza la memoria lineal?","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-2","content":"No. La memoria lineal sigue existiendo para lenguajes como C/C++/Rust. WASM GC agrega un heap paralelo manejado por el garbage collector del runtime del host, específicamente para lenguajes que necesitan memoria administrada."},{"heading":"¿Qué browsers soportan WASM GC?","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-3","content":"Chrome 119+ y Firefox 120+ ya incorporaron soporte de WASM GC. Safari tiene soporte parcial detrás de flags a principios de 2026. Verificar el roadmap de WebAssembly para el estado actual."},{"heading":"¿Cuánto más pequeños son los binarios de Kotlin/WASM con GC?","level":3,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#question-4","content":"Los binarios de Kotlin/WASM GC son típicamente 60-80% más pequeños que Kotlin/WASM con un GC bundleado, porque ya no distribuyes un garbage collector custom y un memory allocator en el binario."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#article-tips","content":"1. **Tip:** Los structs de WASM GC *no* son lo mismo que objetos en memoria lineal. Viven en el heap del GC del host, así que las alocaciones con ` struct.new ` las maneja el collector de V8/SpiderMonkey del browser, no tu propio allocator.\n2. **Tip:** Al portar Kotlin a WASM GC, no realizar boxing manual de primitivos. El compilador Kotlin/WASM ya mapea ` Int `, ` Long ` y ` Double ` a ` i31ref ` de WASM GC o campos unboxed dentro de structs.\n3. **Tip:** Utilizar ` ref.cast ` con moderación, cada downcast dispara un chequeo de tipo en runtime. Si tu hot path hace casts repetidos, reestructurar la jerarquía de tipos para usar ` ref.test ` y branch.\n4. **Tip:** Perfilar apps WASM GC con el flag ` --experimental-wasm-gc ` de Chrome y el panel Memory de DevTools. Los structs manejados por el GC aparecen bajo *Wasm externals* en heap snapshots.\n\n- webassembly\n- wasm-gc\n- kotlin\n- dart\n- browser-runtimes\n- garbage-collection\n- rendimiento\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/wasm-gc-garbage-collection-browser-runtimes"},{"text":"JetBrains","url":"https://www.jetbrains.com/"},{"text":"Flutter","url":"https://flutter.dev/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes"},"headline":"WASM GC: Tipos con Garbage Collection en WebAssembly","description":"La propuesta WASM GC incorpora structs y arrays con recolección de basura automática a WebAssembly. Este artículo examina su impacto en Kotlin/WASM, Dart/WASM y la arquitectura de los browser runtimes.","datePublished":"2026-01-20","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"WebAssembly","keywords":"webassembly, wasm-gc, kotlin, dart, browser-runtimes, garbage-collection, rendimiento","wordCount":1544,"timeRequired":"PT9M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/wasm-gc-garbage-collection-browser-runtimes"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"WASM GC: Tipos con Garbage Collection en WebAssembly","item":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué es la propuesta WASM GC?","acceptedAnswer":{"@type":"Answer","text":"La propuesta WASM GC agrega tipos de referencia con garbage collection (structs, arrays y jerarquías de tipos) directamente a WebAssembly, permitiendo que lenguajes como Kotlin y Dart compilen a WASM sin incluir su propio runtime de GC."}},{"@type":"Question","name":"¿WASM GC reemplaza la memoria lineal?","acceptedAnswer":{"@type":"Answer","text":"No. La memoria lineal sigue existiendo para lenguajes como C/C++/Rust. WASM GC agrega un heap paralelo manejado por el garbage collector del runtime del host, específicamente para lenguajes que necesitan memoria administrada."}},{"@type":"Question","name":"¿Qué browsers soportan WASM GC?","acceptedAnswer":{"@type":"Answer","text":"Chrome 119+ y Firefox 120+ ya incorporaron soporte de WASM GC. Safari tiene soporte parcial detrás de flags a principios de 2026. Verificar el roadmap de WebAssembly para el estado actual."}},{"@type":"Question","name":"¿Cuánto más pequeños son los binarios de Kotlin/WASM con GC?","acceptedAnswer":{"@type":"Answer","text":"Los binarios de Kotlin/WASM GC son típicamente 60-80% más pequeños que Kotlin/WASM con un GC bundleado, porque ya no distribuyes un garbage collector custom y un memory allocator en el binario."}}]}]}],"contentHash":"a40d01181a0ac3c03f09c6e9f90c8d3d39a987a6e273117e52672bc3c3d03150","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/wasm-gc-garbage-collection-browser-runtimes","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":69206,"contentBytes":12510}}
