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 and initialize it once for all return paths.
212    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(tcx.types.unit, source_info);
213    body.basic_blocks_mut()[START_BLOCK].statements.push(Statement::new(
214        source_info,
215        StatementKind::Assign(Box::new((
216            Place::return_place(),
217            Rvalue::Use(
218                Operand::zero_sized_constant(tcx.types.unit, source_info.span),
219                WithRetag::Yes,
220            ),
221        ))),
222    ));
223
224    make_coroutine_state_argument_indirect(tcx, &mut body);
225
226    // Make sure we remove dead blocks to remove
227    // unrelated code from the resume part of the function
228    simplify::remove_dead_blocks(&mut body);
229
230    // Run derefer to fix Derefs that are not in the first place
231    deref_finder(tcx, &mut body, false);
232
233    // Update the body's def to become the drop glue.
234    let coroutine_instance = body.source.instance;
235    let drop_glue = tcx.require_lang_item(LangItem::DropGlue, body.span);
236    let drop_instance = InstanceKind::Shim(ShimKind::DropGlue(drop_glue, Some(coroutine_ty)));
237
238    // Temporary change MirSource to coroutine's instance so that dump_mir produces more sensible
239    // filename.
240    body.source.instance = coroutine_instance;
241    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop", &body) {
242        dumper.dump_mir(&body);
243    }
244    body.source.instance = drop_instance;
245
246    // Creating a coroutine drop shim happens on `Analysis(PostCleanup) -> Runtime(Initial)`
247    // but the pass manager doesn't update the phase of the coroutine drop shim. Update the
248    // phase of the drop shim so that later on when we run the pass manager on the shim, in
249    // the `mir_shims` query, we don't ICE on the intra-pass validation before we've updated
250    // the phase of the body from analysis.
251    body.phase = MirPhase::Runtime(RuntimePhase::Initial);
252
253    body
254}
255
256// Create async drop shim function to drop coroutine itself
257#[tracing::instrument(level = "trace", skip(tcx, transform, body))]
258pub(super) fn create_coroutine_drop_shim_async<'tcx>(
259    tcx: TyCtxt<'tcx>,
260    transform: &TransformVisitor<'tcx>,
261    body: &Body<'tcx>,
262    drop_clean: BasicBlock,
263    can_unwind: bool,
264) -> Body<'tcx> {
265    let mut body = body.clone();
266    // Take the coroutine info out of the body, since the drop shim is
267    // not a coroutine body itself; it just has its drop built out of it.
268    let _ = body.coroutine.take();
269
270    FixReturnPendingVisitor { tcx }.visit_body(&mut body);
271
272    // Poison the coroutine when it unwinds
273    if can_unwind {
274        generate_poison_block_and_redirect_unwinds_there(transform, &mut body);
275    }
276
277    let source_info = SourceInfo::outermost(body.span);
278
279    let mut cases = create_cases(&mut body, transform, Operation::AsyncDrop);
280
281    cases.insert(0, (CoroutineArgs::UNRESUMED, drop_clean));
282
283    use rustc_middle::mir::AssertKind::ResumedAfterPanic;
284    // Panic when resumed on the returned or poisoned state
285    if can_unwind {
286        cases.insert(
287            1,
288            (
289                CoroutineArgs::POISONED,
290                if tcx.sess.opts.unstable_opts.async_panic {
291                    insert_panic_block(tcx, &mut body, ResumedAfterPanic(transform.coroutine_kind))
292                } else {
293                    insert_poll_pending_block(tcx, &mut body, transform.old_ret_ty)
294                },
295            ),
296        );
297    }
298
299    // RETURNED state also goes to default_block with `return Ready<()>`.
300    // For fully-polled coroutine, async drop has nothing to do.
301    let default_block = insert_poll_ready_block(tcx, &mut body);
302    insert_switch(&mut body, cases, transform, default_block);
303
304    for block in body.basic_blocks_mut() {
305        let kind = &mut block.terminator_mut().kind;
306        if let TerminatorKind::CoroutineDrop = *kind {
307            *kind = TerminatorKind::Return;
308            block.statements.push(return_poll_ready_assign(tcx, source_info));
309        }
310    }
311
312    // Replace the return variable: Poll<RetT> to Poll<()>
313    let poll_adt_ref = tcx.adt_def(tcx.require_lang_item(LangItem::Poll, body.span));
314    let poll_enum = Ty::new_adt(tcx, poll_adt_ref, tcx.mk_args(&[tcx.types.unit.into()]));
315    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(poll_enum, source_info);
316
317    match transform.coroutine_kind {
318        // Iterator::next doesn't accept a pinned argument,
319        // unlike for all other coroutine kinds.
320        CoroutineKind::Desugared(CoroutineDesugaring::Gen, _) => {
321            make_coroutine_state_argument_indirect(tcx, &mut body);
322        }
323
324        _ => {
325            make_coroutine_state_argument_pinned(tcx, &mut body);
326        }
327    }
328
329    // Make sure we remove dead blocks to remove
330    // unrelated code from the resume part of the function
331    simplify::remove_dead_blocks(&mut body);
332
333    pm::run_passes_no_validate(
334        tcx,
335        &mut body,
336        &[&abort_unwinding_calls::AbortUnwindingCalls],
337        None,
338    );
339
340    // Run derefer to fix Derefs that are not in the first place
341    deref_finder(tcx, &mut body, false);
342
343    if transform.coroutine_kind.is_async_desugaring() {
344        transform_async_context(tcx, &mut body);
345    }
346
347    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop_async", &body) {
348        dumper.dump_mir(&body);
349    }
350
351    body
352}
353
354// Create async drop shim proxy function for future_drop_poll
355// It is just { call coroutine_drop(); return Poll::Ready(); }
356pub(super) fn create_coroutine_drop_shim_proxy_async<'tcx>(
357    tcx: TyCtxt<'tcx>,
358    body: &Body<'tcx>,
359    coroutine_kind: CoroutineKind,
360) -> Body<'tcx> {
361    let mut body = body.clone();
362    // Take the coroutine info out of the body, since the drop shim is
363    // not a coroutine body itself; it just has its drop built out of it.
364    let _ = body.coroutine.take();
365    let basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>> = IndexVec::new();
366    body.basic_blocks = BasicBlocks::new(basic_blocks);
367    body.var_debug_info.clear();
368
369    // Keeping return value and args
370    body.local_decls.truncate(1 + body.arg_count);
371
372    let source_info = SourceInfo::outermost(body.span);
373
374    // Replace the return variable: Poll<RetT> to Poll<()>
375    let poll_adt_ref = tcx.adt_def(tcx.require_lang_item(LangItem::Poll, body.span));
376    let poll_enum = Ty::new_adt(tcx, poll_adt_ref, tcx.mk_args(&[tcx.types.unit.into()]));
377    body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(poll_enum, source_info);
378
379    // call coroutine_drop()
380    let call_bb = body.basic_blocks_mut().push(BasicBlockData::maybe_unterminated(None, false));
381
382    // return Poll::Ready()
383    let ret_bb = insert_poll_ready_block(tcx, &mut body);
384
385    let kind = TerminatorKind::Drop {
386        place: Place::from(SELF_ARG),
387        target: ret_bb,
388        unwind: UnwindAction::Continue,
389        replace: false,
390        drop: None,
391    };
392    body.basic_blocks_mut()[call_bb].terminator = Some(Terminator { source_info, kind });
393
394    make_coroutine_state_argument_pinned(tcx, &mut body);
395
396    // Run derefer to fix Derefs that are not in the first place
397    deref_finder(tcx, &mut body, false);
398
399    if coroutine_kind.is_async_desugaring() {
400        transform_async_context(tcx, &mut body);
401    }
402
403    if let Some(dumper) = MirDumper::new(tcx, "coroutine_drop_proxy_async", &body) {
404        dumper.dump_mir(&body);
405    }
406
407    body
408}