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