Skip to content

Commit 4f7605b

Browse files
Add RemoveUninitDrops MIR pass
1 parent d707707 commit 4f7605b

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ mod nrvo;
6363
mod remove_false_edges;
6464
mod remove_noop_landing_pads;
6565
mod remove_storage_markers;
66+
mod remove_uninit_drops;
6667
mod remove_unneeded_drops;
6768
mod remove_zsts;
6869
mod required_consts;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use rustc_index::bit_set::BitSet;
2+
use rustc_middle::mir::{Body, Field, Rvalue, Statement, StatementKind, TerminatorKind};
3+
use rustc_middle::ty::subst::SubstsRef;
4+
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, VariantDef};
5+
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
6+
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
7+
use rustc_mir_dataflow::{self, move_path_children_matching, Analysis, MoveDataParamEnv};
8+
9+
use crate::MirPass;
10+
11+
/// Removes `Drop` and `DropAndReplace` terminators whose target is known to be uninitialized at
12+
/// that point.
13+
///
14+
/// This is redundant with drop elaboration, but we need to do it prior to const-checking, and
15+
/// running const-checking after drop elaboration makes it opimization dependent, causing issues
16+
/// like [#90770].
17+
///
18+
/// [#90770]: https://github.com/rust-lang/rust/issues/90770
19+
pub struct RemoveUninitDrops;
20+
21+
impl<'tcx> MirPass<'tcx> for RemoveUninitDrops {
22+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23+
let param_env = tcx.param_env(body.source.def_id());
24+
let Ok(move_data) = MoveData::gather_moves(body, tcx, param_env) else {
25+
// We could continue if there are move errors, but there's not much point since our
26+
// init data isn't complete.
27+
return;
28+
};
29+
30+
let mdpe = MoveDataParamEnv { move_data, param_env };
31+
let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
32+
.into_engine(tcx, body)
33+
.pass_name("remove_uninit_drops")
34+
.iterate_to_fixpoint()
35+
.into_results_cursor(body);
36+
37+
let mut to_remove = vec![];
38+
for (bb, block) in body.basic_blocks().iter_enumerated() {
39+
let terminator = block.terminator();
40+
let (TerminatorKind::Drop { place, .. } | TerminatorKind::DropAndReplace { place, .. })
41+
= &terminator.kind
42+
else { continue };
43+
44+
maybe_inits.seek_before_primary_effect(body.terminator_loc(bb));
45+
46+
// If there's no move path for the dropped place, it's probably a `Deref`. Let it alone.
47+
let LookupResult::Exact(mpi) = mdpe.move_data.rev_lookup.find(place.as_ref()) else {
48+
continue;
49+
};
50+
51+
let should_keep = is_needs_drop_and_init(
52+
tcx,
53+
param_env,
54+
maybe_inits.get(),
55+
&mdpe.move_data,
56+
place.ty(body, tcx).ty,
57+
mpi,
58+
);
59+
if !should_keep {
60+
to_remove.push(bb)
61+
}
62+
}
63+
64+
for bb in to_remove {
65+
let block = &mut body.basic_blocks_mut()[bb];
66+
67+
let (TerminatorKind::Drop { target, .. } | TerminatorKind::DropAndReplace { target, .. })
68+
= &block.terminator().kind
69+
else { unreachable!() };
70+
71+
// Replace block terminator with `Goto`.
72+
let target = *target;
73+
let old_terminator_kind = std::mem::replace(
74+
&mut block.terminator_mut().kind,
75+
TerminatorKind::Goto { target },
76+
);
77+
78+
// If this is a `DropAndReplace`, we need to emulate the assignment to the return place.
79+
if let TerminatorKind::DropAndReplace { place, value, .. } = old_terminator_kind {
80+
block.statements.push(Statement {
81+
source_info: block.terminator().source_info,
82+
kind: StatementKind::Assign(Box::new((place, Rvalue::Use(value)))),
83+
});
84+
}
85+
}
86+
}
87+
}
88+
89+
fn is_needs_drop_and_init(
90+
tcx: TyCtxt<'tcx>,
91+
param_env: ParamEnv<'tcx>,
92+
maybe_inits: &BitSet<MovePathIndex>,
93+
move_data: &MoveData<'tcx>,
94+
ty: Ty<'tcx>,
95+
mpi: MovePathIndex,
96+
) -> bool {
97+
// No need to look deeper if the root is definitely uninit or if it has no `Drop` impl.
98+
if !maybe_inits.contains(mpi) || !ty.needs_drop(tcx, param_env) {
99+
return false;
100+
}
101+
102+
let field_needs_drop_and_init = |(f, f_ty, mpi)| {
103+
let child = move_path_children_matching(move_data, mpi, |x| x.is_field_to(f));
104+
let Some(mpi) = child else {
105+
return f_ty.needs_drop(tcx, param_env);
106+
};
107+
108+
is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)
109+
};
110+
111+
// This pass is only needed for const-checking, so it doesn't handle as many cases as
112+
// `DropCtxt::open_drop`, since they aren't relevant in a const-context.
113+
match ty.kind() {
114+
ty::Adt(adt, substs) => {
115+
let dont_elaborate = adt.is_union() || adt.is_manually_drop() || adt.has_dtor(tcx);
116+
if dont_elaborate {
117+
return true;
118+
}
119+
120+
// Look at all our fields, or if we are an enum all our variants and their fields.
121+
//
122+
// If a field's projection *is not* present in `MoveData`, it has the same
123+
// initializedness as its parent (maybe init).
124+
//
125+
// If its projection *is* present in `MoveData`, then the field may have been moved
126+
// from separate from its parent. Recurse.
127+
adt.variants.iter_enumerated().any(|(vid, variant)| {
128+
// Enums have multiple variants, which are discriminated with a `Downcast` projection.
129+
// Structs have a single variant, and don't use a `Downcast` projection.
130+
let mpi = if adt.is_enum() {
131+
let downcast =
132+
move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid));
133+
let Some(dc_mpi) = downcast else {
134+
return variant_needs_drop(tcx, param_env, substs, variant);
135+
};
136+
137+
dc_mpi
138+
} else {
139+
mpi
140+
};
141+
142+
variant
143+
.fields
144+
.iter()
145+
.enumerate()
146+
.map(|(f, field)| (Field::from_usize(f), field.ty(tcx, substs), mpi))
147+
.any(field_needs_drop_and_init)
148+
})
149+
}
150+
151+
ty::Tuple(_) => ty
152+
.tuple_fields()
153+
.enumerate()
154+
.map(|(f, f_ty)| (Field::from_usize(f), f_ty, mpi))
155+
.any(field_needs_drop_and_init),
156+
157+
_ => true,
158+
}
159+
}
160+
161+
fn variant_needs_drop(
162+
tcx: TyCtxt<'tcx>,
163+
param_env: ParamEnv<'tcx>,
164+
substs: SubstsRef<'tcx>,
165+
variant: &VariantDef,
166+
) -> bool {
167+
variant.fields.iter().any(|field| {
168+
let f_ty = field.ty(tcx, substs);
169+
f_ty.needs_drop(tcx, param_env)
170+
})
171+
}

0 commit comments

Comments
 (0)