Skip to content

CEP-0061: Incremental Comptime Cache Invalidation

Draft

Draft proposal for cross-run and cross-edit comptime cache reuse. V1 requires only deterministic per-compilation caching and may discard comptime caches after source or module changes.

Summary

Catalyst should eventually define an incremental comptime cache validity model so compilers can reuse comptime evaluation results across compiler runs and source edits without risking stale semantic facts.

V1 keeps this deliberately simple: comptime caching is semantically invisible, valid within a compilation only when the active semantic environment matches, and may be discarded wholesale between compiler runs or after any source/module change.

Motivation

Comptime evaluation can depend on more than ordinary value inputs. Reflection calls may inspect visible impls, imports, module contents, attribute-produced metadata, dynamic metadata, target facts, safety mode, and captured Scope.caller() / Scope.current() handles.

Naive cross-run reuse can be unsound. For example, a cached T.implements(Eq(T)) result may become stale if an impl is added, removed, hidden by imports, or changed by an attribute provider. V1 can avoid that complexity by invalidating conservatively, but a future compiler should be able to reuse more work.

Proposed Direction

The proposal should evaluate three compatible implementation levels.

Per-Compilation Cache

This is the V1 baseline. A compiler may cache comptime results during one compilation when the semantic instance, explicit comptime arguments, target, safety mode, captured scope context, and active in-memory semantic environment match.

The compiler may discard all comptime cache entries between compiler runs or whenever source/module inputs change.

Coarse Input-Content Cache

A future compiler may key cross-run cache entries by content identities for the loaded input set:

semantic instance: can_eq(User)
comptime args: User
target: x86_64-linux
safety: Checked
captured scope: app.ct callsite identity
loaded inputs:
  prelude.ct content hash A
  lib.ct content hash B
  app.ct content hash C
  std/... content hash D

Reuse is valid only when every recorded content identity still matches. This is conservative: an unrelated edit in a loaded file may invalidate more work than necessary, but it avoids tracking individual declaration and impl dependencies.

File modification times may be used as an implementation shortcut, but the semantic model should be based on content identity or another trustworthy version identity. Modification times alone are not a stable language-level validity model.

Fine-Grained Dependency Cache

A more precise future cache may record the declarations, imports, modules, visible conformance sets, attribute metadata, contract surfaces, dynamic metadata, and source spans actually consulted during evaluation.

For scope-sensitive reflection, a cache entry might record:

captured caller scope
subject type identity
contract or structural shape identity
visible impl/import dependency set
lookup result dependency
attribute metadata dependencies
module/include dependencies
target and safety mode

This allows an unrelated source edit to preserve cached comptime results, but it requires a richer dependency graph and stable versioning for semantic facts.

Example

Illustrative future cache pressure:

fn can_eq(comptime T: Type) Type.Predicate {
  return T.implements(Eq(T))
}

impl Eq(User) for User {
  fn eq(self: *const Self, other: *const Self) bool => self.id == other.id
}

const ok = can_eq(User)

If the Eq(User) impl is removed, a cached ok = true result must not be reused. V1 may avoid this by discarding all caches after source changes. A future incremental cache can choose coarse content identities or fine-grained visible-impl dependencies.

V1 Compatibility

Active V1 docs should not require cross-run incremental comptime-cache reuse. They should require deterministic evaluation, per-compilation cache soundness, and conservative re-evaluation when validity is not proven.

Any future accepted incremental cache model must preserve V1 source semantics: caching remains semantically invisible except for execution-time Compiler.note and Compiler.warn replay behavior already documented in the comptime model.