]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_mir_transform/src/remove_uninit_drops.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / compiler / rustc_mir_transform / src / remove_uninit_drops.rs
CommitLineData
5e7ed085 1use rustc_index::bit_set::ChunkedBitSet;
353b0b11 2use rustc_middle::mir::{Body, TerminatorKind};
a2a8927a
XL
3use rustc_middle::ty::subst::SubstsRef;
4use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, VariantDef};
5use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
6use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
7use rustc_mir_dataflow::{self, move_path_children_matching, Analysis, MoveDataParamEnv};
353b0b11 8use rustc_target::abi::FieldIdx;
a2a8927a
XL
9
10use crate::MirPass;
11
353b0b11 12/// Removes `Drop` terminators whose target is known to be uninitialized at
a2a8927a
XL
13/// that point.
14///
15/// This is redundant with drop elaboration, but we need to do it prior to const-checking, and
5e7ed085 16/// running const-checking after drop elaboration makes it optimization dependent, causing issues
a2a8927a
XL
17/// like [#90770].
18///
19/// [#90770]: https://github.com/rust-lang/rust/issues/90770
20pub struct RemoveUninitDrops;
21
22impl<'tcx> MirPass<'tcx> for RemoveUninitDrops {
23 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
24 let param_env = tcx.param_env(body.source.def_id());
064997fb 25 let Ok((_,move_data)) = MoveData::gather_moves(body, tcx, param_env) else {
a2a8927a
XL
26 // We could continue if there are move errors, but there's not much point since our
27 // init data isn't complete.
28 return;
29 };
30
31 let mdpe = MoveDataParamEnv { move_data, param_env };
32 let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)
33 .into_engine(tcx, body)
34 .pass_name("remove_uninit_drops")
35 .iterate_to_fixpoint()
36 .into_results_cursor(body);
37
38 let mut to_remove = vec![];
f2b60f7d 39 for (bb, block) in body.basic_blocks.iter_enumerated() {
a2a8927a 40 let terminator = block.terminator();
353b0b11 41 let TerminatorKind::Drop { place, .. } = &terminator.kind
a2a8927a
XL
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
353b0b11 67 let TerminatorKind::Drop { target, .. }
a2a8927a
XL
68 = &block.terminator().kind
69 else { unreachable!() };
70
71 // Replace block terminator with `Goto`.
353b0b11 72 block.terminator_mut().kind = TerminatorKind::Goto { target: *target };
a2a8927a
XL
73 }
74 }
75}
76
77fn is_needs_drop_and_init<'tcx>(
78 tcx: TyCtxt<'tcx>,
79 param_env: ParamEnv<'tcx>,
5e7ed085 80 maybe_inits: &ChunkedBitSet<MovePathIndex>,
a2a8927a
XL
81 move_data: &MoveData<'tcx>,
82 ty: Ty<'tcx>,
83 mpi: MovePathIndex,
84) -> bool {
85 // No need to look deeper if the root is definitely uninit or if it has no `Drop` impl.
86 if !maybe_inits.contains(mpi) || !ty.needs_drop(tcx, param_env) {
87 return false;
88 }
89
90 let field_needs_drop_and_init = |(f, f_ty, mpi)| {
91 let child = move_path_children_matching(move_data, mpi, |x| x.is_field_to(f));
92 let Some(mpi) = child else {
064997fb 93 return Ty::needs_drop(f_ty, tcx, param_env);
a2a8927a
XL
94 };
95
96 is_needs_drop_and_init(tcx, param_env, maybe_inits, move_data, f_ty, mpi)
97 };
98
99 // This pass is only needed for const-checking, so it doesn't handle as many cases as
100 // `DropCtxt::open_drop`, since they aren't relevant in a const-context.
101 match ty.kind() {
102 ty::Adt(adt, substs) => {
103 let dont_elaborate = adt.is_union() || adt.is_manually_drop() || adt.has_dtor(tcx);
104 if dont_elaborate {
105 return true;
106 }
107
108 // Look at all our fields, or if we are an enum all our variants and their fields.
109 //
110 // If a field's projection *is not* present in `MoveData`, it has the same
111 // initializedness as its parent (maybe init).
112 //
113 // If its projection *is* present in `MoveData`, then the field may have been moved
114 // from separate from its parent. Recurse.
5e7ed085 115 adt.variants().iter_enumerated().any(|(vid, variant)| {
a2a8927a
XL
116 // Enums have multiple variants, which are discriminated with a `Downcast` projection.
117 // Structs have a single variant, and don't use a `Downcast` projection.
118 let mpi = if adt.is_enum() {
119 let downcast =
120 move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid));
121 let Some(dc_mpi) = downcast else {
122 return variant_needs_drop(tcx, param_env, substs, variant);
123 };
124
125 dc_mpi
126 } else {
127 mpi
128 };
129
130 variant
131 .fields
132 .iter()
133 .enumerate()
353b0b11 134 .map(|(f, field)| (FieldIdx::from_usize(f), field.ty(tcx, substs), mpi))
a2a8927a
XL
135 .any(field_needs_drop_and_init)
136 })
137 }
138
5e7ed085
FG
139 ty::Tuple(fields) => fields
140 .iter()
a2a8927a 141 .enumerate()
353b0b11 142 .map(|(f, f_ty)| (FieldIdx::from_usize(f), f_ty, mpi))
a2a8927a
XL
143 .any(field_needs_drop_and_init),
144
145 _ => true,
146 }
147}
148
149fn variant_needs_drop<'tcx>(
150 tcx: TyCtxt<'tcx>,
151 param_env: ParamEnv<'tcx>,
152 substs: SubstsRef<'tcx>,
153 variant: &VariantDef,
154) -> bool {
155 variant.fields.iter().any(|field| {
156 let f_ty = field.ty(tcx, substs);
157 f_ty.needs_drop(tcx, param_env)
158 })
159}