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