Skip to main content

rustc_mir_transform/
remove_noop_landing_pads.rs

1use rustc_index::bit_set::DenseBitSet;
2use rustc_middle::mir::*;
3use rustc_middle::ty::{self, Instance, TyCtxt};
4use tracing::{debug, instrument};
5
6use crate::PassPolicy;
7use crate::patch::MirPatch;
8
9/// A pass that removes noop landing pads and replaces jumps to them with
10/// `UnwindAction::Continue`. This is important because otherwise LLVM generates
11/// terrible code for these.
12pub(super) struct RemoveNoopLandingPads;
13
14impl<'tcx> crate::MirPass<'tcx> for RemoveNoopLandingPads {
15    fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
16        // FIXME: Should this really run on opt-level 0? Or is the LLVM code so terrible we want this even with
17        // "no" optimizations?
18        PassPolicy::optional(ctx.panic_strategy().unwinds())
19    }
20
21    #[instrument(level = "debug", skip(self, tcx, body))]
22    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23        let def_id = body.source.def_id();
24        debug!(?def_id);
25
26        // Skip the pass if there are no blocks with a resume terminator.
27        let has_resume = body
28            .basic_blocks
29            .iter_enumerated()
30            .any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::UnwindResume));
31        if !has_resume {
32            debug!("no resume block in MIR");
33            return;
34        }
35
36        let nop_landing_pads = find_noop_landing_pads(tcx, body, None);
37
38        if nop_landing_pads.is_empty() {
39            debug!("no nop landing pads in MIR");
40            return;
41        }
42
43        // make sure there's a resume block without any statements
44        let resume_block = {
45            let mut patch = MirPatch::new(body);
46            let resume_block = patch.resume_block();
47            patch.apply(body);
48            resume_block
49        };
50        debug!(?resume_block);
51
52        let basic_blocks = body.basic_blocks.as_mut();
53        for (bb, bbdata) in basic_blocks.iter_enumerated_mut() {
54            debug!("processing {:?}", bb);
55
56            if let Some(unwind) = bbdata.terminator_mut().unwind_mut()
57                && let UnwindAction::Cleanup(unwind_bb) = *unwind
58                && nop_landing_pads.contains(unwind_bb)
59            {
60                debug!("    removing noop landing pad");
61                *unwind = UnwindAction::Continue;
62            }
63
64            bbdata.terminator_mut().successors_mut(|target| {
65                if *target != resume_block && nop_landing_pads.contains(*target) {
66                    debug!("    folding noop jump to {:?} to resume block", target);
67                    *target = resume_block;
68                }
69            });
70        }
71    }
72}
73
74impl RemoveNoopLandingPads {
75    fn is_nop_landing_pad<'tcx>(
76        &self,
77        tcx: TyCtxt<'tcx>,
78        bbdata: &BasicBlockData<'tcx>,
79        body: &Body<'tcx>,
80        nop_landing_pads: &DenseBitSet<BasicBlock>,
81        // Extra post-monomorphization info that allows more cases to be identified.
82        extra: Option<(Instance<'tcx>, ty::TypingEnv<'tcx>)>,
83    ) -> bool {
84        for stmt in &bbdata.statements {
85            match &stmt.kind {
86                StatementKind::FakeRead(..)
87                | StatementKind::StorageLive(_)
88                | StatementKind::StorageDead(_)
89                | StatementKind::PlaceMention(..)
90                | StatementKind::AscribeUserType(..)
91                | StatementKind::Coverage(..)
92                | StatementKind::ConstEvalCounter
93                | StatementKind::BackwardIncompatibleDropHint { .. }
94                | StatementKind::Nop => {
95                    // These are all noops in a landing pad
96                }
97
98                StatementKind::Assign((place, Rvalue::Use(..) | Rvalue::Discriminant(_))) => {
99                    if place.as_local().is_some() {
100                        // Writing to a local (e.g., a drop flag) does not
101                        // turn a landing pad to a non-nop
102                    } else {
103                        return false;
104                    }
105                }
106
107                StatementKind::Assign { .. }
108                | StatementKind::SetDiscriminant { .. }
109                | StatementKind::Intrinsic(..) => {
110                    return false;
111                }
112            }
113        }
114
115        let terminator = bbdata.terminator();
116        match terminator.kind {
117            TerminatorKind::Goto { .. }
118            | TerminatorKind::UnwindResume
119            | TerminatorKind::SwitchInt { .. }
120            | TerminatorKind::FalseEdge { .. }
121            | TerminatorKind::FalseUnwind { .. } => {
122                terminator.successors().all(|succ| nop_landing_pads.contains(succ))
123            }
124            TerminatorKind::Drop { place, .. } => {
125                if let Some((instance, typing_env)) = extra {
126                    let ty = place.ty(body, tcx).ty;
127                    debug!("monomorphize: instance={instance:?}");
128                    let ty = instance.instantiate_mir_and_normalize_erasing_regions(
129                        tcx,
130                        typing_env,
131                        ty::EarlyBinder::bind(tcx, ty),
132                    );
133                    let drop_fn = Instance::resolve_drop_glue(tcx, ty);
134                    if let ty::InstanceKind::Shim(ty::ShimKind::DropGlue(_, None)) = drop_fn.def {
135                        // no need to drop anything, if all of our successors are also no-op then we
136                        // can be skipped.
137                        return terminator.successors().all(|succ| nop_landing_pads.contains(succ));
138                    }
139                }
140
141                false
142            }
143            TerminatorKind::CoroutineDrop
144            | TerminatorKind::Yield { .. }
145            | TerminatorKind::Return
146            | TerminatorKind::UnwindTerminate(_)
147            | TerminatorKind::Unreachable
148            | TerminatorKind::Call { .. }
149            | TerminatorKind::TailCall { .. }
150            | TerminatorKind::Assert { .. }
151            | TerminatorKind::InlineAsm { .. } => false,
152        }
153    }
154}
155
156/// Hook impl for [`TyCtxt::find_noop_landing_pads_for_instance`].
157pub(crate) fn find_noop_landing_pads_for_instance<'tcx>(
158    tcx: TyCtxt<'tcx>,
159    body: &Body<'tcx>,
160    instance: Instance<'tcx>,
161    typing_env: ty::TypingEnv<'tcx>,
162) -> DenseBitSet<BasicBlock> {
163    find_noop_landing_pads(tcx, body, Some((instance, typing_env)))
164}
165
166fn find_noop_landing_pads<'tcx>(
167    tcx: TyCtxt<'tcx>,
168    body: &Body<'tcx>,
169    extra: Option<(Instance<'tcx>, ty::TypingEnv<'tcx>)>,
170) -> DenseBitSet<BasicBlock> {
171    let mut nop_landing_pads = DenseBitSet::new_empty(body.basic_blocks.len());
172
173    // This is a post-order traversal, so that if A post-dominates B
174    // then A will be visited before B.
175    let postorder: Vec<_> = traversal::postorder(body).map(|(bb, _)| bb).collect();
176    for bb in postorder {
177        let is_nop_landing_pad = RemoveNoopLandingPads.is_nop_landing_pad(
178            tcx,
179            &body.basic_blocks[bb],
180            body,
181            &nop_landing_pads,
182            extra,
183        );
184        if is_nop_landing_pad {
185            nop_landing_pads.insert(bb);
186        }
187        debug!("    is_nop_landing_pad({:?}) = {}", bb, is_nop_landing_pad);
188    }
189
190    nop_landing_pads
191}