Skip to content

Hash expansion call sites position-independently so macro-expanded code survives shift edits - #98

Draft
xmakro wants to merge 1 commit into
perf/anchor-respanfrom
perf/expn-anchor
Draft

Hash expansion call sites position-independently so macro-expanded code survives shift edits#98
xmakro wants to merge 1 commit into
perf/anchor-respanfrom
perf/expn-anchor

Conversation

@xmakro

@xmakro xmakro commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Stacked on #99 and #97 (the fused def_anchor stack), which this depends on. Reworked from the earlier revision that sat on the closed #91's channel; the expn_call_site_position position-fold query from that revision is replaced by expn_anchor, the expansion-side twin of def_anchor.

ExpnData is the remaining position carrier after #97: its call_site and def_site spans hashed absolutely, so editing text above a macro invocation re-fingerprinted the expansion and everything expanded from it, even though relative spans and the content-addressed source_span anchor were already position-free. This PR hashes both spans position-independently (stable_hash_span and stable_hash_anchor_span become one implementation differing only in whether the absolute arm covers the offset; parented spans were already relative). Identity between textually identical invocations rests on the existing disambiguator mechanism, plus one lowering fix: mark_span_with_reason now parents desugaring call sites the way rustc_expand already parents macro invocation spans, so same-kind desugarings in different definitions are distinct by parent rather than by session-order disambiguation. That also turns a previously order-dependent typeck_root invalidation in tests/incremental/hashes/function_interfaces.rs deterministic; the test's annotations are updated.

The anchor

With positions out of the expansion fingerprint, a renderer that lands on a parentless collapsed call site (debuginfo for macro-generated definitions under #[collapse_debuginfo], #[track_caller] whose expansion cause is an item-level invocation) needs an explicit dependency, exactly as definition-extent renderings need def_anchor. That dependency is expn_anchor(ExpnHash): the same content as def_anchor, the file id plus SourceFile::line_extent_hash over the call-site span, so it covers the rendered line index, column and extent line structure of the call site and nothing else. A byte-shifting edit above the invocation that moves no line break stays green; an edit that changes any rendered value goes red. The previous revision paired an opaque position fold with a separate line-table dependency at each site; here it is one node with the precision of the rendered values, the same fusion #99 applies on the definition side.

The dep-node key is the ExpnHash itself: it packs losslessly into the key fingerprint, so this PR reintroduces the small KeyFingerprintStyle::SelfHash recovery style (three hunks) that the bucketed design had used, now scoped to this one key type. Forcing a node for a foreign expansion resolves it through the defining crate's metadata; expn_hash_to_expn_id becomes fallible so a vanished crate or expansion keeps the zero sentinel and re-executes dependents instead of ICEing, while the on-disk-cache decode path (whose references can only have been green if the expansion still exists) unwraps loudly.

Wiring

TyCtxt::walk_chain_collapsed_tracked replaces the untracked hygiene walk wherever the collapsed result feeds a cached artifact, returning the span together with the DefAnchored witness (#99) when it recorded an expn_anchor for a parentless call site; otherwise the caller anchors through the span's parent or the instance as before, so the witness discipline is unbroken. Wired at the cg_ssa debuginfo funnels, cg_clif's get_span_loc, cg_llvm's file_metadata_from_def_id, and span_as_caller_location via the new expansion_cause_with_expn.

tests/run-make/incr-macro-static-decl-line pins the edge: a #[collapse_debuginfo] macro generates a static, and the two byte-length-identical versions swap the invocation line with a padding line, so nothing re-fingerprints and only the expn_anchor dependency refreshes the static's DW_AT_decl_line.

Performance

Same-day three-way A/B on the setup from #99 (instructions:u, jemalloc, same worktree and config, base = merge-base 3659db0, 81 cells). Marginal effect of this commit against the stack head (#99 + #97):

scenario marginal vs stack
full +0.01%
incr-full +0.04%
incr-unchanged +0.26%
incr-patched -3.16%
all cells -1.00%

The patched win is where expansion-heavy code lives: serde incr-patched improves -18.26% (check), -16.70% (debug), -17.27% (opt); cargo, ripgrep, regex-automata and hyper check incr-patched improve -3.95% to -4.64%. The incr-unchanged cost is the flip side of the new dependency: eight cells regress +0.32% to +0.57% from marking the expn_anchor eval_always nodes during try-mark-green. The fusion keeps that cost to one node per rendered call site; the previous revision of this PR paid a position node plus a line-table node.

Cumulative for the whole stack (#99 + lint + #97 + this) against the merge-base: full -0.14%, incr-full -0.13%, incr-unchanged -0.43%, incr-patched -3.95%, all cells -1.48%, with 43 of 81 cells improved by at least 0.25% and three regressions (ripgrep opt/debug incr-unchanged +0.76%/+0.62% and hyper debug incr-unchanged +0.33%, all the per-node try-mark-green class). For reference, the previous #91-based stack measured -3.19% patched / -1.18% all cells cumulative on its own same-day run; the fused stack is ahead on every aggregate with two fewer query kinds.

@xmakro
xmakro force-pushed the perf/expn-anchor branch from a1b2ce1 to ea7a63f Compare August 7, 2026 14:21
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.

1 participant