]>
Commit | Line | Data |
---|---|---|
5e7ed085 | 1 | use rustc_index::bit_set::ChunkedBitSet; |
353b0b11 | 2 | use rustc_middle::mir::{Body, TerminatorKind}; |
a2a8927a XL |
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}; | |
353b0b11 | 8 | use rustc_target::abi::FieldIdx; |
a2a8927a XL |
9 | |
10 | use 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 | |
20 | pub struct RemoveUninitDrops; | |
21 | ||
22 | impl<'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 | ||
77 | fn 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 | ||
149 | fn 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 | } |