Skip to main content

rustc_mir_transform/
simplify_comparison_integral.rs

1use std::iter;
2
3use rustc_middle::bug;
4use rustc_middle::mir::interpret::Scalar;
5use rustc_middle::mir::{
6    BasicBlock, BinOp, Body, Operand, Place, Rvalue, Statement, StatementKind, SwitchTargets,
7    TerminatorKind,
8};
9use rustc_middle::ty::{Ty, TyCtxt};
10use tracing::trace;
11
12use crate::ssa::SsaLocals;
13
14/// Pass to convert `if` conditions on integrals into switches on the integral.
15/// For an example, it turns something like
16///
17/// ```ignore (MIR)
18/// _3 = Eq(move _4, const 43i32);
19/// StorageDead(_4);
20/// switchInt(_3) -> [false: bb2, otherwise: bb3];
21/// ```
22///
23/// into:
24///
25/// ```ignore (MIR)
26/// switchInt(_4) -> [43i32: bb3, otherwise: bb2];
27/// ```
28pub(super) struct SimplifyComparisonIntegral;
29
30impl<'tcx> crate::MirPass<'tcx> for SimplifyComparisonIntegral {
31    fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
32        sess.mir_opt_level() > 1
33    }
34
35    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
36        trace!("Running SimplifyComparisonIntegral on {:?}", body.source);
37
38        let typing_env = body.typing_env(tcx);
39        let ssa = SsaLocals::new(tcx, body, typing_env);
40        let helper = OptimizationFinder { body };
41        let opts = helper.find_optimizations(&ssa);
42        let mut storage_deads_to_insert = vec![];
43        let mut storage_deads_to_remove: Vec<(usize, BasicBlock)> = vec![];
44        for opt in opts {
45            trace!("SUCCESS: Applying {:?}", opt);
46            // replace terminator with a switchInt that switches on the integer directly
47            let bbs = &mut body.basic_blocks_mut();
48            let bb = &mut bbs[opt.bb_idx];
49            let new_value = match opt.branch_value_scalar {
50                Scalar::Int(int) => {
51                    let layout = tcx
52                        .layout_of(typing_env.as_query_input(opt.branch_value_ty))
53                        .expect("if we have an evaluated constant we must know the layout");
54                    int.to_bits(layout.size)
55                }
56                Scalar::Ptr(..) => continue,
57            };
58            const FALSE: u128 = 0;
59
60            let mut new_targets = opt.targets;
61            let first_value = new_targets.iter().next().unwrap().0;
62            let first_is_false_target = first_value == FALSE;
63            match opt.op {
64                BinOp::Eq => {
65                    // if the assignment was Eq we want the true case to be first
66                    if first_is_false_target {
67                        new_targets.all_targets_mut().swap(0, 1);
68                    }
69                }
70                BinOp::Ne => {
71                    // if the assignment was Ne we want the false case to be first
72                    if !first_is_false_target {
73                        new_targets.all_targets_mut().swap(0, 1);
74                    }
75                }
76                _ => unreachable!(),
77            }
78
79            // if the integer being compared to a const integral is being moved into the
80            // comparison, e.g `_2 = Eq(move _3, const 'x');`
81            // we want to avoid making a double move later on in the switchInt on _3.
82            // So to avoid `switchInt(move _3) -> ['x': bb2, otherwise: bb1];`,
83            // we convert the move in the comparison statement to a copy.
84
85            // unwrap is safe as we know this statement is an assign
86            let (_, rhs) = bb.statements[opt.bin_op_stmt_idx].kind.as_assign_mut().unwrap();
87
88            use Operand::*;
89            match rhs {
90                Rvalue::BinaryOp(_, box (left @ Move(_), Constant(_))) => {
91                    *left = Copy(opt.to_switch_on);
92                }
93                Rvalue::BinaryOp(_, box (Constant(_), right @ Move(_))) => {
94                    *right = Copy(opt.to_switch_on);
95                }
96                _ => (),
97            }
98
99            let terminator = bb.terminator();
100
101            // remove StorageDead (if it exists) being used in the assign of the comparison
102            for (stmt_idx, stmt) in bb.statements.iter().enumerate() {
103                if !matches!(
104                    stmt.kind,
105                    StatementKind::StorageDead(local) if local == opt.to_switch_on.local
106                ) {
107                    continue;
108                }
109                storage_deads_to_remove.push((stmt_idx, opt.bb_idx));
110                // if we have StorageDeads to remove then make sure to insert them at the top of
111                // each target
112                for bb_idx in new_targets.all_targets() {
113                    storage_deads_to_insert.push((
114                        *bb_idx,
115                        Statement::new(
116                            terminator.source_info,
117                            StatementKind::StorageDead(opt.to_switch_on.local),
118                        ),
119                    ));
120                }
121            }
122
123            let [bb_cond, bb_otherwise] = match new_targets.all_targets() {
124                [a, b] => [*a, *b],
125                e => bug!("expected 2 switch targets, got: {:?}", e),
126            };
127
128            let targets = SwitchTargets::new(iter::once((new_value, bb_cond)), bb_otherwise);
129
130            let terminator = bb.terminator_mut();
131            terminator.kind =
132                TerminatorKind::SwitchInt { discr: Operand::Copy(opt.to_switch_on), targets };
133        }
134
135        for (idx, bb_idx) in storage_deads_to_remove {
136            body.basic_blocks_mut()[bb_idx].statements[idx].make_nop(true);
137        }
138
139        for (idx, stmt) in storage_deads_to_insert {
140            body.basic_blocks_mut()[idx].statements.insert(0, stmt);
141        }
142    }
143
144    fn is_required(&self) -> bool {
145        false
146    }
147}
148
149struct OptimizationFinder<'a, 'tcx> {
150    body: &'a Body<'tcx>,
151}
152
153impl<'tcx> OptimizationFinder<'_, 'tcx> {
154    fn find_optimizations(&self, ssa: &SsaLocals) -> Vec<OptimizationInfo<'tcx>> {
155        self.body
156            .basic_blocks
157            .iter_enumerated()
158            .filter_map(|(bb_idx, bb)| {
159                // find switch
160                let (discr, targets) = bb.terminator().kind.as_switch()?;
161                let place_switched_on = discr.place()?;
162                // Make sure that the place is not modified.
163                if !ssa.is_ssa(place_switched_on.local) || !place_switched_on.is_stable_offset() {
164                    return None;
165                }
166
167                // find the statement that assigns the place being switched on
168                bb.statements.iter().enumerate().rev().find_map(|(stmt_idx, stmt)| {
169                    match &stmt.kind {
170                        rustc_middle::mir::StatementKind::Assign(box (lhs, rhs))
171                            if *lhs == place_switched_on =>
172                        {
173                            match rhs {
174                                Rvalue::BinaryOp(
175                                    op @ (BinOp::Eq | BinOp::Ne),
176                                    box (left, right),
177                                ) => {
178                                    let (branch_value_scalar, branch_value_ty, to_switch_on) =
179                                        find_branch_value_info(left, right, ssa)?;
180
181                                    Some(OptimizationInfo {
182                                        bin_op_stmt_idx: stmt_idx,
183                                        bb_idx,
184                                        to_switch_on,
185                                        branch_value_scalar,
186                                        branch_value_ty,
187                                        op: *op,
188                                        targets: targets.clone(),
189                                    })
190                                }
191                                _ => None,
192                            }
193                        }
194                        _ => None,
195                    }
196                })
197            })
198            .collect()
199    }
200}
201
202fn find_branch_value_info<'tcx>(
203    left: &Operand<'tcx>,
204    right: &Operand<'tcx>,
205    ssa: &SsaLocals,
206) -> Option<(Scalar, Ty<'tcx>, Place<'tcx>)> {
207    // check that either left or right is a constant.
208    // if any are, we can use the other to switch on, and the constant as a value in a switch
209    use Operand::*;
210    match (left, right) {
211        (Constant(branch_value), Copy(to_switch_on) | Move(to_switch_on))
212        | (Copy(to_switch_on) | Move(to_switch_on), Constant(branch_value)) => {
213            // Make sure that the place is not modified.
214            if !ssa.is_ssa(to_switch_on.local) || !to_switch_on.is_stable_offset() {
215                return None;
216            }
217            let branch_value_ty = branch_value.const_.ty();
218            // we only want to apply this optimization if we are matching on integrals (and chars),
219            // as it is not possible to switch on floats
220            if !branch_value_ty.is_integral() && !branch_value_ty.is_char() {
221                return None;
222            };
223            let branch_value_scalar = branch_value.const_.try_to_scalar()?;
224            Some((branch_value_scalar, branch_value_ty, *to_switch_on))
225        }
226        _ => None,
227    }
228}
229
230#[derive(Debug)]
231struct OptimizationInfo<'tcx> {
232    /// Basic block to apply the optimization
233    bb_idx: BasicBlock,
234    /// Statement index of Eq/Ne assignment
235    bin_op_stmt_idx: usize,
236    /// Place that needs to be switched on. This place is of type integral
237    to_switch_on: Place<'tcx>,
238    /// Constant to use in switch target value
239    branch_value_scalar: Scalar,
240    /// Type of the constant value
241    branch_value_ty: Ty<'tcx>,
242    /// Either Eq or Ne
243    op: BinOp,
244    /// Current targets used in the switch
245    targets: SwitchTargets,
246}