rustc_mir_transform/
unreachable_prop.rs
1use rustc_abi::Size;
6use rustc_data_structures::fx::FxHashSet;
7use rustc_middle::bug;
8use rustc_middle::mir::interpret::Scalar;
9use rustc_middle::mir::*;
10use rustc_middle::ty::{self, TyCtxt};
11
12use crate::patch::MirPatch;
13
14pub(super) struct UnreachablePropagation;
15
16impl crate::MirPass<'_> for UnreachablePropagation {
17 fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
18 sess.mir_opt_level() >= 2
20 }
21
22 fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23 let mut patch = MirPatch::new(body);
24 let mut unreachable_blocks = FxHashSet::default();
25
26 for (bb, bb_data) in traversal::postorder(body) {
27 let terminator = bb_data.terminator();
28 let is_unreachable = match &terminator.kind {
29 TerminatorKind::Unreachable => true,
30 TerminatorKind::Goto { target } if unreachable_blocks.contains(target) => {
33 patch.patch_terminator(bb, TerminatorKind::Unreachable);
34 true
35 }
36 TerminatorKind::SwitchInt { .. } => {
38 remove_successors_from_switch(tcx, bb, &unreachable_blocks, body, &mut patch)
39 }
40 _ => false,
41 };
42 if is_unreachable {
43 unreachable_blocks.insert(bb);
44 }
45 }
46
47 patch.apply(body);
48
49 #[allow(rustc::potential_query_instability)]
52 for bb in unreachable_blocks {
53 body.basic_blocks_mut()[bb].statements.clear();
54 }
55 }
56
57 fn is_required(&self) -> bool {
58 false
59 }
60}
61
62fn remove_successors_from_switch<'tcx>(
64 tcx: TyCtxt<'tcx>,
65 bb: BasicBlock,
66 unreachable_blocks: &FxHashSet<BasicBlock>,
67 body: &Body<'tcx>,
68 patch: &mut MirPatch<'tcx>,
69) -> bool {
70 let terminator = body.basic_blocks[bb].terminator();
71 let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind else { bug!() };
72 let source_info = terminator.source_info;
73 let location = body.terminator_loc(bb);
74
75 let is_unreachable = |bb| unreachable_blocks.contains(&bb);
76
77 let discr_ty = discr.ty(body, tcx);
96 let discr_size = Size::from_bits(match discr_ty.kind() {
97 ty::Uint(uint) => uint.normalize(tcx.sess.target.pointer_width).bit_width().unwrap(),
98 ty::Int(int) => int.normalize(tcx.sess.target.pointer_width).bit_width().unwrap(),
99 ty::Char => 32,
100 ty::Bool => 1,
101 other => bug!("unhandled type: {:?}", other),
102 });
103
104 let mut add_assumption = |binop, value| {
105 let local = patch.new_temp(tcx.types.bool, source_info.span);
106 let value = Operand::Constant(Box::new(ConstOperand {
107 span: source_info.span,
108 user_ty: None,
109 const_: Const::from_scalar(tcx, Scalar::from_uint(value, discr_size), discr_ty),
110 }));
111 let cmp = Rvalue::BinaryOp(binop, Box::new((discr.to_copy(), value)));
112 patch.add_assign(location, local.into(), cmp);
113
114 let assume = NonDivergingIntrinsic::Assume(Operand::Move(local.into()));
115 patch.add_statement(location, StatementKind::Intrinsic(Box::new(assume)));
116 };
117
118 let otherwise = targets.otherwise();
119 let otherwise_unreachable = is_unreachable(otherwise);
120
121 let reachable_iter = targets.iter().filter(|&(value, bb)| {
122 let is_unreachable = is_unreachable(bb);
123 if is_unreachable && !otherwise_unreachable {
125 add_assumption(BinOp::Ne, value);
126 }
127 !is_unreachable
128 });
129
130 let new_targets = SwitchTargets::new(reachable_iter, otherwise);
131
132 let num_targets = new_targets.all_targets().len();
133 let fully_unreachable = num_targets == 1 && otherwise_unreachable;
134
135 let terminator = match (num_targets, otherwise_unreachable) {
136 (1, true) => TerminatorKind::Unreachable,
138 (1, false) => TerminatorKind::Goto { target: otherwise },
139 (2, true) => {
140 let (value, target) = new_targets.iter().next().unwrap();
142 add_assumption(BinOp::Eq, value);
143 TerminatorKind::Goto { target }
144 }
145 _ if num_targets == targets.all_targets().len() => {
146 return false;
148 }
149 _ => TerminatorKind::SwitchInt { discr: discr.clone(), targets: new_targets },
150 };
151
152 patch.patch_terminator(bb, terminator);
153 fully_unreachable
154}