|
| 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