Only materialize reachable code for back passes - #3039
Draft
plajjan wants to merge 9 commits into
Draft
Conversation
plajjan
force-pushed
the
selective-back
branch
from
August 18, 2026 15:44
89048e9 to
26702d0
Compare
plajjan
force-pushed
the
selective-back
branch
from
August 19, 2026 12:41
26702d0 to
e0320b8
Compare
added 9 commits
August 19, 2026 14:48
The Vars class gives every syntax type a context-free traversal, but an analysis that must know the environment at each reference point cannot be expressed as freeQ: which names are local, what type a receiver has, and whether a type constructor is merely mentioned or actually needed all depend on the enclosing binders. Summ is one structural fold over the typed syntax with those decisions injected through a Walk record. Env hooks advance the environment past statements, declaration groups, parameters, patterns and other binders; result hooks contribute facts at variables, member selections, calls, conditions, iterators, assignment targets and type constructors. The Dot hook owns its receiver, so a client decides how the receiver is walked. plainWalk is inert; a client overrides only the hooks it needs, and env and result are type parameters so Names stays free of any environment type. Vars is unchanged.
Deactorizer decides which actor initializer bindings become instance state: parameters and initializer names that the actor's methods use, all of them when the body is unfinished, and hidden names only when they are state variables or method free variables. Move that decision next to envOf as QuickType.actorBindings, returning both the promoted bindings and the initializer names that stay local, so other consumers of the actor layout share Deactorizer's rule instead of restating it. envOf now covers Protocol and Extension declarations with an empty environment. Both are translated away during inference, so a post-inference caller that walks a typed suite containing them no longer hits a missing pattern.
Split a type-checked module into rows that a later build can load independently: one row per top-level statement, and for each class, actor, protocol and extension a container skeleton plus one row per method, per attribute declaration and per attribute initializer group. The skeleton keeps the original statement order and marks each hole with the member row that fills it, so restoring all rows reproduces the module exactly and restoring a subset yields a partial declaration whose remaining members are known. Attribute initialization is split by the type checker's own rule. Its scan of the constructor already decides which self attributes are initialized before self can escape; scanInitPrefix now also reports how many leading statements that prefix covers, and row partitioning uses that count as the boundary between per-attribute initializer fragments and the rest of the constructor. Actor bodies use the same actor state classification as Deactorizer, so an initializer that Deactorizer would promote to state is stored under that attribute. The interface format version changes because rows written without this structure cannot be loaded.
Walk each type-checked module once with its type environment and record what every stored row depends on: which top-level names it needs, which class members it selects, which classes it constructs, and which types it only mentions in a signature. Names inside a class body resolve against that class, so a bare method call from another method becomes a member edge rather than a global one. Selection of a member on an expression is classified from the receiver: a class-valued receiver gives a static edge, an instance receiver a dispatch edge, and a receiver whose type is an alias is resolved through the alias first. Conditions and iterated expressions record the __bool__, __iter__ and __next__ methods that the Normalizer will introduce later, since those calls do not exist in the typed tree yet. The walk is Names.summ with a hook record; this module keeps only the classification. Rows are stored alongside the syntax rows: a summary per top-level name, per member and per initializer group, the effective provider of each method and attribute slot for a class after inheritance and extensions, the constructor kind, the reflectable attributes, and one aggregate summary for consumers that must materialize a whole module. Every row is written as its own key with a stored copy of that key, so a reader validates what it loaded; the reader and writer for all row kinds are one keyed-row engine parameterized by a small descriptor per kind. Missing or impossible targets are compiler errors. Selection never falls back to keeping everything. Hashing gains a module-owned component for statements not owned by any name and a codegen identity derived from the compiler executable, so a selective output is keyed by the compiler that produced it. CodeGen emits opaque struct forward declarations for classes a projected module only references, and leaves witness fields out of class tables so a class's C layout does not depend on which witnesses were materialized.
Add a deterministic pretty-printer for the reachability rows of one module or one top-level name, plus a reader that loads exactly those rows, so the persisted dependency facts can be inspected and compared in tests. The --reachability flag on acton sig is defined here; the command wiring arrives with the scheduler, which also renders the closed project selection.
Close the program roots over persisted reachability rows with a worklist that reads only exact keys: a needed top-level name pulls its summary and its class shape, a member edge resolves to the slot's effective provider and pulls that member's row, a construction pulls the constructor and the initializer groups of every attribute the program reads, and reflection retains every reflectable attribute of every constructed subclass. Inherited attribute declarations are kept for layout even when their initialization is not, so a partially rendered class keeps the field prefix of its ancestors. Materialize the selection into partial modules for the ordinary back pipeline: restore only the selected syntax rows from each interface, project imports to the retained names while keeping an emptied import so the provider's module initialization still runs, and install declaration-only bindings for classes that are referenced but not rendered. Missing, ambiguous or abstract targets are errors; the projection is never widened.
Run back passes on the projected modules instead of the whole ones. Once every front pass has finished, partition the modules into those that must be rendered whole and those that can be projected, close the selection over the executable roots, and enqueue one back job per projected module with a codegen key derived from the whole selected universe. A rootless module that was directly requested stops after its interface unless a declared library boundary or a diagnostic output needs its back pass. Selective output for a provider lives in the provider's output tree but is keyed by the consumer's universe, so concurrent builds of different roots take a shared output lock. The language server compiles from a compile context and a changed-path set so it can reuse the plan cache. acton sig --reachability prints the closed project selection without running back jobs, or the cached rows of one module or name.
Unit tests cover row round-trips, exact-key reads, worklist closure and projection assembly. End-to-end projects compile and run through deferred scheduling, generated C, incremental invalidation, inheritance, native providers, output locking and consumer-specific provider output, including aliases, optional truth conversion, inherited property layout, module initialization, declaration-only types and rootless requests. The important dce2 cases are carried over.
Describe the exact interface rows and their keys, the module-owned hash component, consumer-specific projections, constructor fragments, opaque and native boundaries, output locks and the codegen key. Missing rows are errors and never widen the selection.
plajjan
force-pushed
the
selective-back
branch
from
August 19, 2026 12:57
e0320b8 to
c7fc4d2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Front passes run on 100% of a module. This changes so that for back passes, we only load and process names that are reachable.
Reachability facts are stored as exact TYDB rows and selected interfaces are reconstructed on demand. Rootless modules stop after front-pass persistence, while executable builds generate consumer-specific provider projections.
The project selection can be inspected with acton sig --reachability. Module and exact-name targets show the cached reachability facts stored in a TYDB.