Definition-anchored line-table dependencies for offset-based span hashing - #99
Draft
xmakro wants to merge 3 commits into
Draft
Definition-anchored line-table dependencies for offset-based span hashing#99xmakro wants to merge 3 commits into
xmakro wants to merge 3 commits into
Conversation
xmakro
force-pushed
the
perf/def-anchored-lines
branch
from
August 7, 2026 10:18
78f3825 to
3178a45
Compare
This was referenced Aug 7, 2026
xmakro
force-pushed
the
perf/def-anchored-lines
branch
2 times, most recently
from
August 7, 2026 21:13
6bf4d05 to
cb68f0c
Compare
xmakro
force-pushed
the
perf/def-anchored-lines
branch
from
August 8, 2026 00:43
cb68f0c to
9d318eb
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.
Replaces the witness-and-lint enforcement that was previously in this PR: same span-hashing change, same fused
def_anchorquery, but soundness no longer depends on enumerating rendering sites. Line observations are tracked automatically at the lookup layer, and an observation that no anchor covers makes the observing task unconditionally red instead of silently going stale.Span fingerprints and the
TAG_FULL_SPANcache encoding are as before: (file, offset in file, length), no line-table work in hashing,CachingSourceMapViewdeleted, hardened offset-range assert on decode. What changes is how line-observing consumers are invalidated.The query
def_anchor(DefId)is aneval_alwaysquery hashing everything that determines rendered line/column values for positions within a definition's extent: the file id, the line index of the definition's start, the definition's column on that line, the lengths of the extent's lines, and the multibyte-character and position-normalization entries inside the extent relative to its start (the line begin of the definition's first line, so character columns of a definition starting mid-line after a sibling are covered). Lengths are the shift-invariant representation of line structure, and aligned runs of 64 of them come from a per-file cache built lazily in one pass, so a large extent costs its edges plus one cached fingerprint per covered block. SeeSourceFile::line_extent_hash. Anchors coalesce typeck children (closures, coroutines, inline consts) into their root definition's node: the root's extent contains theirs and they codegen into the root's CGU anyway, so per-child nodes would add count without precision.line(pos)for a position in the extent is the anchor line plus the count of line starts between the definition's start andpos, and columns count from the last line start at or beforepos, so the hash covers exactly the rendered values. Consequences:cargo testrust-lang/rust#74890 class) changes the relative entries: covered.Soundness: automatic tracking, fail red
The previous revision enforced anchoring by threading a zero-sized witness through the codegen backends and denying raw lookups with an internal lint. That put the soundness argument in the review process: every line-rendering site had to be found, converted, and kept converted, and out-of-tree backends could not use the lint at all. This revision inverts the posture.
SourceFile::lookup_lineand the accessors built on it notifyrustc_span::LINE_TABLE_TRACK, a callback installed byrustc_interfacein the same way as the existingSPAN_TRACK. Inside a dep task the callback resolves every observation in one of three ways:TyCtxt::track_def_anchorrecords thedef_anchordependency and registers the anchored extent in the task'sTaskDeps. An observation at a position inside a registered extent is already invalidated by the recorded anchor and needs nothing further.FOREVER_RED_NODE: the observing task re-executes every session. A missed anchor, a mismatched anchor whose extent does not contain the rendered position, or a rendering class nobody thought about costs reuse, never staleness.The anchors themselves are tiered by how much of the recorded span data can be trusted, computed by the session-memoized
def_anchor_extentquery (eval_always no_hash: consumers read it without a dependency, and its result carries session-local positions):expn_that_definedis the root) anchors its fullsource_spanextent: the precise per-definition channel. A file module anchors its inner span, because itssource_spanis themod foo;declaration in the parent file.$itemtokens keep their real spans, so a function inside acfg-style wrapper keeps its true extent) plus the invocation's extent; def-site renderings from the macro's own file are covered by additionally anchoring the macro's definition.tracing::instrumentclass, whose recorded spans cover a few signature tokens) anchors the hull of its HIR spans within its own file: the one tier that walks HIR, on the rarest class.crate_source_anchor(cnum): a digest over (file id, content hash) of every source file of the crate, precomputed at metadata encode time (CrateRoot::source_files_digest), so the node costs a single metadata read to re-execute during try-mark-green. Foreign metadata only carries shrunk signature spans, and content hashes cover line structure.The expansion anchors are tiered the same way: a bang invocation's
expn_anchorhashes the call-site extent (the wholefoo!(...)span), while derive and attribute invocations, whose recorded call sites are just the path, stay file-granular.Outside dep tasks the callback is a no-op, and diagnostic emission already renders under
Ignore(seetrack_diagnostic), so error rendering costs nothing.eval_alwaystasks discard reads, which also makes the anchor providers themselves (which walk the line table) exempt by construction.The explicit anchor calls remain, but as precision opt-ins rather than soundness obligations:
codegen_miranchors the instance root once so the per-instruction debug-location lookups resolve as covered, the cg_ssa funnels anchor spans parented outside the instance (inlined callee bodies), coverage anchors the body definition, and the type pretty-printer anchors the printed definition for{closure@file:line:col}strings that reach cached, replayed diagnostics. Deleting any of these calls would turn the affected consumers red, not stale.This also removes the residual known gap of the previous revision, and the fallback earned its keep immediately: converting to fail-red surfaced two real staleness bugs in the previous revision of this stack. The anchors hashed
def_spanextents, which are shrunk to the signature, so a net-zero line move inside a function body invalidated nothing while its rendered#[track_caller]and debuginfo lines changed (a third build pass inincr-track-caller-line-shiftnow pins this), and expansion-created definitions were covered by extents that never contained their rendered positions. Both classes previously passed the test suite by silently staying green; under fail-red they showed up as reuse failures and are now anchored correctly. The expansion-anchor PR later in the stack turns the remaining parentless call-site class back into a precise dependency, as an optimization on a sound base rather than a soundness patch.Compared to the previous revision this deletes the
DefAnchoredwitness type and its threading through the cg_ssa debuginfo traits, cg_llvm, cg_gcc and cg_clif (the backend trait signatures revert to upstream), therustc::untracked_line_lookupinternal lint with its attribute plumbing, and the known-gaps section. The line lookups that legitimately bypass the hook (the anchor providers, metadata encoding of the line table, and post-lookup indexing at sites that already notified) use explicit*_untrackedaccessors.What this deletes compared to #91 is unchanged:
KeyFingerprintStyle::SelfHashand the newDepNodeKeyimpl, the truncated file-id index on theSourceMapand its collision abort, the import-all-crates fallback in the provider, theOption/sentinel for vanished files, and the bucket-boundary arithmetic.Wiring
codegen_miranchors each codegened function once when debuginfo is enabled: that covers itsDW_AT_decl_lineand every body position within its extent.dbg_scope_fnrecords the callee's anchor for the inlined function's own declaration position. cg_clif does the equivalent in its own codegen loop, including for statics'DW_AT_decl_line; cg_gcc is covered by the cg_ssa paths.TyCtxt::source_file_tracked(pos, def)), which is finer than a whole-file dependency. A mapping span outside the anchored extent trips the fallback and costs that codegen task's reuse rather than emitting stale coordinates.span_as_caller_locationand the const-eval location renderer take the calling definition as the fallback anchor for parentless cause spans; parented cause spans use their parent.{closure@file:line:col}and<impl at ...>forms anchor to the printed definition.-Zdebug-info-type-line-numberstype DIEs and coroutine variant DIEs anchor to the rendered definition or the span's parent, falling back to the coroutine definition for synthesized parentless variant spans.Validation
x checkgreen including cg_clif and cg_gcc, tidy and fmt clean, 50/50rustc_spanunit tests, 7/7 run-make (incr-track-caller-line-shiftincluding the new intra-body third pass,incr-line-table-bucket-boundary,incr-foreign-line-table-dep,incr-coverage-line-move,incr-multibyte-caller-column,incr-anchor-move,incr-macro-static-decl-line), 178/178tests/incremental, 634 closure/track_caller ui tests. The reuse assertions in the run-make tests double as enforcement tests: a hole in the extent-coverage bookkeeping failsrustc_partition_reusedinstead of going stale. Fallback-frequency probes on real crates (regex-automata, cargo at Debug and Opt with incremental) show zero uncovered codegen renderings on regex-automata and a residual tail of ~20 on cargo, plus a class insideearly_lint_checks, which reads the untracked AST and re-executes every session regardless.Perf
Standalone, this PR measures full -0.22%, incr-full +0.03%, incr-unchanged +0.38%, incr-patched -0.42%, all cells -0.10% against the merge-base: broad small wins (check incr-unchanged around -1.7% on most crates) against cargo's macro-heavy cells going red (opt incr-unchanged +21.0%), because the renderers' expansion-side coverage only lands with the expansion-anchor PR, and the position-carrying
source_spanfingerprint is only removed by the next PR. The stack is where the design pays; the standalone shape is the expected one.Same-day A/B against the merge-base (fresh base run) on the usual setup (instructions:u, jemalloc, 6 crates x Check/Debug/Opt x scenarios = 81 cells). Cumulative for the full reworked stack (#99 + #97 + #98): full -0.15%, incr-full +0.07%, incr-unchanged -0.47%, incr-patched -4.03%, all cells -1.48%, with 47 cells improved by at least 0.25% and 8 regressed. Every check-profile cell improves (serde incr-patched -19.0% to -20.7%, the other crates' check patched around -6%). The largest residual is cargo debug incr-unchanged +9.4%; the rest are at or below +1%.
The precision tiers above were driven to a near-zero uncovered-rendering rate by fallback-frequency probes on regex-automata, syn, cargo, hyper (with
--features=full) and ripgrep across Debug and Opt: each crate exposed a distinct span-trust failure (derive attr offsets,cfg-wrapper passthrough, nested helper-macro call sites, inert tool attributes on generated files, mid-line extent starts), each now pinned by the tier that covers it. A CGU-reuse investigation on ripgrep's patched rebuild additionally motivated recordingExpnHash-keyed anchors only where no definition-keyed anchor covers the position (see the expansion-anchor PR): with that, the patched rebuild re-codegens one module where the baseline compiler re-codegens five.