1use std::fmt;
2
3use rustc_abi::{FieldIdx, VariantIdx};
4use rustc_index::IndexVec;
5use rustc_index::bit_set::DenseBitSet;
6use rustc_middle::mir::*;
7use rustc_middle::ty::{self, TyCtxt};
8use rustc_mir_dataflow::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
9use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
10use rustc_mir_dataflow::{
11 Analysis, DropFlagState, MoveDataTypingEnv, ResultsCursor, on_all_children_bits,
12 on_lookup_result_bits,
13};
14use rustc_span::Span;
15use tracing::{debug, instrument};
16
17use crate::PassPolicy;
18use crate::elaborate_drop::{DropElaborator, DropFlagMode, DropStyle, Unwind, elaborate_drop};
19use crate::patch::MirPatch;
20
21pub(super) struct ElaborateDrops;
50
51impl<'tcx> crate::MirPass<'tcx> for ElaborateDrops {
52 #[instrument(level = "trace", skip(self, tcx, body))]
53 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
54 debug!("elaborate_drops({:?} @ {:?})", body.source, body.span);
55 let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
58 let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, typing_env));
61 let elaborate_patch = {
62 let env = MoveDataTypingEnv { move_data, typing_env };
63
64 let mut inits = MaybeInitializedPlaces::new(tcx, body, &env.move_data)
65 .exclude_inactive_in_otherwise()
66 .skipping_unreachable_unwind()
67 .iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
68 .into_results_cursor(body);
69 let dead_unwinds = compute_dead_unwinds(body, &mut inits);
70
71 let uninits = MaybeUninitializedPlaces::new(tcx, body, &env.move_data)
72 .mark_inactive_variants_as_uninit()
73 .skipping_unreachable_unwind(dead_unwinds)
74 .iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
75 .into_results_cursor(body);
76
77 let drop_flags = IndexVec::from_elem(None, &env.move_data.move_paths);
78 ElaborateDropsCtxt {
79 tcx,
80 body,
81 env: &env,
82 init_data: InitializationData { inits, uninits },
83 drop_flags,
84 patch: MirPatch::new(body),
85 }
86 .elaborate()
87 };
88 elaborate_patch.apply(body);
89 }
90
91 fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
92 PassPolicy::Required
94 }
95}
96
97#[instrument(level = "trace", skip(body, flow_inits), ret)]
100fn compute_dead_unwinds<'a, 'tcx>(
101 body: &'a Body<'tcx>,
102 flow_inits: &mut ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
103) -> DenseBitSet<BasicBlock> {
104 let mut dead_unwinds = DenseBitSet::new_empty(body.basic_blocks.len());
107 for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
108 let TerminatorKind::Drop { place, unwind: UnwindAction::Cleanup(_), .. } =
109 bb_data.terminator().kind
110 else {
111 continue;
112 };
113
114 flow_inits.seek_before_primary_effect(body.terminator_loc(bb));
115 if flow_inits.analysis().is_unwind_dead(place, flow_inits.get()) {
116 dead_unwinds.insert(bb);
117 }
118 }
119
120 dead_unwinds
121}
122
123struct InitializationData<'a, 'tcx> {
124 inits: ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
125 uninits: ResultsCursor<'a, 'tcx, MaybeUninitializedPlaces<'a, 'tcx>>,
126}
127
128impl InitializationData<'_, '_> {
129 fn seek_before(&mut self, loc: Location) {
130 self.inits.seek_before_primary_effect(loc);
131 self.uninits.seek_before_primary_effect(loc);
132 }
133
134 fn maybe_init_uninit(&self, path: MovePathIndex) -> (bool, bool) {
135 (self.inits.get().contains(path), self.uninits.get().contains(path))
136 }
137}
138
139impl<'a, 'tcx> DropElaborator<'a, 'tcx> for ElaborateDropsCtxt<'a, 'tcx> {
140 type Path = MovePathIndex;
141
142 fn patch_ref(&self) -> &MirPatch<'tcx> {
143 &self.patch
144 }
145
146 fn patch(&mut self) -> &mut MirPatch<'tcx> {
147 &mut self.patch
148 }
149
150 fn body(&self) -> &'a Body<'tcx> {
151 self.body
152 }
153
154 fn tcx(&self) -> TyCtxt<'tcx> {
155 self.tcx
156 }
157
158 fn typing_env(&self) -> ty::TypingEnv<'tcx> {
159 self.env.typing_env
160 }
161
162 fn allow_async_drops(&self) -> bool {
163 true
164 }
165
166 #[instrument(level = "debug", skip(self), ret)]
167 fn drop_style(&self, path: Self::Path, mode: DropFlagMode) -> DropStyle {
168 let ((maybe_init, maybe_uninit), multipart) = match mode {
169 DropFlagMode::Shallow => (self.init_data.maybe_init_uninit(path), false),
170 DropFlagMode::Deep => {
171 let mut some_maybe_init = false;
172 let mut some_maybe_uninit = false;
173 let mut children_count = 0;
174 on_all_children_bits(self.move_data(), path, |child| {
175 let (maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(child);
176 debug!("elaborate_drop: state({:?}) = {:?}", child, (maybe_init, maybe_uninit));
177 some_maybe_init |= maybe_init;
178 some_maybe_uninit |= maybe_uninit;
179 children_count += 1;
180 });
181 ((some_maybe_init, some_maybe_uninit), children_count != 1)
182 }
183 };
184 match (maybe_init, maybe_uninit, multipart) {
185 (false, _, _) => DropStyle::Dead,
186 (true, false, _) => DropStyle::Static,
187 (true, true, false) => DropStyle::Conditional,
188 (true, true, true) => DropStyle::Open,
189 }
190 }
191
192 fn drop_flags_for(&mut self, path: Self::Path, mode: DropFlagMode) -> Vec<Place<'tcx>> {
193 let mut flags = vec![];
194 match mode {
195 DropFlagMode::Shallow => {
196 if let Some(flag) = self.drop_flags[path] {
197 flags.push(flag.into());
198 }
199 }
200 DropFlagMode::Deep => {
201 on_all_children_bits(self.move_data(), path, |child| {
202 if let Some(flag) = self.drop_flags[child] {
203 flags.push(flag.into());
204 }
205 });
206 }
207 }
208 flags
209 }
210
211 fn field_subpath(&self, path: Self::Path, field: FieldIdx) -> Option<Self::Path> {
212 rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
213 ProjectionElem::Field(idx, _) => idx == field,
214 _ => false,
215 })
216 }
217
218 fn array_subpath(&self, path: Self::Path, index: u64, size: u64) -> Option<Self::Path> {
219 rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
220 ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
221 debug_assert!(size == min_length, "min_length should be exact for arrays");
222 assert!(!from_end, "from_end should not be used for array element ConstantIndex");
223 offset == index
224 }
225 _ => false,
226 })
227 }
228
229 fn deref_subpath(&self, path: Self::Path) -> Option<Self::Path> {
230 rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| {
231 e == ProjectionElem::Deref
232 })
233 }
234
235 fn downcast_subpath(&self, path: Self::Path, variant: VariantIdx) -> Option<Self::Path> {
236 rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
237 ProjectionElem::Downcast(_, idx) => idx == variant,
238 _ => false,
239 })
240 }
241
242 fn get_drop_flag(&mut self, path: Self::Path) -> Option<Operand<'tcx>> {
243 self.drop_flag(path).map(Operand::Copy)
244 }
245}
246
247struct ElaborateDropsCtxt<'a, 'tcx> {
248 tcx: TyCtxt<'tcx>,
249 body: &'a Body<'tcx>,
250 env: &'a MoveDataTypingEnv<'tcx>,
251 init_data: InitializationData<'a, 'tcx>,
252 drop_flags: IndexVec<MovePathIndex, Option<Local>>,
253 patch: MirPatch<'tcx>,
254}
255
256impl fmt::Debug for ElaborateDropsCtxt<'_, '_> {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 f.debug_struct("ElaborateDropsCtxt").finish_non_exhaustive()
259 }
260}
261
262impl<'a, 'tcx> ElaborateDropsCtxt<'a, 'tcx> {
263 fn move_data(&self) -> &'a MoveData<'tcx> {
264 &self.env.move_data
265 }
266
267 fn create_drop_flag(&mut self, index: MovePathIndex, span: Span) {
268 let patch = &mut self.patch;
269 debug!("create_drop_flag({:?})", self.body.span);
270 self.drop_flags[index].get_or_insert_with(|| patch.new_temp(self.tcx.types.bool, span));
271 }
272
273 fn drop_flag(&mut self, index: MovePathIndex) -> Option<Place<'tcx>> {
274 self.drop_flags[index].map(Place::from)
275 }
276
277 fn elaborate(mut self) -> MirPatch<'tcx> {
280 self.collect_drop_flags();
281
282 self.elaborate_drops();
283
284 self.drop_flags_on_init();
285 self.drop_flags_for_fn_rets();
286 self.drop_flags_for_args();
287 self.drop_flags_for_locs();
288
289 self.patch
290 }
291
292 fn collect_drop_flags(&mut self) {
293 for (bb, data) in self.body.basic_blocks.iter_enumerated() {
294 let terminator = data.terminator();
295 let TerminatorKind::Drop { ref place, .. } = terminator.kind else { continue };
296
297 let path = self.move_data().rev_lookup.find(place.as_ref());
298 debug!("collect_drop_flags: {:?}, place {:?} ({:?})", bb, place, path);
299
300 match path {
301 LookupResult::Exact(path) => {
302 self.init_data.seek_before(self.body.terminator_loc(bb));
303 on_all_children_bits(self.move_data(), path, |child| {
304 let (maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(child);
305 debug!(
306 "collect_drop_flags: collecting {:?} from {:?}@{:?} - {:?}",
307 child,
308 place,
309 path,
310 (maybe_init, maybe_uninit)
311 );
312 if maybe_init && maybe_uninit {
313 self.create_drop_flag(child, terminator.source_info.span)
314 }
315 });
316 }
317 LookupResult::Parent(None) => {}
318 LookupResult::Parent(Some(parent)) => {
319 if self.body.local_decls[place.local].is_deref_temp() {
320 continue;
321 }
322
323 self.init_data.seek_before(self.body.terminator_loc(bb));
324 let (_maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(parent);
325 if maybe_uninit {
326 self.tcx.dcx().span_delayed_bug(
327 terminator.source_info.span,
328 format!(
329 "drop of untracked, uninitialized value {bb:?}, place {place:?} ({path:?})"
330 ),
331 );
332 }
333 }
334 };
335 }
336 }
337
338 fn elaborate_drops(&mut self) {
339 for (bb, data) in self.body.basic_blocks.iter_enumerated() {
341 let terminator = data.terminator();
342 let TerminatorKind::Drop { place, target, unwind, replace, drop } = terminator.kind
343 else {
344 continue;
345 };
346
347 if !place
351 .ty(&self.body.local_decls, self.tcx)
352 .ty
353 .needs_drop(self.tcx, self.typing_env())
354 {
355 self.patch.patch_terminator(bb, TerminatorKind::Goto { target });
356 continue;
357 }
358
359 let path = self.move_data().rev_lookup.find(place.as_ref());
360 match path {
361 LookupResult::Exact(path) => {
362 let unwind = match unwind {
363 _ if data.is_cleanup => Unwind::InCleanup,
364 UnwindAction::Cleanup(cleanup) => Unwind::To(cleanup),
365 UnwindAction::Continue => Unwind::To(self.patch.resume_block()),
366 UnwindAction::Unreachable => {
367 Unwind::To(self.patch.unreachable_cleanup_block())
368 }
369 UnwindAction::Terminate(reason) => {
370 debug_assert_ne!(
371 reason,
372 UnwindTerminateReason::InCleanup,
373 "we are not in a cleanup block, InCleanup reason should be impossible"
374 );
375 Unwind::To(self.patch.terminate_block(reason))
376 }
377 };
378 self.init_data.seek_before(self.body.terminator_loc(bb));
379 elaborate_drop(
380 self,
381 terminator.source_info,
382 place,
383 path,
384 target,
385 unwind,
386 bb,
387 drop,
388 )
389 }
390 LookupResult::Parent(None) => {}
391 LookupResult::Parent(Some(_)) => {
392 if !replace {
393 self.tcx.dcx().span_bug(
394 terminator.source_info.span,
395 format!("drop of untracked value {bb:?}"),
396 );
397 }
398 assert!(!data.is_cleanup);
402 }
403 }
404 }
405 }
406
407 fn constant_bool(&self, span: Span, val: bool) -> Rvalue<'tcx> {
408 Rvalue::Use(
409 Operand::Constant(Box::new(ConstOperand {
410 span,
411 user_ty: None,
412 const_: Const::from_bool(self.tcx, val),
413 })),
414 WithRetag::Yes,
415 )
416 }
417
418 fn set_drop_flag(&mut self, loc: Location, path: MovePathIndex, val: DropFlagState) {
419 if let Some(flag) = self.drop_flags[path] {
420 let span = self.patch.source_info_for_location(self.body, loc).span;
421 let val = self.constant_bool(span, val.value());
422 self.patch.add_assign(loc, Place::from(flag), val);
423 }
424 }
425
426 fn drop_flags_on_init(&mut self) {
427 let loc = Location::START;
428 let span = self.patch.source_info_for_location(self.body, loc).span;
429 let false_ = self.constant_bool(span, false);
430 for flag in self.drop_flags.iter().flatten() {
431 self.patch.add_assign(loc, Place::from(*flag), false_.clone());
432 }
433 }
434
435 fn drop_flags_for_fn_rets(&mut self) {
436 for (bb, data) in self.body.basic_blocks.iter_enumerated() {
437 if let TerminatorKind::Call {
438 destination,
439 target: Some(tgt),
440 unwind: UnwindAction::Cleanup(_),
441 ..
442 } = data.terminator().kind
443 {
444 assert!(!self.patch.is_term_patched(bb));
445
446 let loc = Location { block: tgt, statement_index: 0 };
447 let path = self.move_data().rev_lookup.find(destination.as_ref());
448 on_lookup_result_bits(self.move_data(), path, |child| {
449 self.set_drop_flag(loc, child, DropFlagState::Present)
450 });
451 }
452 }
453 }
454
455 fn drop_flags_for_args(&mut self) {
456 let loc = Location::START;
457 rustc_mir_dataflow::drop_flag_effects_for_function_entry(
458 self.body,
459 &self.env.move_data,
460 |path, ds| {
461 self.set_drop_flag(loc, path, ds);
462 },
463 )
464 }
465
466 fn drop_flags_for_locs(&mut self) {
467 for (bb, data) in self.body.basic_blocks.iter_enumerated() {
474 debug!("drop_flags_for_locs({:?})", data);
475 for i in 0..(data.statements.len() + 1) {
476 debug!("drop_flag_for_locs: stmt {}", i);
477 if i == data.statements.len() {
478 match data.terminator().kind {
479 TerminatorKind::Drop { .. } => {
480 continue;
482 }
483 TerminatorKind::UnwindResume => {
484 }
488 _ => {
489 assert!(!self.patch.is_term_patched(bb));
490 }
491 }
492 }
493 let loc = Location { block: bb, statement_index: i };
494 rustc_mir_dataflow::drop_flag_effects_for_location(
495 self.body,
496 &self.env.move_data,
497 loc,
498 |path, ds| self.set_drop_flag(loc, path, ds),
499 )
500 }
501
502 if let TerminatorKind::Call {
506 destination,
507 target: Some(_),
508 unwind:
509 UnwindAction::Continue | UnwindAction::Unreachable | UnwindAction::Terminate(_),
510 ..
511 } = data.terminator().kind
512 {
513 assert!(!self.patch.is_term_patched(bb));
514
515 let loc = Location { block: bb, statement_index: data.statements.len() };
516 let path = self.move_data().rev_lookup.find(destination.as_ref());
517 on_lookup_result_bits(self.move_data(), path, |child| {
518 self.set_drop_flag(loc, child, DropFlagState::Present)
519 });
520 }
521 }
522 }
523}