Span-resilient incremental hashing and cached proc-macro expansion [-7.75% incr-patched, -1.21% incr-unchanged] - #88
Closed
xmakro wants to merge 4 commits into
Closed
Span-resilient incremental hashing and cached proc-macro expansion [-7.75% incr-patched, -1.21% incr-unchanged]#88xmakro wants to merge 4 commits into
xmakro wants to merge 4 commits into
Conversation
…cached derive output
This was referenced Aug 4, 2026
Draft
Owner
Author
|
Split into independently defensible per-mechanism PRs for upstreaming: #90 (ExpnData position-free hashing) and #89 (attr-arg span parenting) are the flag-independent hashing pair, #91 (offset-based span hashing, isolated A/B), #92 (span-parent dep suppression), #93 (the -Zcache-proc-macros stack, based on #89). This PR stays as the integrated reference; cumulative numbers are in the description. |
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.
Incremental compilation is span fragile: inserting one line at the top of a file shifts every span below it, and the shifted positions leak into query keys, result fingerprints, and the on-disk cache. On a 300-struct serde crate, a one-line insertion at the top costs 41.3B instructions incrementally, against a 6.26B floor measured with
-Zincremental-ignore-spansplus-Zcache-proc-macros. This stack closes most of that gap with no flags: the same edit drops to 10.9B (-74%), and to 9.05B with-Zcache-proc-macros. It also makes the derive expansion cache actually hit across span shifts; before this it got zero hits on any position-shifting edit and was a net regression there (42.0B with the flag vs 41.3B without).One commit per mechanism, each independently useful:
1. Span-agnostic keying for
derive_macro_expansion. The key fingerprint is now (parent module, macroDefPathHash, tokens hashed with span hashing disabled) instead of the raw(LocalExpnId, &TokenStream). Expansion indices shift whenever an edit changes how many expansions precede a derive, so keying on them guarantees misses. The query becomesno_hash: a span-only difference in the output tokens is deliberately treated as no change. The triple is collision-free for legal code because identical tokens include the item name and duplicate names in one module are rejected. Adds an incremental test that a span shift reuses the cached expansion.2. Position-free
ExpnDatahashing, attr-arg span parenting, cached-token respan.ExpnHashhashed call-site positions, so one moved line reddened all macro-expanded HIR; it now hashes the call-site and def-site syntax context only, with uniqueness still provided byupdate_disambiguator(order-based, stable across shifts). Attribute argument tokens were hashed with unparented spans in all code, plain source included, churning every attributed item; they are now span-parented before lowering (only when incremental is on). Cached derive output tokens are respanned to a file-start anchor with root context, keeping each token's own context: cached spans previously round-tripped through line/col decode against the current file, so a stale line spilled into the next line and the hash drifted every session. The anchor choice matters: respanning to call site makessource_spanof every generated def move with edits and re-runs typeck/MIR for all generated bodies; the file-start anchor keeps them position-stable.3. Span-parent dep suppression for embed-only queries.
track_span_parentmakes every query that inspects a parented span record a dep onsource_span(parent). Forthir_body,typeck_root,mir_built,check_unsafety,check_match, andregion_scope_tree, the results embed spans verbatim and rebase them on reuse, so the dep only buys wasted re-execution on position shifts.thir_bodywas the amplifier: it isno_hash, so any red dep makes it unconditionally red and drags the whole MIR chain along. A newTaskDepsRef::AllowIgnoringSpanParentsvariant skips recording span-parent deps for those kinds only. Sub-queries record their own deps under their own tasks, so suppression does not leak, and codegen tracks its own span reads, so debuginfo stays correct (verified: a 3-line shift moves DWARF line info by 3).4. Offset-based span hashing and caching. The absolute span path now hashes (file stable id, offset in file, length) instead of line and column, and
TAG_FULL_SPANin the incremental cache encodes and decodes the same triple. Hash and codec must agree on what is position invariant orincremental_verify_ichreports unstable fingerprints; the codec's line/col round-trip was also the source of the drift bug above. This removesspan_data_to_lines_and_colsfrom ICH entirely (CachingSourceMapViewis gone from both the hashing context and the cache encoder, replaced by one-entrylookup_source_filecaches). Byte anchoring invalidates the same edits line anchoring does, and is strictly better for same-length edits, where bytes after the edit do not move.Perf, rustc-perf collector A/B, 6 crates x check/debug/opt, jemalloc dist config, geomean, flag off (what a normal build sees):
46 of 60 cells improved, none regressed at or above 0.25%. Largest: serde -22% to -24%, hyper -4.7% to -5.8%, ripgrep check -5.5%, cargo debug -3.7%.
Validation: tests/incremental clean, full tests/ui clean (21309 tests), forced
-Zincremental-verify-ichacross 9 successive span shifts in both flag states with no panics, same-length newline edit (the case where byte and line anchoring diverge) clean, DWARF line tables rebase with the edit, output of a cached-then-shifted serde_json build round-trips correctly, and mid-file derive insertion (which shifts later expansion disambiguators) correctly misses instead of reusing stale output.Known tradeoff: with
-Zcache-proc-macros, errors reported inside a cached expansion lose their exact position (help and expansion notes stay correct and line accurate via up-to-dateExpnData). Flag-off diagnostics are unchanged.