]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/remove_uninit_drops.rs
Rollup merge of #93898 - GuillaumeGomez:error-code-check, r=Mark-Simulacrum
[rust.git] / compiler / rustc_mir_transform / src / remove_uninit_drops.rs
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<'tcx>(
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<'tcx>(
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 }