Skip to content

feat: cross-crate dead-function elimination via binary-unit split (-Zdead-fn-elimination)#17239

Draft
yijunyu wants to merge 1 commit into
rust-lang:masterfrom
yijunyu:dfe-option1
Draft

feat: cross-crate dead-function elimination via binary-unit split (-Zdead-fn-elimination)#17239
yijunyu wants to merge 1 commit into
rust-lang:masterfrom
yijunyu:dfe-option1

Conversation

@yijunyu

@yijunyu yijunyu commented Jul 19, 2026

Copy link
Copy Markdown

Draft — depends on an unlanded rustc change. This demonstrates the Cargo side of
cross-crate dead-function elimination and is contingent on the rustc -Zdead-fn-* flags
(branch yijunyu/rust@dfe-flag-clean,
follow-up to rust-lang/rust#158220). It will not build or pass CI here without that toolchain.
Opening as a draft for design discussion, not for merge.

What this is

A follow-up to rust-lang/rust#158220 and the #t-compiler discussion on cross-crate dead-function
elimination. #158220's binary-only -Zdead-fn-elimination is a no-op — a binary's
monomorphization collector is already exact. The real slack is cross-crate: a library is
compiled before its callers, so on its own it must emit its whole pub closure, 55–85% of which
a given binary never calls (measured via nm rlib-vs-binary on nix/socket2/rustix/gix across
nushell/helix/zed/zeroclaw).

This PR implements the binary-unit split — the Cargo-side scheduling change that lets a
single cargo build -Zdead-fn-elimination produce a pruned first build (no LTO, no second
pass).

Approach

For each binary Build unit, a probe is interned: a Check-mode clone of the same binary.
The probe runs the frontend + monomorphization collector and emits a per-dependency used-set
(which of the dependency's functions the build reaches). It depends only on dependency rmeta,
so it runs early. Each eliminable dependency Build unit then depends on the probe and prunes
against its used-set. The graph stays acyclic:

dep(check/rmeta)  ->  probe(emits used-sets)  ->  dep(build, pruned)  ->  bin(link)

Changes

  • core/features.rs-Zdead-fn-elimination unstable flag.
  • core/compiler/unit_dependencies.rsinject_dead_fn_probe_units: the binary-unit split,
    probe interning, cycle-safe ordering edges, and build-time-dependency exclusion (a lib reachable
    from a proc-macro / build script is never pruned).
  • core/compiler/mod.rs — flag wiring: -Zdead-fn-emit-used-set on check units,
    -Zdead-fn-elimination + used-set consumption on eliminable dependency Build units, and
    -Zalways-encode-mir on check units so the binary probe's collector can read dependency MIR.

+441 lines, 3 files.

Result

Measured on zeroclaw (~330-unit workspace): one cargo build -Zdead-fn-elimination links a
working binary that runs, with 2900 functions excluded on the first build — 0 undefined
symbols, 0 ICE, 0 graph cycles, no job-slot deadlock. Validated end-to-end (0 undefined symbols,
binary builds and runs correctly) on: a 2000-fn synthetic (Main→A→B), two clap binaries, an
.or_else(fn) higher-order case, a closure-call case, and an IMAP parser (nom combinators,
imap-proto 0.10 and 0.16 — parses correctly).

Wherever completeness can't be guaranteed (missing used-set or an absent per-crate completeness
marker) the pass keeps the full public closure, so the binary always links and runs; correctness
takes priority over maximum elimination.

Full design document and measurements are available on request. Feedback on the scheduling
approach is very welcome.

…dead-fn-elimination)

Implements option 1 of the cross-crate DCE design (rust-lang/rust#158220
follow-up): one `cargo build -Zdead-fn-elimination` produces a pruned first
build. For each binary a Check-mode probe sibling emits per-dependency used-sets
(depends only on dep rmeta, so it runs early); each eliminable dependency Build
unit depends on the probe and prunes against its used-set. Acyclic:
dep(check) -> probe -> dep(build,pruned) -> bin(link).

- features.rs: -Zdead-fn-elimination unstable flag.
- unit_dependencies.rs: inject_dead_fn_probe_units (the binary-unit split, probe
  interning, cycle-safe wait-edges, build-time-dep exclusion).
- mod.rs: flag wiring (emit-used-set on check units, prune flags + per-crate
  .done-marker wait on dep Build units), -Zalways-encode-mir on check units so
  the binary probe's monomorphization collector can read dependency MIR.

Measured on zeroclaw (~330 units): rc=0, binary links+runs, 2900 fns excluded on
the first build, 0 undefined symbols, 0 cycles, no job-slot deadlock.
@rustbot

rustbot commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes made this pull request unmergeable. Please resolve the merge conflicts.

@rustbot rustbot added the S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. label Jul 19, 2026
@epage

epage commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This gives some details of what is being done. Some parts feel a bit jargon-y which makes it a little less clear what is happening.

A big question that I have that is unaddressed in the PR description (again, maybe I didn't quite understand it) is why this needs a change to Cargo. What role is Cargo filling that the compiler couldn't do?

Also, the PR description has the feel of being written by an AI. I know we don't have an AI policy at this time and you might be working with people on an experimental basis, but a big problem with PR descriptions is AIs focus on all of the wrong details for a human reviewer. For example, the Changes section is redundant with the git information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants