Skip to content

Replace the ATF result cache with evaluation from instance equations#12

Open
nanavati wants to merge 3 commits into
mainfrom
claude/itype-rnf-type-var-cache-sddtz9
Open

Replace the ATF result cache with evaluation from instance equations#12
nanavati wants to merge 3 commits into
mainfrom
claude/itype-rnf-type-var-cache-sddtz9

Conversation

@nanavati

Copy link
Copy Markdown
Owner

Staged here for the upstream PR to B-Lang-org/bsc (the integration cannot open PRs there; open it from B-Lang-org/bsc@main...nanavati:bsc:claude/itype-rnf-type-var-cache-sddtz9).

Summary

This PR replaces the ATF result cache with direct evaluation from the instance declarations — the rewrite rules the cache was memoizing all along. The cache captured point observations of instance equations at whatever types the typechecker happened to discharge them, serialized them into .bo files, and unioned them across the import closure at elaboration; a miss fell back to expandSynN, a full runTI (the constraint solver) invoked from the middle of elaboration. After this PR, a fully applied ATF application at ground arguments is reduced by a pure evaluator (ATFRules) over the instance equations in the symbol table, and nothing is captured, merged, or serialized.

The deeper invariant this enforces: permanent construction (IConv / ISimplify / IExpand) never consults the typechecker. Reduction at ground types is evaluation — matching is decisive, instance contexts resolve recursively at ground types, no judgment is involved — so the constraint solver's machinery (fresh variables, unification, improvement, deferral, incoherence decisions) is not needed and not reachable from the evaluator. Applications over type variables stay dormant in ISyntax exactly as before: whether and how they reduce is scope-dependent (rigid variables act as atoms) and remains the typechecker's business, settled before ISyntax exists.

Commits

  1. Evaluate type functions from instance equations, not the ATF cache — adds src/comp/ATFRules.hs and rewires the consumers. fullTypeNormalizer reduces armed (ground) applications via the evaluator; a ground application that does not reduce is an internal error naming the application, never a solver call. The IATFCache parameter threading disappears from iExpand/genModule/runG (rules are a pure view of the symtab each phase already holds), and elaboration-time cache growth (cExprToIExpr's mergeATFCache) is deleted. The cache's pin tests become rules pin tests (ATFElabReduce, ATFElabImportDef/Use).

  2. Retire the ATF cache — removes the production pipeline end to end: recordATFResult / sat's recordATFs, CATFCache / tiATFCache, the ipkg_atf_cache field, its conversion in iConvPackage, and its serialization in GenBin. A .bo now carries declarations and code, never memoized inferences; every consumer can re-derive every reduction from the instance declarations it already reads.

  3. IConv: reduce ground ATFs from instance equations, not via runTIiConvT ran expandSynN (a typechecker instantiation) on every converted type. Conversion is now pure (expandSyn + atfReduceInType); the typechecker round trip survives only for a type still carrying a variable-dependent ATF application, which normalized typechecker output should never produce (kept until enforced by an assertion).

Why the evaluator commits to the same instances the typechecker does

reducePred walks candidates most-specific-first and commits at the first match. On ground arguments, matching is plain structural pattern matching — no substitution into the argument can change which equations match — so "first match in most-specific-first order" is exactly "the unique most-specific matching instance", which is what the evaluator computes directly (with a pairwise generalization test). For a coherent class the overlap check has already guaranteed that any two instances matching the same ground type are comparable; if the evaluator ever finds an ambiguous ground match, that is an unsealed / incoherent family leaking into permanent construction, and it fails loudly rather than guessing.

Instance contexts are consulted only to bind variables the result mentions, by running their fundeps recursively at ground types (e.g. TupleSize'-style helper classes, or a Bits a sa context whose sa appears in the result). Dictionary satisfiability was the typechecker's obligation at the original use site and is not re-verified — the same stance the old cache-hit path took.

Behavior preservation

  • The cache was consulted only under canNorm (ground keys), so the polymorphic/open-keyed entries recorded by the typechecker were never readable by any consumer — deleting the capture loses nothing that was reachable.
  • normTFun already internalErrored on any ground application the symbol table could not reduce; the evaluator enforces the same contract directly, with a better message.
  • The incoherent-match record refusal (previously a careful comment in sat) becomes moot: nothing is recorded at all.
  • Full ATF test battery passes (107 tests), including new pins: same-package elaboration-time reduction (with -trace-atf-rules and a check that no ATF application survives into the expanded module) and reduction of a family defined entirely in an imported package. bsc.lib/Prelude (79 tests, including TupleSize) and bsc.binary pass.
  • A clean world rebuild (all libraries) succeeds with the new .bo format; existing .bo files are invalidated by the version hash as with any format change.

Performance notes

  • IConv no longer pays a runTI per converted type (every type in every package went through expandSynN).
  • Elaboration no longer pays a runTI per cache miss; an armed reduction costs a walk of the family's (small) instance equations.
  • .bo files shrink by their cache section.

Known restrictions / follow-ups

  • Instance heads are matched structurally; a family whose selection requires solving numeric equations in the head (mgu's deferred numeric equalities) is outside the evaluator's domain and fails loudly. No ATF-carrying family in the current libraries does this.
  • PrimTypeOf's iToCT/iConvT round trip in IExpand (for synonym-expansion-implemented operators like SizeOf) is orthogonal to the ATF cache and unchanged.
  • iConvT's expandSynN fallback for dormant applications is believed unreachable and should become an assertion in a follow-up.
  • This is groundwork for the IType interning work: the evaluator is IType-native (matching binds shared subtrees by reference, so reduction cost is proportional to the rule, not the argument), which is what interning needs to keep reductions DAG-bounded — and it makes ATF reduction at construction time possible in principle, since the rules are a monotone, load-time-complete view of the instance declarations rather than solver state.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A6uLp1FBmGTXMVRjWc9byU


Generated by Claude Code

claude added 3 commits July 18, 2026 06:24
Replace the elaboration-side consumers of the ATF result cache with
ATFRules, a pure ground evaluator over the instance declarations
already in the symbol table.  A fully applied ATF application at
ground arguments is reduced by committing to the unique most-specific
instance whose head matches (for a coherent class, exactly the
instance that reducePred's most-specific-first candidate walk commits
to) and running the instance context's fundeps at ground types to
bind the variables the result mentions.  Applications over type
variables stay dormant, as before: their reduction is scope-dependent
and is the typechecker's judgment, consumed before ISyntax exists.

fullTypeNormalizer loses its cache-hit path and its typechecker
fallback in one move: the old miss path was iConvT -> expandSynN, a
full runTI per query -- the constraint solver invoked from the middle
of elaboration -- and the cache existed to keep that rare.  Reduction
is now a pure function of the symbol table, so iExpand builds the
rules from the symtab it already has, the IATFCache parameter
threading disappears from iExpand/genModule/runG, the cache growth
during elaboration (cExprToIExpr's mergeATFCache) is deleted, and a
ground application that does not reduce is an internal error naming
the application, never an occasion to consult the solver.

ISyntaxCheck's tCheck builds rules from its symtab the same way.

This is behavior-preserving: the cache was consulted only under
canNorm (ground keys), so the polymorphic captures recorded by the
typechecker were never readable by the normalizer, and normTFun
internal-errored on anything the symtab could not reduce -- the same
contract the evaluator now enforces directly.

The cache is still produced and serialized; a follow-up removes the
production pipeline.  -trace-atf-cache[-miss] is replaced by
-trace-atf-rules, and the cache's pin tests become rules pin tests:
ATFCacheHit / ATFPolyCacheMiss collapse into ATFElabReduce (with the
cache gone the recorded/unrecorded distinction no longer exists), and
ATFCacheHitDef/Use become ATFElabImportDef/Use, pinning that
elaboration reduces a family defined entirely in an imported package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6uLp1FBmGTXMVRjWc9byU
With the evaluator reducing ground ATF applications directly from the
instance equations, the cache pipeline no longer has a consumer.
Remove it end to end:

 * recordATFResult / sat's recordATFs -- the typechecker no longer
   captures the ATF projections of coherent discharges.  The captured
   results were point observations of the instance equations, keyed by
   whatever the argument types were at discharge time; open-keyed
   entries (polymorphic and rigid-variable discharges) were never
   readable by the ground-keyed consumers, and ground entries are now
   re-derivable on demand.  The record-or-not distinction for
   incoherent matches goes with it: nothing is recorded at all.

 * CATFCache / tiATFCache -- typecheck and context reduction return
   only their results; cTypeCheck and cCtxReduceIO shrink.

 * IATFCache / ipkg_atf_cache -- the IPackage field, its conversion
   in iConvPackage, its serialization in GenBin, and the "own entries
   only" bookkeeping in fixupDefs are gone.  A .bo carries
   declarations and code, never memoized inferences: its instance
   declarations are the rules, and any consumer can re-derive every
   reduction from them.  Existing .bo files are invalidated by the
   version hash, as with any format change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6uLp1FBmGTXMVRjWc9byU
iConvT ran expandSynN -- a full typechecker instantiation -- on every
converted type, whether or not any type function appeared in it.
Conversion is now pure: expandSyn for synonyms and primitive type
functions, then atfReduceInType for ATF applications, which reduces
the ground ones from the instance equations and leaves everything
else (including partially applied ATF constructors) untouched.

The typechecker round trip survives on one path only: a type that
still carries a fully applied ATF application over type variables,
where expandSynN's scope-relative reduction (bound type variables as
rigid atoms) may apply.  Types reaching conversion have already been
normalized by typechecking, so this fallback is believed unreachable;
it is kept until that is enforced with an assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6uLp1FBmGTXMVRjWc9byU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants