Skip to main content

rustc_mir_transform/coroutine/
drop.rs

1//! Drops and async drops related logic for coroutine transformation pass
2
3use super::*;
4
5// Fix return Poll<Rv>::Pending statement into Poll<()>::Pending for async drop function
6struct FixReturnPendingVisitor<'tcx> {
7    tcx: TyCtxt<'tcx>,
8}
9
10impl<'tcx> MutVisitor<'tcx> for FixReturnPendingVisitor<'tcx> {
11    fn tcx(&self) -> TyCtxt<'tcx> {
12        self.tcx
13    }
14
15    fn visit_assign(
16        &mut self,
17        place: &mut Place<'tcx>,
18        rvalue: &mut Rvalue<'tcx>,
19        _location: Location,
20    ) {
21        if place.local != RETURN_PLACE {
22            return;
23        }
24
25        // Converting `_0 = Poll::<Rv>::Pending` to `_0 = Poll::<()>::Pending`
26        if let Rvalue::Aggregate(kind, _) = rvalue
27            && let AggregateKind::Adt(_, _, ref mut args, _, _) = **kind
28        {
29            *args = self.tcx.mk_args(&[self.tcx.types.unit.into()]);
30        } else if let Rvalue::Use(Operand::Constant(constant), _) = rvalue {
31            if let Some(async_gen_pending_def_id) = self.tcx.lang_items().async_gen_pending()
32                && let Const::Unevaluated(unevaluated, _) = constant.const_
33                && unevaluated.def == async_gen_pending_def_id
34            {
35                let poll_def_id = self.tcx.lang_items().poll().unwrap();
36                *rvalue = Rvalue::Aggregate(
37                    Box::new(AggregateKind::Adt(
38                        poll_def_id,
39                        VariantIdx::from_u32(1),
40                        self.tcx.mk_args(&[self.tcx.types.unit.into()]),
41                        None,
42                        None,
43                    )),
44                    IndexVec::new(),
45                );
46            }
47        }
48    }
49}
50
51/// Drop elaboration has transformed all async drops into `yield` loops.
52/// The resulting coroutine needs `async drop` if it yields on a path
53/// reachable through 'drop' targets of a Yield terminator.
54#[tracing::instrument(level = "trace", skip(body), ret)]
55pub(super) fn has_async_drops<'tcx>(body: &mut Body<'tcx>) -> bool {
56    let mut has_async_drops = false;
57
58    let mut dropline: DenseBitSet<BasicBlock> = DenseBitSet::new_empty(body.basic_blocks.len());
59    for (bb, data) in traversal::reverse_postorder(body) {
60        // Cleanup edges are not async drops.
61        if data.is_cleanup {
62            continue;
63        }
64
65        if let TerminatorKind::Yield { drop, .. } = data.terminator().kind {
66            if dropline.contains(bb) {
67                has_async_drops = true
68            }
69            if let Some(v) = drop {
70                dropline.insert(v);
71            }
72        }
73
74        if dropline.contains(bb) {
75            data.terminator().successors().for_each(|v| {
76                dropline.insert(v);
77            });
78        }
79    }
80
81    has_async_drops
82}
83
84#[tracing::instrument(level = "trace", skip(tcx, body))]
85pub(super) fn elaborate_coroutine_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
86    use crate::elaborate_drop::{Unwind, elaborate_drop};
87    use crate::patch::MirPatch;
88    use crate::shim::DropShimElaborator;
89
90    // Note that `elaborate_drops` only drops the upvars of a coroutine, and
91    // this is ok because `open_drop` can only be reached within that own
92    // coroutine's resume function.
93    let typing_env = body.typing_env(tcx);
94
95    let mut elaborator = DropShimElaborator {
96        body,
97        patch: MirPatch::new(body),
98        tcx,
99        typing_env,
100        // FIXME(async_drop): Drops, produced by insert_clean_drop + elaborate_coroutine_drops, are
101        // currently sync only. To allow async for them, flip this flag and fix the related
102        // problems.
103        produce_async_drops: false,
104    };
105
106    for (block, block_data) in body.basic_blocks.iter_enumerated() {
107        let (target, unwind, source_info, dropline) = match block_data.terminator() {
108            Terminator {
109                source_info,
110                kind: TerminatorKind::Drop { place, target, unwind, replace: _, drop },
111                ..
112            } => {
113                if let Some(local) = place.as_local()
114                    && local == SELF_ARG
115                {
116                    (target, unwind, source_info, *drop)
117                } else {
118                    continue;
119                }
120            }
121            _ => continue,
122        };
123        let unwind = if block_data.is_cleanup {
124            Unwind::InCleanup
125        } else {
126            Unwind::To(match *unwind {
127                UnwindAction::Cleanup(tgt) => tgt,
128                UnwindAction::Continue => elaborator.patch.resume_block(),
129                UnwindAction::Unreachable => elaborator.patch.unreachable_cleanup_block(),
130                UnwindAction::Terminate(reason) => elaborator.patch.terminate_block(reason),
131            })
132        };
133        elaborate_drop(
134            &mut elaborator,
135            *source_info,
136            Place::from(SELF_ARG),
137            (),
138            *target,
139            unwind,
140            block,
141            dropline,
142        );
143    }
144    elaborator.patch.apply(body);
145}
146
147#[tracing::instrument(level = "trace", skip(tcx, body), ret)]
148pub(super) fn insert_clean_drop<'tcx>(
149    tcx: TyCtxt<'tcx>,
150    body: &mut Body<'tcx>,
151    has_async_drops: bool,
152) -> BasicBlock {
153    let return_block = if has_async_drops {
154        insert_poll_ready_block(tcx, body)
155    } else {
156        insert_term_block(body, TerminatorKind::Return)
157    };
158
159    // FIXME: When move insert_clean_drop + elaborate_coroutine_drops before async drops expand,
160    // also set dropline here:
161    // let dropline = if has_async_drops { Some(return_block) } else { None };
162    let dropline = None;
163
164    let term = TerminatorKind::Drop {
165        place: Place::from(SELF_ARG),
166        target: return_block,
167        unwind: UnwindAction::Continue,
168        replace: false,
169        drop: dropline,
170    };
171
172    // Create a block to destroy an unresumed coroutines. This can only destroy upvars.
173    insert_term_block(body, term)
174}
175
176#[tracing::instrument(level = "trace", skip(tcx, transform, body))]
177pub(super) fn create_coroutine_drop_shim<'tcx>(
178    tcx: TyCtxt<'tcx>,
179    transform: &TransformVisitor<'tcx>,
180    coroutine_ty: Ty<'tcx>,
181    body: &Body<'tcx>,
182    drop_clean: BasicBlock,
183) -> Body<'tcx> {
184    let mut body = body.clone();
185    // Take the coroutine info out of the body, since the drop shim is
186    // not a coroutine body itself; it just has its drop built out of it.
187    let _ = body.coroutine.take();
188    // Make sure the resume argument is not included here, since we're
189    // building a body for `drop_glue`.
190    body.arg_count = 1;
191
192    let source_info = SourceInfo::outermost(body.span);
193
194    let mut cases = create_cases(&mut body, transform, Operation::Drop);
195
196    cases.insert(0, (CoroutineArgs::UNRESUMED, drop_clean));
197
198    // The returned state and the poisoned state fall through to the default
199    // case which is just to return
200
201    let default_block = insert_term_block(&mut body, TerminatorKind::Return);
202    insert_switch(&mut body, cases, transform, default_block);
203
204    for block in body.basic_blocks_mut() {
205        let kind = &mut block.terminator_mut().kind;
206        if let TerminatorKind::CoroutineDrop = *kind {
207            *kind = TerminatorKind::Return;
208        }
209    }
210
211    // Replace the return variable
212    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(tcx.types.unit, source_info);
213
214    make_coroutine_state_argument_indirect(tcx, &mut body);
215
216    // Make sure we remove dead blocks to remove
217    // unrelated code from the resume part of the function
218    simplify::remove_dead_blocks(&mut body);
219
220    // Run derefer to fix Derefs that are not in the first place
221    deref_finder(tcx, &mut body, false);
222
223    // Update the body's def to become the drop glue.
224    let coroutine_instance = body.source.instance;
225    let drop_glue = tcx.require_lang_item(LangItem::DropGlue, body.span);
226    let drop_instance = InstanceKind::Shim(ShimKind::DropGlue(drop_glue, Some(coroutine_ty)));
227
228    // Temporary change MirSource to coroutine's instance so that dump_mir produces more sensible
229    // filename.
230    body.source.instance = coroutine_instance;
231    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop", &body) {
232        dumper.dump_mir(&body);
233    }
234    body.source.instance = drop_instance;
235
236    // Creating a coroutine drop shim happens on `Analysis(PostCleanup) -> Runtime(Initial)`
237    // but the pass manager doesn't update the phase of the coroutine drop shim. Update the
238    // phase of the drop shim so that later on when we run the pass manager on the shim, in
239    // the `mir_shims` query, we don't ICE on the intra-pass validation before we've updated
240    // the phase of the body from analysis.
241    body.phase = MirPhase::Runtime(RuntimePhase::Initial);
242
243    body
244}
245
246// Create async drop shim function to drop coroutine itself
247#[tracing::instrument(level = "trace", skip(tcx, transform, body))]
248pub(super) fn create_coroutine_drop_shim_async<'tcx>(
249    tcx: TyCtxt<'tcx>,
250    transform: &TransformVisitor<'tcx>,
251    body: &Body<'tcx>,
252    drop_clean: BasicBlock,
253    can_unwind: bool,
254) -> Body<'tcx> {
255    let mut body = body.clone();
256    // Take the coroutine info out of the body, since the drop shim is
257    // not a coroutine body itself; it just has its drop built out of it.
258    let _ = body.coroutine.take();
259
260    FixReturnPendingVisitor { tcx }.visit_body(&mut body);
261
262    // Poison the coroutine when it unwinds
263    if can_unwind {
264        generate_poison_block_and_redirect_unwinds_there(transform, &mut body);
265    }
266
267    let source_info = SourceInfo::outermost(body.span);
268
269    let mut cases = create_cases(&mut body, transform, Operation::AsyncDrop);
270
271    cases.insert(0, (CoroutineArgs::UNRESUMED, drop_clean));
272
273    use rustc_middle::mir::AssertKind::ResumedAfterPanic;
274    // Panic when resumed on the returned or poisoned state
275    if can_unwind {
276        cases.insert(
277            1,
278            (
279                CoroutineArgs::POISONED,
280                insert_panic_block(tcx, &mut body, ResumedAfterPanic(transform.coroutine_kind)),
281            ),
282        );
283    }
284
285    // RETURNED state also goes to default_block with `return Ready<()>`.
286    // For fully-polled coroutine, async drop has nothing to do.
287    let default_block = insert_poll_ready_block(tcx, &mut body);
288    insert_switch(&mut body, cases, transform, default_block);
289
290    for block in body.basic_blocks_mut() {
291        let kind = &mut block.terminator_mut().kind;
292        if let TerminatorKind::CoroutineDrop = *kind {
293            *kind = TerminatorKind::Return;
294            block.statements.push(return_poll_ready_assign(tcx, source_info));
295        }
296    }
297
298    // Replace the return variable: Poll<RetT> to Poll<()>
299    let poll_adt_ref = tcx.adt_def(tcx.require_lang_item(LangItem::Poll, body.span));
300    let poll_enum = Ty::new_adt(tcx, poll_adt_ref, tcx.mk_args(&[tcx.types.unit.into()]));
301    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(poll_enum, source_info);
302
303    match transform.coroutine_kind {
304        // Iterator::next doesn't accept a pinned argument,
305        // unlike for all other coroutine kinds.
306        CoroutineKind::Desugared(CoroutineDesugaring::Gen, _) => {
307            make_coroutine_state_argument_indirect(tcx, &mut body);
308        }
309
310        _ => {
311            make_coroutine_state_argument_pinned(tcx, &mut body);
312        }
313    }
314
315    // Make sure we remove dead blocks to remove
316    // unrelated code from the resume part of the function
317    simplify::remove_dead_blocks(&mut body);
318
319    pm::run_passes_no_validate(
320        tcx,
321        &mut body,
322        &[&abort_unwinding_calls::AbortUnwindingCalls],
323        None,
324    );
325
326    // Run derefer to fix Derefs that are not in the first place
327    deref_finder(tcx, &mut body, false);
328
329    if transform.coroutine_kind.is_async_desugaring() {
330        transform_async_context(tcx, &mut body);
331    }
332
333    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop_async", &body) {
334        dumper.dump_mir(&body);
335    }
336
337    body
338}
339
340// Create async drop shim proxy function for future_drop_poll
341// It is just { call coroutine_drop(); return Poll::Ready(); }
342pub(super) fn create_coroutine_drop_shim_proxy_async<'tcx>(
343    tcx: TyCtxt<'tcx>,
344    body: &Body<'tcx>,
345    coroutine_kind: CoroutineKind,
346) -> Body<'tcx> {
347    let mut body = body.clone();
348    // Take the coroutine info out of the body, since the drop shim is
349    // not a coroutine body itself; it just has its drop built out of it.
350    let _ = body.coroutine.take();
351    let basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>> = IndexVec::new();
352    body.basic_blocks = BasicBlocks::new(basic_blocks);
353    body.var_debug_info.clear();
354
355    // Keeping return value and args
356    body.local_decls.truncate(1 + body.arg_count);
357
358    let source_info = SourceInfo::outermost(body.span);
359
360    // Replace the return variable: Poll<RetT> to Poll<()>
361    let poll_adt_ref = tcx.adt_def(tcx.require_lang_item(LangItem::Poll, body.span));
362    let poll_enum = Ty::new_adt(tcx, poll_adt_ref, tcx.mk_args(&[tcx.types.unit.into()]));
363    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(poll_enum, source_info);
364
365    // call coroutine_drop()
366    let call_bb = body.basic_blocks_mut().push(BasicBlockData::new(None, false));
367
368    // return Poll::Ready()
369    let ret_bb = insert_poll_ready_block(tcx, &mut body);
370
371    let kind = TerminatorKind::Drop {
372        place: Place::from(SELF_ARG),
373        target: ret_bb,
374        unwind: UnwindAction::Continue,
375        replace: false,
376        drop: None,
377    };
378    body.basic_blocks_mut()[call_bb].terminator =
379        Some(Terminator { source_info, kind, attributes: ThinVec::new() });
380
381    // Run derefer to fix Derefs that are not in the first place
382    deref_finder(tcx, &mut body, false);
383
384    if coroutine_kind.is_async_desugaring() {
385        transform_async_context(tcx, &mut body);
386    }
387
388    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop_proxy_async", &body) {
389        dumper.dump_mir(&body);
390    }
391
392    body
393}