Skip to content

Support borrowed locals in DestinationPropagation.#146743

Draft
cjgillot wants to merge 1 commit into
rust-lang:mainfrom
cjgillot:dest-prop-borrowed
Draft

Support borrowed locals in DestinationPropagation.#146743
cjgillot wants to merge 1 commit into
rust-lang:mainfrom
cjgillot:dest-prop-borrowed

Conversation

@cjgillot
Copy link
Copy Markdown
Contributor

Follow-up to #142915

The current implementation of DestinationPropagation refuses to consider locals that are borrowed at any point in the MIR.

This PR attempts to relax this requirement without trying to perform alias analysis.

For each local, we consider they are borrowed from the first Rvalue::Ref or Rvalue::RawPtr statement until they are marked StorageDead.

The liveness analysis is modified to consider that:

  • an indirect write may write to any currently borrowed local;
  • an indirect read may read to any currently borrowed local;
  • a call/tailcall/drop/yield/inlineasm terminator may read and write to any currently borrowed local.

Future extensions may consider a move to discard borrows, but this requires work on MIR semantics beforehand.

r? @Amanieu

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Sep 19, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 19, 2025
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Sep 19, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@tmiasko
Copy link
Copy Markdown
Contributor

tmiasko commented Sep 19, 2025

Those conditions are insufficient since they make it possible to observe that storage of unified locals overlaps:

$ cat a.rs 
fn main() {
    let x;
    let y;
    {
        let a = 0;
        x = &a as *const _;
        let b = a;
        y = &b as *const _;
    }
    assert!(x != y);
}
$ rustc +stage1 -O a.rs
$ ./a
thread 'main' (34419) panicked at a.rs:10:5:
assertion failed: x != y
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@Amanieu
Copy link
Copy Markdown
Member

Amanieu commented Sep 21, 2025

Right, the correct way to calculate the live range of a borrowed local is to start from the assignment that initializes it (at the late position) and end at StorageDead. This needs to be done using a separate forward dataflow analysis, but then the produced live range interval should be compatible with those from the backwards analysis for non-borrowed locals.

@cjgillot cjgillot marked this pull request as draft September 28, 2025 20:50
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 28, 2025
@bors
Copy link
Copy Markdown
Collaborator

bors commented Oct 3, 2025

☔ The latest upstream changes (presumably #142771) made this pull request unmergeable. Please resolve the merge conflicts.

invictustitan2 added a commit to invictustitan2/rust that referenced this pull request May 25, 2026
Follow-up to 6b87f07. Closes the gap between the Range B+ implementation
landed there and the 4 hand-blessed tests whose outputs change when borrowed-
local dest_prop merges more candidates.

mir-opt (blessed re-output):

* `tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.{panic-abort,panic-unwind}.mir`
  — the iterator-state copy `_5 = copy _4` is `nop`'d and the subsequent
  `&mut _5` becomes `&mut _4` (the local indices and a `StorageLive`/
  `StorageDead` pair shift accordingly). This is the optimization doing
  its job; no behavioural change.

codegen-llvm (`CHECK` directives tightened, not blessed):

* `tests/codegen-llvm/function-arguments.rs` — `%x.0` / `%x.1` → `%{{[^,]+}}`
  / `%{{[^)]+}}`. Parameter NAMES change when a name-bearing local is merged
  out; every LLVM attribute the test probes (`noundef nonnull align 4`,
  `noalias`, `captures(address, read_provenance)`) is byte-identical to
  before. Pure name-cosmetic.

* `tests/codegen-llvm/noalias-box-off.rs` — `%foo` → `%{{[^)]+}}` for the
  same reason. The actual assertion (`CHECK-NOT: noalias` under
  `-Zbox-noalias=no`) is unchanged and still holds.

* `tests/codegen-llvm/zip.rs` — the previous `CHECK: memcpy` was passing in
  the historical compiler by incidentally matching the 48-byte
  `Zip<Iter, IterMut>` iterator-state struct copy (`_5 = copy _3` lowered
  to `llvm.memcpy.p0.p0.i64(%iter, %_3, 48)`), NOT the loop body. At
  `-Cno-prepopulate-passes -Copt-level=3` the body lowers to per-byte
  `load i8` / `store i8` in both the historical compiler and the current
  one — LLVM never autovectorised it. With dest_prop merging borrowed-local
  candidates soundly (rust-lang#146743), the iterator-state copy is
  eliminated and no memcpy appears anywhere in either function. Inverting
  the assertion to `CHECK-NOT: memcpy` locks in the win: the test now
  regresses if dest_prop ever stops eliminating the redundant Zip copy.
  A test comment records why the original positive memcpy assertion was
  brittle and what changed.

Re-validated:

* `./x.py test --stage 1 tests/codegen-llvm`: 0 failed (was 935/3/128 in
  the bare Range B+ build at 6b87f07; now clean).
* `./x.py test --stage 1 tests/mir-opt tests/codegen-units tests/assembly-llvm tests/crashes`: 0 failed.
* `./x.py test --stage 1 tests/ui` (the load-bearing run-pass corpus):
  21013/0/223, unchanged.
* tmiasko's counterexample (rust-lang#146743 comment 2025-09-19):
  `x != y` holds at `-O` and `-Zmir-opt-level=3`, unchanged.
* rustlantis differential, 1500 random UB-free seeds, base4 vs treatment:
  1500 clean, 0 ICEs, 0 real divergences, unchanged.

This commit also tightens the characterisation in 6b87f07's message:
zip.rs is not a name-cosmetic golden diff — it's a real codegen improvement
(one redundant 48-byte memcpy eliminated per `zip_copy`/`zip_copy_mapped`
invocation) that the original `CHECK: memcpy` was incidentally matching.
The other two codegen-llvm files genuinely are name-cosmetic and the
regex tolerance preserves what the tests actually intend to assert.

Refs: rust-lang#146743

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants