Generational Garbage Collector
Archivo: src/gc.rs (1000 líneas)
¿Por qué existe?
Implementa un garbage collector generacional diseñado para reemplazar Arc en ForjaFast. En lugar de contar referencias y liberar memoria individualmente, el GC administra el heap por generaciones: los objetos nuevos viven en la young generation (nursery) y los que sobreviven suficientes colecciones se promueven a la old generation. La idea es que la mayoría de los objetos mueren jóvenes, así que la colección frecuente opera sobre un espacio pequeño y barato.
Arquitectura
El GC separa la memoria en tres bump allocators: nursery (young generation), survivor (espacio intermedio de copia) y old_memory (old generation). Cada objeto comienza su vida en el nursery con bump allocation (sin free list), y las colecciones copian los objetos marcados entre espacios aplicando promoción por edad: un objeto se vuelve Generation::Old cuando su edad alcanza el promotion_age (por defecto 3).
| Componente | Descripción |
|---|---|
GcHeader | Header de cada objeto: size, alloc_size (header + data + padding), bit de marked, age (colecciones young sobrevividas) y generation |
Generation | Enumeración Young / Old que clasifica a cada objeto |
GcRef | Puntero seguro (copy) a un objeto del heap GC, con acceso a header() y data_ptr() |
BumpAllocator | Asignador por bump sin free list: alloc, reset, for_each_object y copy_marked_to con promoción |
GenerationalGC | El GC completo: nursery + survivor + old, raíces, remembered set, colecciones young/full y métricas |
GcStats | Estadísticas: conteos de colecciones, bytes usados por generación, raíces, remembered set, total_allocated y total_freed |
Write Barriers
El GC generacional requiere write barriers para rastrear referencias old→young. Cuando un objeto de la old generation es mutado para apuntar a un objeto young, la VM debe llamar a write_barrier (o write_barrier_raw) para registrar el objeto en el remembered set. Sin esto, la young collection no encontraría esos objetos young y los liberaría prematuramente (use-after-free).
write_barrier(&obj, field_offset): lee el campo enfield_offsety, si el objeto es old y el valor apunta a young, lo registra en el remembered set (evitando duplicados).write_barrier_raw(&obj, new_ptr_value): variante eficiente cuando ya se tiene el valor del puntero nuevo.invalidate_remembered_set_entry: opcional, reduce trabajo cuando una referencia old→young se vuelve old→old o null.is_in_young_generation/is_valid_gc_ptr_value: verifican por rango de direcciones si un valor es un puntero al heap GC.
Colecciones
| Colección | Estrategia |
|---|---|
young_collection() | Mark desde raíces + remembered set, copia los marcados del nursery y del survivor al survivor temporal (con promoción por edad), resetea el nursery. Se dispara cuando el nursery llega al 80% (should_collect_young). |
full_collection() | Mark desde raíces + remembered set, promueve los supervivientes del survivor a old, compacta la old generation, resetea nursery y survivor, y limpia el remembered set (los write barriers de la VM lo re-registran). |
mark() | Tracing conservador: escanea el área de datos de cada objeto palabra por palabra buscando valores que parezcan punteros al heap GC y los empuja al mark stack. |
Cómo se integra
La VM ForjaFast lo usa para la gestión de memoria del runtime. La integración exige que la VM llame a write_barrier en cualquier SetField, SetIndex, asignación de variables que contengan referencias GC, y en retornos de punteros GC desde FFI. El patrón típico es mutar primero y notificar después:
set_field(obj, offset, new_value); // mutar primero
gc.write_barrier(&obj, offset); // notificar al GC use-after-free.nursery_threshold y old_threshold), y los tamaños por defecto son 4 MB de nursery, 4 MB de survivor y 16 MB de old (with_sizes permite personalizarlos).