rustc_mir_transform/
simplify_comparison_integral.rs1use std::iter;
2
3use rustc_middle::bug;
4use rustc_middle::mir::interpret::Scalar;
5use rustc_middle::mir::{
6 BasicBlock, BinOp, Body, Operand, Place, Rvalue, StatementKind, SwitchTargets, TerminatorKind,
7};
8use rustc_middle::ty::{Ty, TyCtxt};
9use tracing::trace;
10
11use crate::PassPolicy;
12use crate::ssa::SsaLocals;
13
14pub(super) struct SimplifyComparisonIntegral;
28
29impl<'tcx> crate::MirPass<'tcx> for SimplifyComparisonIntegral {
30 fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
31 PassPolicy::optimization(sess.mir_opt_level() > 1)
32 }
33
34 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
35 trace!("Running SimplifyComparisonIntegral on {:?}", body.source);
36
37 let typing_env = body.typing_env(tcx);
38 let ssa = SsaLocals::new(tcx, body, typing_env);
39 let helper = OptimizationFinder { body };
40 let opts = helper.find_optimizations(&ssa);
41 for opt in opts {
42 trace!("SUCCESS: Applying {:?}", opt);
43 let bbs = &mut body.basic_blocks_mut();
45 let bb = &mut bbs[opt.bb_idx];
46 let new_value = match opt.branch_value_scalar {
47 Scalar::Int(int) => {
48 let layout = tcx
49 .layout_of(typing_env.as_query_input(opt.branch_value_ty))
50 .expect("if we have an evaluated constant we must know the layout");
51 int.to_bits(layout.size)
52 }
53 Scalar::Ptr(..) => continue,
54 };
55 const FALSE: u128 = 0;
56
57 let mut new_targets = opt.targets;
58 let first_value = new_targets.iter().next().unwrap().0;
59 let first_is_false_target = first_value == FALSE;
60 match opt.op {
61 BinOp::Eq => {
62 if first_is_false_target {
64 new_targets.all_targets_mut().swap(0, 1);
65 }
66 }
67 BinOp::Ne => {
68 if !first_is_false_target {
70 new_targets.all_targets_mut().swap(0, 1);
71 }
72 }
73 _ => unreachable!(),
74 }
75
76 let (_, rhs) = bb.statements[opt.bin_op_stmt_idx].kind.as_assign_mut().unwrap();
84
85 use Operand::*;
86 match rhs {
87 Rvalue::BinaryOp(_, (left @ Move(_), Constant(_))) => {
88 *left = Copy(opt.to_switch_on);
89 }
90 Rvalue::BinaryOp(_, (Constant(_), right @ Move(_))) => {
91 *right = Copy(opt.to_switch_on);
92 }
93 _ => (),
94 }
95
96 let [bb_cond, bb_otherwise] = match new_targets.all_targets() {
97 [a, b] => [*a, *b],
98 e => bug!("expected 2 switch targets, got: {:?}", e),
99 };
100
101 let targets = SwitchTargets::new(iter::once((new_value, bb_cond)), bb_otherwise);
102
103 let terminator = bb.terminator_mut();
104 terminator.kind =
105 TerminatorKind::SwitchInt { discr: Operand::Copy(opt.to_switch_on), targets };
106 }
107 }
108}
109
110struct OptimizationFinder<'a, 'tcx> {
111 body: &'a Body<'tcx>,
112}
113
114impl<'tcx> OptimizationFinder<'_, 'tcx> {
115 fn find_optimizations(&self, ssa: &SsaLocals) -> Vec<OptimizationInfo<'tcx>> {
116 self.body
117 .basic_blocks
118 .iter_enumerated()
119 .filter_map(|(bb_idx, bb)| {
120 let (discr, targets) = bb.terminator().kind.as_switch()?;
122 let place_switched_on = discr.place()?;
123 if !ssa.is_ssa(place_switched_on.local) || !place_switched_on.is_stable_offset() {
125 return None;
126 }
127
128 bb.statements.iter().enumerate().rev().find_map(|(stmt_idx, stmt)| {
130 match &stmt.kind {
131 rustc_middle::mir::StatementKind::Assign((lhs, rhs))
132 if *lhs == place_switched_on =>
133 {
134 match rhs {
135 Rvalue::BinaryOp(op @ (BinOp::Eq | BinOp::Ne), (left, right)) => {
136 let (branch_value_scalar, branch_value_ty, to_switch_on) =
137 find_branch_value_info(left, right, ssa)?;
138
139 if bb.statements[stmt_idx + 1..].iter().any(|stmt| {
143 matches!(
144 stmt.kind,
145 StatementKind::StorageLive(local)
146 | StatementKind::StorageDead(local)
147 if local == to_switch_on.local
148 )
149 }) {
150 return None;
151 }
152
153 Some(OptimizationInfo {
154 bin_op_stmt_idx: stmt_idx,
155 bb_idx,
156 to_switch_on,
157 branch_value_scalar,
158 branch_value_ty,
159 op: *op,
160 targets: targets.clone(),
161 })
162 }
163 _ => None,
164 }
165 }
166 _ => None,
167 }
168 })
169 })
170 .collect()
171 }
172}
173
174fn find_branch_value_info<'tcx>(
175 left: &Operand<'tcx>,
176 right: &Operand<'tcx>,
177 ssa: &SsaLocals,
178) -> Option<(Scalar, Ty<'tcx>, Place<'tcx>)> {
179 use Operand::*;
182 match (left, right) {
183 (Constant(branch_value), Copy(to_switch_on) | Move(to_switch_on))
184 | (Copy(to_switch_on) | Move(to_switch_on), Constant(branch_value)) => {
185 if !ssa.is_ssa(to_switch_on.local) || !to_switch_on.is_stable_offset() {
187 return None;
188 }
189 let branch_value_ty = branch_value.const_.ty();
190 if !branch_value_ty.is_integral() && !branch_value_ty.is_char() {
193 return None;
194 };
195 let branch_value_scalar = branch_value.const_.try_to_scalar()?;
196 Some((branch_value_scalar, branch_value_ty, *to_switch_on))
197 }
198 _ => None,
199 }
200}
201
202#[derive(Debug)]
203struct OptimizationInfo<'tcx> {
204 bb_idx: BasicBlock,
206 bin_op_stmt_idx: usize,
208 to_switch_on: Place<'tcx>,
210 branch_value_scalar: Scalar,
212 branch_value_ty: Ty<'tcx>,
214 op: BinOp,
216 targets: SwitchTargets,
218}