summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_mir_transform/src/unreachable_prop.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_mir_transform/src/unreachable_prop.rs')
-rw-r--r--compiler/rustc_mir_transform/src/unreachable_prop.rs102
1 files changed, 102 insertions, 0 deletions
diff --git a/compiler/rustc_mir_transform/src/unreachable_prop.rs b/compiler/rustc_mir_transform/src/unreachable_prop.rs
new file mode 100644
index 000000000..f916ca362
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/unreachable_prop.rs
@@ -0,0 +1,102 @@
+//! A pass that propagates the unreachable terminator of a block to its predecessors
+//! when all of their successors are unreachable. This is achieved through a
+//! post-order traversal of the blocks.
+
+use crate::simplify;
+use crate::MirPass;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_middle::mir::*;
+use rustc_middle::ty::TyCtxt;
+
+pub struct UnreachablePropagation;
+
+impl MirPass<'_> for UnreachablePropagation {
+ fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
+ // Enable only under -Zmir-opt-level=4 as in some cases (check the deeply-nested-opt
+ // perf benchmark) LLVM may spend quite a lot of time optimizing the generated code.
+ sess.mir_opt_level() >= 4
+ }
+
+ fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
+ let mut unreachable_blocks = FxHashSet::default();
+ let mut replacements = FxHashMap::default();
+
+ for (bb, bb_data) in traversal::postorder(body) {
+ let terminator = bb_data.terminator();
+ if terminator.kind == TerminatorKind::Unreachable {
+ unreachable_blocks.insert(bb);
+ } else {
+ let is_unreachable = |succ: BasicBlock| unreachable_blocks.contains(&succ);
+ let terminator_kind_opt = remove_successors(&terminator.kind, is_unreachable);
+
+ if let Some(terminator_kind) = terminator_kind_opt {
+ if terminator_kind == TerminatorKind::Unreachable {
+ unreachable_blocks.insert(bb);
+ }
+ replacements.insert(bb, terminator_kind);
+ }
+ }
+ }
+
+ let replaced = !replacements.is_empty();
+ for (bb, terminator_kind) in replacements {
+ if !tcx.consider_optimizing(|| {
+ format!("UnreachablePropagation {:?} ", body.source.def_id())
+ }) {
+ break;
+ }
+
+ body.basic_blocks_mut()[bb].terminator_mut().kind = terminator_kind;
+ }
+
+ if replaced {
+ simplify::remove_dead_blocks(tcx, body);
+ }
+ }
+}
+
+fn remove_successors<'tcx, F>(
+ terminator_kind: &TerminatorKind<'tcx>,
+ predicate: F,
+) -> Option<TerminatorKind<'tcx>>
+where
+ F: Fn(BasicBlock) -> bool,
+{
+ let terminator = match *terminator_kind {
+ TerminatorKind::Goto { target } if predicate(target) => TerminatorKind::Unreachable,
+ TerminatorKind::SwitchInt { ref discr, switch_ty, ref targets } => {
+ let otherwise = targets.otherwise();
+
+ let original_targets_len = targets.iter().len() + 1;
+ let (mut values, mut targets): (Vec<_>, Vec<_>) =
+ targets.iter().filter(|(_, bb)| !predicate(*bb)).unzip();
+
+ if !predicate(otherwise) {
+ targets.push(otherwise);
+ } else {
+ values.pop();
+ }
+
+ let retained_targets_len = targets.len();
+
+ if targets.is_empty() {
+ TerminatorKind::Unreachable
+ } else if targets.len() == 1 {
+ TerminatorKind::Goto { target: targets[0] }
+ } else if original_targets_len != retained_targets_len {
+ TerminatorKind::SwitchInt {
+ discr: discr.clone(),
+ switch_ty,
+ targets: SwitchTargets::new(
+ values.iter().copied().zip(targets.iter().copied()),
+ *targets.last().unwrap(),
+ ),
+ }
+ } else {
+ return None;
+ }
+ }
+ _ => return None,
+ };
+ Some(terminator)
+}