Skip to main content

gruel_cache/
wire_air.rs

1//! Wire format for cached AIR (ADR-0074 Phase 4).
2//!
3//! See `wire.rs` for the parse/RIR cache wire types. This module covers
4//! the typed-IR side: a [`CachedAirOutput`] envelope holding everything
5//! a downstream consumer needs to skip running sema on a file:
6//!
7//! - The per-file `InternerSnapshot` (same as parse/RIR) so cached
8//!   `Spur`s can be remapped into the build's shared interner.
9//! - Each file's [`AnalyzedFunction`]s with their typed `Air`.
10//! - The serializable parts of `TypeInternPool` (the canonical
11//!   `Vec<TypeData>`; structural-dedup maps reconstruct on load).
12//! - The string and byte literal tables (`strings`, `bytes`) that
13//!   AIR's `*Const` instructions index into.
14//! - The interface definitions and vtable witnesses produced by sema.
15//! - The comptime `@dbg` output buffer collected during sema, so cache
16//!   hits can replay it to stderr and remain observably identical to
17//!   cache misses (ADR-0074 "Comptime side-effects replay" subsection).
18//!
19//! ## What this does NOT cache
20//!
21//! Compile *warnings* (`Vec<CompileWarning>`) are not yet serialized
22//! — `DiagnosticWrapper<WarningKind>` carries `Box<Diagnostic>` with
23//! complex label/note/help machinery whose serialization is its own
24//! focused implementation pass. On AIR cache hit, the build won't
25//! surface the cached warnings until that's done. This is a known
26//! regression that the integration code documents and the next
27//! follow-up will address.
28//!
29//! ## TypeId remapping
30//!
31//! The cached `TypeInternPool` is loaded into a fresh pool (replace,
32//! not merge). Cross-file caching needs a `TypeId` remap walker that
33//! visits every `Type`/`InternedType`/`StructId`/`EnumId`/`InterfaceId`
34//! field in the AIR and remaps it from cached numbering to the build's.
35//! That walker is the one piece of Phase 4 still missing; until it
36//! lands, AIR caching is single-file-only (sufficient for an end-to-end
37//! demo but not for a multi-file build).
38
39use serde::{Deserialize, Serialize};
40
41use gruel_air::{AnalyzedFunction, InterfaceDef, InterfaceVtables, TypeInternPool};
42
43use crate::wire::InternerSnapshot;
44
45/// Envelope around a per-file AIR + interner snapshot, ready for
46/// bincode serialization to the AIR cache.
47#[derive(Debug, Serialize, Deserialize)]
48pub struct CachedAirOutput {
49    /// Per-file interner snapshot (same role as in `CachedParseOutput`).
50    pub interner: InternerSnapshot,
51    /// All analyzed functions in this file with their typed AIR.
52    pub functions: Vec<AnalyzedFunction>,
53    /// Type intern pool snapshot. See `TypeInternPool`'s custom serde
54    /// impl: only the canonical `types: Vec<TypeData>` is captured;
55    /// the structural-dedup HashMaps reconstruct on load.
56    pub type_pool: TypeInternPool,
57    /// String literals, indexed by `AirInstData::StringConst` index.
58    pub strings: Vec<String>,
59    /// Byte-blob literals (`@embed_file`), indexed by `BytesConst` index.
60    pub bytes: Vec<Vec<u8>>,
61    /// Interface definitions, indexed by `InterfaceId.0`.
62    pub interface_defs: Vec<InterfaceDef>,
63    /// Vtable witnesses keyed by `(StructId, InterfaceId)`.
64    pub interface_vtables: InterfaceVtables,
65    /// Lines of `@dbg` output collected during comptime evaluation.
66    /// Replayed verbatim to stderr on cache hit so the build is
67    /// observably identical to a cold build.
68    pub comptime_dbg_output: Vec<String>,
69}
70
71impl CachedAirOutput {
72    /// Serialize to the bincode wire format used by `CacheStore::put`.
73    pub fn encode(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
74        bincode::serde::encode_to_vec(self, bincode::config::standard())
75    }
76
77    /// Deserialize from the bincode wire format.
78    pub fn decode(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError> {
79        let (out, _read) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
80        Ok(out)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use rustc_hash::FxHashMap;
88
89    #[test]
90    fn empty_air_output_round_trips() {
91        let cached = CachedAirOutput {
92            interner: InternerSnapshot::default(),
93            functions: Vec::new(),
94            type_pool: TypeInternPool::new(),
95            strings: Vec::new(),
96            bytes: Vec::new(),
97            interface_defs: Vec::new(),
98            interface_vtables: FxHashMap::default(),
99            comptime_dbg_output: Vec::new(),
100        };
101        let bytes = cached.encode().expect("encode");
102        let decoded = CachedAirOutput::decode(&bytes).expect("decode");
103        assert!(decoded.functions.is_empty());
104        assert!(decoded.strings.is_empty());
105        assert!(decoded.comptime_dbg_output.is_empty());
106    }
107}