Compilación Incremental — Caché Persistente en Disco

Archivo: src/incremental_cache.rs (241 líneas)

¿Por qué existe?

Evita recompilar módulos cuyo código fuente no cambió. Almacena bytecode serializado de cada módulo en .forja/cache/ y lo reutiliza en ejecuciones subsiguientes, comparando el hash del fuente guardado con el hash actual. La persistencia cruza corridas del compilador, no solo memoria.

Arquitectura

La caché es un HashMap<String, CachedModule> (ruta de módulo → entrada) que se carga desde manifest.json al construir y se vuelca a disco con save_to_disk. Cada entrada guarda el hash del fuente, la versión del formato (CACHE_VERSION, actualmente 1), el bytecode binario en formato .fbc y la lista de imports para invalidación transitiva.

ComponenteDescripción
CachedModuleEntrada serializable: source_hash, cache_version, bytecode_bin y imports
IncrementalCacheCaché en disco en .forja/cache/ con estadísticas hits, misses e invalidated
CacheStatsEstadísticas: entradas totales, hits, misses, invalidados y hit_rate

API Pública

FunciónDescripción
get(module_path, source_hash) -> Option<Vec<Opcode>>Devuelve bytecode si el hash coincide y la versión es actual; deserializa con deserializar_bytecode
put(module_path, source_hash, opcodes, imports)Serializa el bytecode (serializar_bytecode) y guarda la entrada
invalidate(module_path)Borra el módulo y todos sus dependientes transitivos (los que lo importan) con DFS
invalidate_changed() -> Vec<String>Re-hashea cada fuente con ModuleResolver::hash_fuente e invalida los que cambiaron
save_to_disk() / load_from_disk()Persisten/cargan el manifest.json con serde
clear() / stats()Limpia toda la caché y expone estadísticas (incluido hit rate)

Cómo se integra

El pipeline de compilación calcula el hash de cada fuente (ModuleResolver::hash_fuente), consulta get para reutilizar bytecode sin recompilar, y guarda con put los módulos recién compilados. invalidate_changed detecta cambios de fuente y la invalidación transitiva garantiza que, si base.fa cambia, lib.fa (que lo importa) también se recompile.

💡 invalidate usa un stack DFS con HashSet de visitados: la invalidación de un módulo se propaga a todos sus dependientes en cadena.
⚠️ Si cambia CACHE_VERSION, toda la caché existente se invalida: cada entrada almacena la versión con la que fue creada y get la compara.