Skip to content

Commit 44fbdb3

Browse files
committed
Fix StorageDead on unwind paths and add MIR transform to remove them
- Add StorageDead to unwind paths for all functions (not just coroutines) - Modify CleanupPostBorrowck to remove StorageDead from cleanup blocks - Add tests for the fix and StorageDead removal - Fixes #147875
1 parent 3cacec0 commit 44fbdb3

24 files changed

+506
-175
lines changed

compiler/rustc_mir_build/src/builder/scope.rs

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ struct DropNodeKey {
257257
impl Scope {
258258
/// Whether there's anything to do for the cleanup path, that is,
259259
/// when unwinding through this scope. This includes destructors
260-
/// and StorageDead statements to maintain proper drop ordering.
260+
/// (Value and ForLint drops). StorageDead drops are not included
261+
/// here because they don't require cleanup blocks - they're only
262+
/// needed for borrow-checking and are removed before codegen.
261263
fn needs_cleanup(&self) -> bool {
262264
self.drops.iter().any(|drop| match drop.kind {
263265
DropKind::Value | DropKind::ForLint => true,
@@ -1655,17 +1657,36 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16551657

16561658
let is_coroutine = self.coroutine.is_some();
16571659
// Check if there's a cleanup path (i.e., Value or ForLint drops that require cleanup)
1658-
let has_cleanup_path = self.scopes.scopes[uncached_scope..=target]
1659-
.iter()
1660-
.any(|scope| scope.drops.iter().any(|d| d.kind == DropKind::Value || d.kind == DropKind::ForLint));
1660+
let has_cleanup_path = self.scopes.scopes[uncached_scope..=target].iter().any(|scope| {
1661+
scope.drops.iter().any(|d| d.kind == DropKind::Value || d.kind == DropKind::ForLint)
1662+
});
16611663
for scope in &mut self.scopes.scopes[uncached_scope..=target] {
16621664
for drop in &scope.drops {
1663-
// Add all drops to unwind_drops for all functions (not just coroutines)
1664-
// to maintain proper drop ordering for borrow-checking. This matches
1665-
// the behavior in build_exit_tree.
1666-
// For coroutines, we always add all drops. For other functions, we now
1667-
// also add StorageDead and ForLint drops, but only when there's a cleanup path.
1668-
if is_coroutine || drop.kind == DropKind::Value || drop.kind == DropKind::ForLint || (drop.kind == DropKind::Storage && has_cleanup_path) {
1665+
// We add drops to unwind_drops to ensure the borrow-checker treats locals as dead
1666+
// at the same point on all paths (normal and unwind). This is for static semantics
1667+
// (borrow-checking), not runtime drop order.
1668+
//
1669+
// StorageDead drops are only needed when there are Value or ForLint drops present,
1670+
// because:
1671+
// 1. StorageDead is only relevant for borrow-checking when there are destructors
1672+
// that might reference the dead variable
1673+
// 2. If there are no drops, there's no unwind path to emit StorageDead on
1674+
// 3. StorageDead on unwind paths ensures the borrow-checker correctly tracks
1675+
// when variables are dead for the purpose of checking whether they might be
1676+
// referenced by destructors
1677+
//
1678+
// These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
1679+
// transform pass before codegen, so they don't affect LLVM output. They only
1680+
// affect static analysis (borrow-checking).
1681+
//
1682+
// For coroutines, we always add all drops. For other functions, we add:
1683+
// - Value and ForLint drops (always, as they require cleanup)
1684+
// - StorageDead drops (only when there's a cleanup path, for borrow-checking)
1685+
if is_coroutine
1686+
|| drop.kind == DropKind::Value
1687+
|| drop.kind == DropKind::ForLint
1688+
|| (drop.kind == DropKind::Storage && has_cleanup_path)
1689+
{
16691690
cached_drop = self.scopes.unwind_drops.add_drop(*drop, cached_drop);
16701691
}
16711692
}
@@ -1885,9 +1906,13 @@ where
18851906
// another set of arrows).
18861907
//
18871908
// We unwind from a drop on a local to its StorageDead statement for all
1888-
// functions (not just coroutines) to maintain proper drop ordering for
1889-
// borrow-checking. The drops for the unwind path should have already been
1890-
// generated by `diverge_cleanup_gen`.
1909+
// functions (not just coroutines) to ensure the borrow-checker treats locals
1910+
// as dead at the same point on all paths. This is for static semantics
1911+
// (borrow-checking), not runtime drop order. The drops for the unwind path
1912+
// should have already been generated by `diverge_cleanup_gen`.
1913+
//
1914+
// Note: StorageDead statements are removed by the `RemoveStorageMarkers` MIR
1915+
// transform pass before codegen, so they don't affect LLVM output.
18911916

18921917
// `unwind_to` indicates what needs to be dropped should unwinding occur.
18931918
// This is a subset of what needs to be dropped when exiting the scope.
@@ -1917,7 +1942,10 @@ where
19171942
// We adjust this BEFORE we create the drop (e.g., `drops[n]`)
19181943
// because `drops[n]` should unwind to `drops[n-1]`.
19191944
if unwind_to != DropIdx::MAX {
1920-
debug_assert_eq!(unwind_drops.drop_nodes[unwind_to].data.local, drop_data.local);
1945+
debug_assert_eq!(
1946+
unwind_drops.drop_nodes[unwind_to].data.local,
1947+
drop_data.local
1948+
);
19211949
debug_assert_eq!(unwind_drops.drop_nodes[unwind_to].data.kind, drop_data.kind);
19221950
unwind_to = unwind_drops.drop_nodes[unwind_to].next;
19231951
}
@@ -1993,10 +2021,20 @@ where
19932021
);
19942022
}
19952023
DropKind::Storage => {
1996-
// We emit storage-dead nodes on the unwind path for borrow-checking
1997-
// purposes. When `storage_dead_on_unwind` is true, we need to adjust
1998-
// the `unwind_to` pointer now that the storage-dead has completed, so
1999-
// that any future drops we emit will not register storage-dead.
2024+
// We emit StorageDead statements on the unwind path to ensure the borrow-checker
2025+
// treats locals as dead at the same point on all paths. This is for static semantics
2026+
// (borrow-checking), not runtime drop order. StorageDead is only needed when there
2027+
// are Value or ForLint drops present, because:
2028+
// 1. StorageDead is only relevant for borrow-checking when there are destructors
2029+
// that might reference the dead variable
2030+
// 2. If there are no drops, there's no unwind path to emit StorageDead on
2031+
//
2032+
// These StorageDead statements are removed by the `RemoveStorageMarkers` MIR
2033+
// transform pass before codegen, so they don't affect LLVM output.
2034+
//
2035+
// When `storage_dead_on_unwind` is true, we need to adjust the `unwind_to` pointer
2036+
// now that the storage-dead has completed, so that any future drops we emit will
2037+
// not register storage-dead.
20002038
if storage_dead_on_unwind && unwind_to != DropIdx::MAX {
20012039
debug_assert_eq!(
20022040
unwind_drops.drop_nodes[unwind_to].data.local,

compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//! - [`FakeRead`]
77
//! - [`Assign`] statements with a [`Fake`] borrow
88
//! - [`Coverage`] statements of kind [`BlockMarker`] or [`SpanMarker`]
9+
//! - [`StorageDead`] statements (these are only needed for borrow-checking and are removed
10+
//! after borrowck completes to ensure they don't affect later optimization passes or codegen)
911
//!
1012
//! [`AscribeUserType`]: rustc_middle::mir::StatementKind::AscribeUserType
1113
//! [`Assign`]: rustc_middle::mir::StatementKind::Assign
@@ -15,6 +17,7 @@
1517
//! [`Coverage`]: rustc_middle::mir::StatementKind::Coverage
1618
//! [`BlockMarker`]: rustc_middle::mir::coverage::CoverageKind::BlockMarker
1719
//! [`SpanMarker`]: rustc_middle::mir::coverage::CoverageKind::SpanMarker
20+
//! [`StorageDead`]: rustc_middle::mir::StatementKind::StorageDead
1821
1922
use rustc_middle::mir::coverage::CoverageKind;
2023
use rustc_middle::mir::*;
@@ -28,6 +31,10 @@ impl<'tcx> crate::MirPass<'tcx> for CleanupPostBorrowck {
2831
// Manually invalidate CFG caches if we actually change a terminator's edges.
2932
let mut invalidate_cfg = false;
3033
for basic_block in body.basic_blocks.as_mut_preserves_cfg().iter_mut() {
34+
// Only remove StorageDead from cleanup blocks (unwind paths).
35+
// StorageDead on normal paths is still needed for MIR validation
36+
// and will be removed later by RemoveStorageMarkers during optimization.
37+
let is_cleanup = basic_block.is_cleanup;
3138
for statement in basic_block.statements.iter_mut() {
3239
match statement.kind {
3340
StatementKind::AscribeUserType(..)
@@ -41,6 +48,9 @@ impl<'tcx> crate::MirPass<'tcx> for CleanupPostBorrowck {
4148
| StatementKind::BackwardIncompatibleDropHint { .. } => {
4249
statement.make_nop(true)
4350
}
51+
StatementKind::StorageDead(..) if is_cleanup => {
52+
statement.make_nop(true)
53+
}
4454
StatementKind::Assign(box (
4555
_,
4656
Rvalue::Cast(

tests/mir-opt/async_drop_live_dead.a-{closure#0}.coroutine_drop_async.0.panic-unwind.mir

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ fn a::{closure#0}(_1: Pin<&mut {async fn body of a<T>()}>, _2: &mut Context<'_>)
4444
}
4545

4646
bb3 (cleanup): {
47-
nop;
4847
nop;
4948
goto -> bb5;
5049
}

tests/mir-opt/basic_assignment.main.SimplifyCfg-initial.after.mir

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,19 @@ fn main() -> () {
7373
}
7474

7575
bb6 (cleanup): {
76+
StorageDead(_6);
7677
drop(_5) -> [return: bb7, unwind terminate(cleanup)];
7778
}
7879

7980
bb7 (cleanup): {
81+
StorageDead(_5);
8082
drop(_4) -> [return: bb8, unwind terminate(cleanup)];
8183
}
8284

8385
bb8 (cleanup): {
86+
StorageDead(_4);
87+
StorageDead(_2);
88+
StorageDead(_1);
8489
resume;
8590
}
8691
}

tests/mir-opt/building/logical_or_in_conditional.test_complex.built.after.mir

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ fn test_complex() -> () {
1919
bb0: {
2020
StorageLive(_1);
2121
StorageLive(_2);
22-
_2 = E::f() -> [return: bb1, unwind: bb35];
22+
_2 = E::f() -> [return: bb1, unwind: bb38];
2323
}
2424

2525
bb1: {
@@ -42,7 +42,7 @@ fn test_complex() -> () {
4242

4343
bb5: {
4444
StorageLive(_4);
45-
_4 = always_true() -> [return: bb6, unwind: bb35];
45+
_4 = always_true() -> [return: bb6, unwind: bb38];
4646
}
4747

4848
bb6: {
@@ -64,7 +64,7 @@ fn test_complex() -> () {
6464
}
6565

6666
bb9: {
67-
drop(_7) -> [return: bb11, unwind: bb35];
67+
drop(_7) -> [return: bb11, unwind: bb37];
6868
}
6969

7070
bb10: {
@@ -78,7 +78,7 @@ fn test_complex() -> () {
7878
}
7979

8080
bb12: {
81-
drop(_7) -> [return: bb13, unwind: bb35];
81+
drop(_7) -> [return: bb13, unwind: bb37];
8282
}
8383

8484
bb13: {
@@ -98,7 +98,7 @@ fn test_complex() -> () {
9898
}
9999

100100
bb15: {
101-
drop(_10) -> [return: bb17, unwind: bb35];
101+
drop(_10) -> [return: bb17, unwind: bb36];
102102
}
103103

104104
bb16: {
@@ -139,7 +139,7 @@ fn test_complex() -> () {
139139
StorageDead(_4);
140140
StorageDead(_1);
141141
StorageLive(_11);
142-
_11 = always_true() -> [return: bb23, unwind: bb35];
142+
_11 = always_true() -> [return: bb23, unwind: bb38];
143143
}
144144

145145
bb23: {
@@ -156,7 +156,7 @@ fn test_complex() -> () {
156156

157157
bb26: {
158158
StorageLive(_12);
159-
_12 = E::f() -> [return: bb27, unwind: bb35];
159+
_12 = E::f() -> [return: bb27, unwind: bb38];
160160
}
161161

162162
bb27: {
@@ -199,6 +199,25 @@ fn test_complex() -> () {
199199
}
200200

201201
bb35 (cleanup): {
202+
StorageDead(_10);
203+
StorageDead(_9);
204+
StorageDead(_2);
205+
goto -> bb38;
206+
}
207+
208+
bb36 (cleanup): {
209+
StorageDead(_10);
210+
StorageDead(_9);
211+
goto -> bb38;
212+
}
213+
214+
bb37 (cleanup): {
215+
StorageDead(_7);
216+
StorageDead(_6);
217+
goto -> bb38;
218+
}
219+
220+
bb38 (cleanup): {
202221
resume;
203222
}
204223
}

tests/mir-opt/building/logical_or_in_conditional.test_or.built.after.mir

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn test_or() -> () {
2020
}
2121

2222
bb1: {
23-
drop(_3) -> [return: bb3, unwind: bb13];
23+
drop(_3) -> [return: bb3, unwind: bb14];
2424
}
2525

2626
bb2: {
@@ -34,7 +34,7 @@ fn test_or() -> () {
3434
}
3535

3636
bb4: {
37-
drop(_3) -> [return: bb5, unwind: bb13];
37+
drop(_3) -> [return: bb5, unwind: bb14];
3838
}
3939

4040
bb5: {
@@ -86,6 +86,19 @@ fn test_or() -> () {
8686
}
8787

8888
bb13 (cleanup): {
89+
StorageDead(_6);
90+
StorageDead(_5);
91+
goto -> bb15;
92+
}
93+
94+
bb14 (cleanup): {
95+
StorageDead(_3);
96+
StorageDead(_2);
97+
goto -> bb15;
98+
}
99+
100+
bb15 (cleanup): {
101+
StorageDead(_1);
89102
resume;
90103
}
91104
}

0 commit comments

Comments
 (0)