Repository navigation
Headstart: start dependents on early metadata, in cargo check and cargo build - #1
Merged
Merged
Conversation
rustc -Zearly-metadata writes a crate's metadata once item interfaces are checked, before function bodies are. cargo with CARGO_HEADSTART=1 pipelines check builds on it, so dependents start while their dependencies are still checking bodies. Includes setup and benchmark scripts, a smoke test, design notes, and a correctness sweep over rustc-perf on macOS and Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Cargo now holds a provisional unit's diagnostics, JSON messages and result until every dependency finishes cleanly, and drops them if one fails, so a failing build prints what it prints today (scripts/check-errors.sh checks this). Check units no longer get the synthetic full-build edges meant for linking, and early metadata no longer computes the reachable set, which forced inline and generic bodies before the write. Adds the error-delay measurement, prior art, and a Linux rerun. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
rustc: nested definitions are encoded when their body is an interface body (consts, const fns, coroutines, statics, opaque-type-defining functions), computed from the code, instead of by peeking at the query cache. -Zearly-metadata-verify checks the set; the sweep reports no misses. The early write now runs outside analysis's dependency tracking, depends on the crate's item list so incremental metadata reuse notices new items, and the flag is tracked. cargo: a failing dependency's dropped dependents no longer trigger the 'waiting for other jobs' message. Evidence: incremental edit sequences match headstart-off and a clean build; clippy (5534 diagnostics) and rustdoc output match; UI suite passes; peak memory, -Zthreads=8, and an incremental edit loop measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
rustc: a new analysis_interfaces query splits analysis; the driver writes libfoo.early-rmeta between it and the bodies, outside any query, replacing the session flag and global. Crate loading accepts early metadata and, before code generation, swaps in full metadata (same crate hash), pausing for it with the jobserver token handed back. Both files share an HIR-based SVH. Early metadata leaves out exported symbols. cargo: -Zearly-metadata on every compile; dependents start on the early-metadata notification in check and build; failure markers stop waiting dependents; emit kinds are parsed explicitly. Verified: check and build sweeps, UI suite (22129 passed), cargo testsuite, error and incremental scripts in both modes. Debug builds 6-32% faster, checks 7-47% faster on 16 cores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
A paused rustc no longer hands its jobserver token back itself. Cargo stops counting it as running (reusing its slot), and once the full metadata it waits for is written, starts no new work until it has resumed with a freed token, which cargo returns to the jobserver when it finishes. Before this, paused compilations were starved by work started in their place: 148 s of cargo-0.87.1's build, and ~17 s on its check critical path. scripts/log-rustc now logs artifact events per rustc run, and scripts/critical-path.py reports the critical path, linking crates held back, and pause time split into waiting for metadata vs. for a slot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
The SVH is upstream's metadata-bytes hash, computed over the early metadata (plus the same supplement: the HIR hash covering all bodies, and tracked options), and full metadata carries the same one. That's a complete identity from the start, so the HIR-based hash override is gone. Writing early metadata deletes the crate's stale full metadata, so a full file next to early metadata is always current: crate loading prefers it when it exists, with no modification-time rules. A failed compilation retracts its early metadata (rustc on errors or panics, cargo on crashes), and a dependent waiting for full metadata stops when that happens. The .failed-rmeta markers are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Under headstart, every rustc-compiled unit starts on its rlib dependencies' early metadata; proc-macro and dylib dependencies still need a full build. A crate that links records where each dependency's rlib will be written, and waits for them before linking (before code generation with LTO), with the same pause protocol. Writing early metadata deletes the crate's stale rlib, so an rlib that exists is current. Cargo drops the synthetic wait-for-every-rlib edges for these units. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
A paused compilation announces each file it waits for, and resumes as soon as it's written, without touching the jobserver. Cargo stops counting it as running while it's paused, so its slot goes to other work, and counts it again when it resumes, starting nothing new until enough jobs finish. The previous rule (cargo holds new work until a ready job has taken a token) stalled builds whose build scripts also draw tokens, such as libgit2's parallel C compile: cargo-0.87.1 went from 96 s to 101 s (build) and 51 s to 66 s (check) with headstart. Resuming without a token: 83 s and 41 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
With optimization on, writing early metadata computed deduced parameter attributes, which optimizes every function body. The MIR inliner then asked dependencies still loaded from early metadata whether they had MIR, and the "no" stayed cached after the swap to full metadata. Dependents failed with "missing optimized MIR" (typst and zed, which build dependencies with opt-level 2 or 3). Early metadata now records optimized MIR only for coroutines, and no deduced parameter attributes. -Zearly-metadata-verify reports queries that only full metadata answers when asked of an early crate. scripts/check-swap.sh reproduces the swap at every opt-level; it fails at 2 and 3 without this change. bench.sh takes per-project cargo arguments (dir::args) and deletes each project's target directory when done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
- Evaluate statics before the early write with par_hir_body_owners, as body checking does, and prefetch consts' and const fns' MIR in parallel under -Zthreads. zola's minify_html_common (large generated statics) took 78 s single-threaded before its early write, against 26 s for all of analysis with -Zthreads=8 and no headstart. - Without code generation, full metadata doesn't compute the reachable set under -Zearly-metadata. It's never read there, and at opt-level >= 1 it ran the MIR inliner against dependencies still loaded from early metadata (2819 -Zearly-metadata-verify reports in cargo check --release). Sweeps of all 53 rustc-perf benchmarks, headstart off and on, all clean with -Zearly-metadata-verify: build --release, check --release, and check and build with -Zthreads=8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Results - 13 real projects (rust-analyzer, zed, bevy, lemmy, polars, wasmtime, typst, helix, nushell, atuin, vaultwarden, zola, lldap), check and build, with and without -Zthreads=8. Default front end: 2-54% (check), 0-42% (build). On top of -Zthreads: up to 25%, three within noise. - rustc-perf -Zthreads=8 re-timed after the statics fix: nothing slower. - Correctness: six sweeps (debug, release, -Zthreads; check and build) clean with -Zearly-metadata-verify; UI and cargo test suites. rustc - Keep the early-metadata guard through linking, so a code generation failure withdraws early metadata without relying on the build tool. - Fix stale comments (jobserver tokens, choosing metadata by age, empty metadata for binaries); -Zearly-metadata-verify help covers both checks. cargo - Patch renamed to 0001-headstart.patch; fix stale comments. Scripts - scripts/sweep.sh and scripts/real-projects.sh (pinned commits, bevy's lockfile in projects/) replace one-off drivers. - bench.sh: a new output directory per run by default. - critical-path.py: pauses counted per episode, rlib waits included. - check-swap.sh covers opt-level z; bench-incremental.sh fails if its edit doesn't apply. Docs: one list of what output can differ; old-version numbers labeled; retraction, emit parsing and incremental checks described accurately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM
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.
Headstart starts dependent crates as soon as a dependency's item interfaces are checked, instead of after all of its function bodies are, for both
cargo checkandcargo build.On rustc's default front end, it makes clean builds of 13 real projects up to 54% faster for
cargo checkand up to 42% forcargo build, and none slower. Failing builds print the same diagnostics as today.What changes
rustc
-Zearly-metadata(patch):analysis_interfacesquery splits analysis into item interfaces and function bodies.libfoo.early-rmetaand announces it with anearly-metadataartifact notification.wait-metadata, polls, then announcesresume. It never touches the jobserver.-Zearly-metadata-verifyreports two things:cargo
CARGO_HEADSTART=1(patch):-Zearly-metadatato every compile.early-metadatanotification, in check and build. That covers libraries, binaries, tests, proc macros and build scripts.With the variable unset, cargo behaves like upstream.
docs/design.md covers how it works, what early metadata leaves out, the risks, and prior art (#64112).
Correctness
scripts/sweep.sh):-Zearly-metadata-verify.cargo checkandcargo build, each in debug, release and debug with-Zthreads=8.-Zthreads, whose overflow warnings move between runs even without headstart.scripts/check-errors.sh: an error in a dependency's body and an error in the binary, under check and build, in human and JSON formats. Diagnostics, exit status and the binary's output are identical.scripts/check-incremental.sh check|build: ten edit steps. Each matches headstart-off, and the final state matches a clean build.scripts/check-swap.sh: a library that starts on early metadata and swaps in the full metadata while paused, at opt-levels 0–3, s and z. It fails at 2 and 3 without the first fix below.Timing (Linux, 16 cores, median of 3 runs)
cargo checkcargo build-Zthreads=8(check / build)The full tables are in docs/results.md. They also cover helix, lldap, nushell and 21 rustc-perf benchmarks, where headstart saves 5–46% and, on top of
-Zthreads=8, 0–32%.Bugs found by real projects, all fixed
Until this round, the sweeps built only in debug mode, so none of these paths had been exercised:
-Zthreads, zola was 80% slower with headstart. regex-automata's long-standing small loss had the same cause.cargo check --release: full metadata computed the reachable set. It asked early dependencies for MIR, and the result was never read.A review of this PR also found that rustc withdrew early metadata only until code generation started. Its guard now lasts through linking.
Open
The next PR in the stack has the full list of what's left, and how to move this work to Claude Code containers. The main items:
cargo build.🤖 Generated with Claude Code
https://claude.ai/code/session_01Tet2pfGBx9bM6ge28w8rgM