Persistent frontend cache: replay the expanded AST when frontend inputs are unchanged - #108
Open
xmakro wants to merge 7 commits into
Open
Persistent frontend cache: replay the expanded AST when frontend inputs are unchanged#108xmakro wants to merge 7 commits into
xmakro wants to merge 7 commits into
Conversation
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.
Prototype of a persistent frontend cache:
-Zfrontend-cache(default on under-Cincremental) snapshots the expanded AST after macro expansion and replays it in later sessions whose frontend inputs are unchanged, skipping macro matching, transcription, fragment parsing and proc macro execution wholesale. Incremental compilation currently re-runs all of this every session only to rediscover, fingerprint by fingerprint, that nothing changed.The snapshot stores the post-expansion crate together with the session state that expansion created: the hygiene table delta, the def creation log, the source map additions (include!-ed, generated and metadata-imported files), the crate load order, metavariable spans, expansion-recorded glob map entries, stripped cfg items, and each module's binding insertion order. Everything is encoded with raw session-local ids. That is sound because the replay reconstructs the id spaces exactly: parsing reproduces the source map prefix, dependency crates are re-loaded and verified by name and SVH, hygiene rows are appended onto a length-checked table prefix, and the def log is replayed through the regular
create_defmachinery, soDefPathHashes, disambiguators, spans and syntax contexts come out bit for bit identical. The expansion driver then runs over the restored crate in replay mode: it finds no invocations, keeps the restoredNodeIds, treats all remaining attributes as inert, and rebuilds the resolver's module graph in one pass. Snapshotted binding orders are applied to the rebuilt resolution tables afterwards so that order-observing consumers (trait-in-scope lists hashed intohir_owner, module children, glob maps) match the recording session. Validation covers the compiler version, the option dep-tracking hash, the crate cfg set, every source file hash,env!reads, raw content hashes of non-source file deps, and dependency SVHs; any mismatch falls back to normal expansion. When inputs are valid but the crate numbering cannot be reproduced (several loaded crates sharing a name), the session expands normally without rewriting the snapshot, costing only the validation.Perf (stage2, lto=thin + jemalloc, collector bench_local, Check profile, primary suite, instructions:u): incr-unchanged mean -5.7%, with unicode-normalization -26.1%, libc -24.7%, syn -17.3%, diesel -16.1%, typenum -13.6%, serde -12.4%, nalgebra -9.9%. Full builds are unchanged (mean -0.06%). The cost is the snapshot write on recording sessions: incr-full +0.6% mean, incr-patched +2.4% median. Steady-state warm rebuilds measured outside the collector reach -20% to -55% (html5ever -55%, where proc macro execution is also skipped). tests/incremental passes 178/178 with the cache active; ui imports/resolve/macros subsets pass 1207/1207.
Known limitations, deliberate for the prototype: buffered early lints from expansion are not snapshotted, so some expansion-emitted warnings (unused_macros among them) disappear on cache hits; proc macros reading untracked state are assumed deterministic; proc-macro crates are not cached; crates with duplicate dependency names in the crate graph never hit (about half the primary suite, including hyper and html5ever under the collector's dependency sets) - provenance-aware crate preloading would unlock those; the first warm session after a recording still re-executes a small slice (
resolutions,crate_hash) because expansion-time glob-use and trait-import-use recordings are not replayed; and the snapshot write on every miss should eventually be made asynchronous or skipped when inputs did not change.