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