1use rustc_abi::Integer;
2use rustc_const_eval::const_eval::mk_eval_cx_for_const_val;
3use rustc_middle::mir::*;
4use rustc_middle::ty::layout::{IntegerExt, TyAndLayout};
5use rustc_middle::ty::util::Discr;
6use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
7
8use super::simplify::simplify_cfg;
9use crate::PassPolicy;
10use crate::patch::MirPatch;
11use crate::unreachable_prop::remove_successors_from_switch;
12
13pub(super) struct MatchBranchSimplification;
15
16impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification {
17 fn policy(&self, sess: &rustc_session::Session) -> PassPolicy {
18 PassPolicy::optimization(sess.mir_opt_level() >= 2)
20 }
21
22 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23 let typing_env = body.typing_env(tcx);
24 let mut changed = false;
25 for bb in body.basic_blocks.indices() {
26 if !candidate_match(body, bb) {
27 continue;
28 };
29 changed |= simplify_match(tcx, typing_env, body, bb)
30 }
31
32 if changed {
33 simplify_cfg(tcx, body);
34 }
35 }
36}
37
38struct SimplifyMatch<'tcx, 'a> {
39 tcx: TyCtxt<'tcx>,
40 typing_env: ty::TypingEnv<'tcx>,
41 patch: MirPatch<'tcx>,
42 body: &'a Body<'tcx>,
43 switch_bb: BasicBlock,
44 discr: &'a Operand<'tcx>,
45 discr_local: Option<Local>,
46 discr_ty: Ty<'tcx>,
47}
48
49impl<'tcx, 'a> SimplifyMatch<'tcx, 'a> {
50 fn discr_local(&mut self) -> Local {
51 *self.discr_local.get_or_insert_with(|| {
52 let source_info = self.body.basic_blocks[self.switch_bb].terminator().source_info;
54 self.patch.new_temp(self.discr_ty, source_info.span)
55 })
56 }
57
58 fn unify_if_equal_const(
60 &self,
61 dest: Place<'tcx>,
62 consts: &[(u128, &ConstOperand<'tcx>)],
63 otherwise: Option<&ConstOperand<'tcx>>,
64 ) -> Option<StatementKind<'tcx>> {
65 let (_, first_const, mut others) = split_first_case(consts, otherwise);
66 let first_scalar_int = first_const.const_.try_eval_scalar_int(self.tcx, self.typing_env)?;
67 if others.all(|const_| {
68 const_.const_.try_eval_scalar_int(self.tcx, self.typing_env) == Some(first_scalar_int)
69 }) {
70 Some(StatementKind::Assign(Box::new((
71 dest,
72 Rvalue::Use(Operand::Constant(Box::new(first_const.clone())), WithRetag::No),
75 ))))
76 } else {
77 None
78 }
79 }
80
81 fn unify_by_eq_op(
113 &mut self,
114 dest: Place<'tcx>,
115 consts: &[(u128, &ConstOperand<'tcx>)],
116 otherwise: Option<&ConstOperand<'tcx>>,
117 ) -> Option<StatementKind<'tcx>> {
118 let (first_case, first_const, mut others) = split_first_case(consts, otherwise);
120 if !first_const.ty().is_bool() {
121 return None;
122 }
123 let first_bool = first_const.const_.try_eval_bool(self.tcx, self.typing_env)?;
124 if others.all(|const_| {
125 const_.const_.try_eval_bool(self.tcx, self.typing_env) == Some(!first_bool)
126 }) {
127 let size =
129 self.tcx.layout_of(self.typing_env.as_query_input(self.discr_ty)).unwrap().size;
130 let const_cmp = Operand::const_from_scalar(
131 self.tcx,
132 self.discr_ty,
133 rustc_const_eval::interpret::Scalar::from_uint(first_case, size),
134 rustc_span::DUMMY_SP,
135 );
136 let op = if first_bool { BinOp::Eq } else { BinOp::Ne };
137 let rval = Rvalue::BinaryOp(
138 op,
139 Box::new((Operand::Copy(Place::from(self.discr_local())), const_cmp)),
140 );
141 Some(StatementKind::Assign(Box::new((dest, rval))))
142 } else {
143 None
144 }
145 }
146
147 fn unify_by_int_to_int(
185 &mut self,
186 dest: Place<'tcx>,
187 consts: &[(u128, &ConstOperand<'tcx>)],
188 ) -> Option<StatementKind<'tcx>> {
189 let (_, first_const) = consts[0];
190 if !first_const.ty().is_integral() {
191 return None;
192 }
193 let discr_layout =
194 self.tcx.layout_of(self.typing_env.as_query_input(self.discr_ty)).unwrap();
195 if consts.iter().all(|&(case, const_)| {
196 let Some(scalar_int) = const_.const_.try_eval_scalar_int(self.tcx, self.typing_env)
197 else {
198 return false;
199 };
200 can_cast(self.tcx, case, discr_layout, const_.ty(), scalar_int)
201 }) {
202 let operand = Operand::Copy(Place::from(self.discr_local()));
203 let rval = if first_const.ty() == self.discr_ty {
204 Rvalue::Use(operand, WithRetag::No)
205 } else {
206 Rvalue::Cast(CastKind::IntToInt, operand, first_const.ty())
207 };
208 Some(StatementKind::Assign(Box::new((dest, rval))))
209 } else {
210 None
211 }
212 }
213
214 fn unify_by_copy(
231 &self,
232 dest: Place<'tcx>,
233 rvals: &[(u128, &Rvalue<'tcx>)],
234 ) -> Option<StatementKind<'tcx>> {
235 let bbs = &self.body.basic_blocks;
236 let &Statement {
240 kind: StatementKind::Assign((discr_place, Rvalue::Discriminant(copy_src_place))),
241 ..
242 } = bbs[self.switch_bb].statements.last()?
243 else {
244 return None;
245 };
246 if self.discr.place() != Some(discr_place) {
247 return None;
248 }
249 let src_ty = copy_src_place.ty(self.body.local_decls(), self.tcx);
250 if !src_ty.ty.is_enum() || src_ty.variant_index.is_some() {
251 return None;
252 }
253 let dest_ty = dest.ty(self.body.local_decls(), self.tcx);
254 if dest_ty.ty != src_ty.ty || dest_ty.variant_index.is_some() {
255 return None;
256 }
257 let ty::Adt(def, _) = dest_ty.ty.kind() else {
258 return None;
259 };
260
261 for &(case, rvalue) in rvals.iter() {
262 match rvalue {
263 Rvalue::Use(Operand::Constant(constant), _)
265 if let Const::Val(const_, ty) = constant.const_ =>
266 {
267 let (ecx, op) = mk_eval_cx_for_const_val(
268 self.tcx.at(constant.span),
269 self.typing_env,
270 const_,
271 ty,
272 )?;
273 let variant = ecx.read_discriminant(&op).discard_err()?;
274 if !def.variants()[variant].fields.is_empty() {
275 return None;
276 }
277 let Discr { val, .. } = ty.discriminant_for_variant(self.tcx, variant)?;
278 if val != case {
279 return None;
280 }
281 }
282 Rvalue::Use(Operand::Copy(src_place), _) if *src_place == copy_src_place => {}
283 Rvalue::Aggregate(AggregateKind::Adt(_, variant_index, _, _, None), fields)
285 if fields.is_empty()
286 && let Some(Discr { val, .. }) =
287 src_ty.ty.discriminant_for_variant(self.tcx, *variant_index)
288 && val == case => {}
289 _ => return None,
290 }
291 }
292 Some(StatementKind::Assign(Box::new((
295 dest,
296 Rvalue::Use(Operand::Copy(copy_src_place), WithRetag::No),
297 ))))
298 }
299
300 fn try_unify_stmts(
302 &mut self,
303 index: usize,
304 stmts: &[(u128, &StatementKind<'tcx>)],
305 otherwise: Option<&StatementKind<'tcx>>,
306 ) -> Option<StatementKind<'tcx>> {
307 if let Some(new_stmt) = identical_stmts(stmts, otherwise) {
308 return Some(new_stmt);
309 }
310
311 let (dest, rvals, otherwise) = candidate_assign(stmts, otherwise)?;
312 if let Some((consts, otherwise)) = candidate_const(&rvals, otherwise) {
313 if let Some(new_stmt) = self.unify_if_equal_const(dest, &consts, otherwise) {
314 return Some(new_stmt);
315 }
316 if let Some(new_stmt) = self.unify_by_eq_op(dest, &consts, otherwise) {
317 return Some(new_stmt);
318 }
319 if otherwise.is_none()
321 && let Some(new_stmt) = self.unify_by_int_to_int(dest, &consts)
322 {
323 return Some(new_stmt);
324 }
325 }
326
327 if index == 0
329 && dest.is_stable_offset()
331 && otherwise.is_none()
333 && let Some(new_stmt) = self.unify_by_copy(dest, &rvals)
334 {
335 return Some(new_stmt);
336 }
337 None
338 }
339}
340
341fn candidate_match<'tcx>(body: &Body<'tcx>, switch_bb: BasicBlock) -> bool {
343 use itertools::Itertools;
344 let targets = match &body.basic_blocks[switch_bb].terminator().kind {
345 TerminatorKind::SwitchInt {
346 discr: Operand::Copy(_) | Operand::Move(_), targets, ..
347 } => targets,
348 _ => return false,
350 };
351 if targets.all_targets().contains(&switch_bb) {
353 return false;
354 }
355 if !targets.is_distinct() {
357 return false;
358 }
359 targets
361 .all_targets()
362 .iter()
363 .map(|&bb| &body.basic_blocks[bb])
364 .filter(|bb| !bb.is_empty_unreachable())
365 .map(|bb| (bb.statements.len(), &bb.terminator().kind))
366 .all_equal()
367}
368
369fn simplify_match<'tcx>(
370 tcx: TyCtxt<'tcx>,
371 typing_env: ty::TypingEnv<'tcx>,
372 body: &mut Body<'tcx>,
373 switch_bb: BasicBlock,
374) -> bool {
375 let (discr, targets) = match &body.basic_blocks[switch_bb].terminator().kind {
376 TerminatorKind::SwitchInt { discr, targets, .. } => (discr, targets),
377 _ => unreachable!(),
378 };
379 let mut simplify_match = SimplifyMatch {
380 tcx,
381 typing_env,
382 patch: MirPatch::new(body),
383 body,
384 switch_bb,
385 discr,
386 discr_local: None,
387 discr_ty: discr.ty(body.local_decls(), tcx),
388 };
389 let reachable_cases: Vec<_> =
390 targets.iter().filter(|&(_, bb)| !body.basic_blocks[bb].is_empty_unreachable()).collect();
391 let mut new_stmts = Vec::new();
392 let otherwise = if body.basic_blocks[targets.otherwise()].is_empty_unreachable() {
393 None
394 } else {
395 Some(targets.otherwise())
396 };
397 match (reachable_cases.len(), otherwise.is_none()) {
399 (1, true) | (0, false) => {
400 let mut patch = simplify_match.patch;
401 remove_successors_from_switch(tcx, switch_bb, body, &mut patch, |bb| {
402 body.basic_blocks[bb].is_empty_unreachable()
403 });
404 patch.apply(body);
405 return true;
406 }
407 _ => {}
408 }
409 let Some(&(_, first_case_bb)) = reachable_cases.first() else {
410 return false;
411 };
412 let stmt_len = body.basic_blocks[first_case_bb].statements.len();
413 let mut cases = Vec::with_capacity(stmt_len);
414 for index in 0..stmt_len {
416 cases.clear();
417 let otherwise = otherwise.map(|bb| &body.basic_blocks[bb].statements[index].kind);
418 for &(case, bb) in &reachable_cases {
419 cases.push((case, &body.basic_blocks[bb].statements[index].kind));
420 }
421 let Some(new_stmt) = simplify_match.try_unify_stmts(index, &cases, otherwise) else {
422 return false;
423 };
424 new_stmts.push(new_stmt);
425 }
426 let discr = discr.clone();
428
429 let statement_index = body.basic_blocks[switch_bb].statements.len();
430 let parent_end = Location { block: switch_bb, statement_index };
431 let mut patch = simplify_match.patch;
432 if let Some(discr_local) = simplify_match.discr_local {
433 patch.add_statement(parent_end, StatementKind::StorageLive(discr_local));
434 patch.add_assign(parent_end, Place::from(discr_local), Rvalue::Use(discr, WithRetag::No));
435 }
436 for new_stmt in new_stmts {
437 patch.add_statement(parent_end, new_stmt);
438 }
439 if let Some(discr_local) = simplify_match.discr_local {
440 patch.add_statement(parent_end, StatementKind::StorageDead(discr_local));
441 }
442 patch.patch_terminator(switch_bb, body.basic_blocks[first_case_bb].terminator().kind.clone());
443 patch.apply(body);
444 true
445}
446
447fn can_cast(
449 tcx: TyCtxt<'_>,
450 src_val: impl Into<u128>,
451 src_layout: TyAndLayout<'_>,
452 cast_ty: Ty<'_>,
453 target_scalar: ScalarInt,
454) -> bool {
455 let from_scalar = ScalarInt::try_from_uint(src_val.into(), src_layout.size).unwrap();
456 let v = match src_layout.ty.kind() {
457 ty::Uint(_) => from_scalar.to_uint(src_layout.size),
458 ty::Int(_) => from_scalar.to_int(src_layout.size) as u128,
459 _ => return false,
462 };
463 let size = match *cast_ty.kind() {
464 ty::Int(t) => Integer::from_int_ty(&tcx, t).size(),
465 ty::Uint(t) => Integer::from_uint_ty(&tcx, t).size(),
466 _ => return false,
467 };
468 let v = size.truncate(v);
469 let cast_scalar = ScalarInt::try_from_uint(v, size).unwrap();
470 cast_scalar == target_scalar
471}
472
473fn candidate_assign<'tcx, 'a>(
474 stmts: &'a [(u128, &'a StatementKind<'tcx>)],
475 otherwise: Option<&'a StatementKind<'tcx>>,
476) -> Option<(Place<'tcx>, Vec<(u128, &'a Rvalue<'tcx>)>, Option<&'a Rvalue<'tcx>>)> {
477 let (_, first_stmt) = stmts[0];
478 let (dest, _) = first_stmt.as_assign()?;
479 let otherwise = if let Some(otherwise) = otherwise {
480 let Some((otherwise_dest, rval)) = otherwise.as_assign() else {
481 return None;
482 };
483 if otherwise_dest != dest {
484 return None;
485 }
486 Some(rval)
487 } else {
488 None
489 };
490 let rvals = stmts
491 .into_iter()
492 .map(|&(case, stmt)| {
493 let (other_dest, rval) = stmt.as_assign()?;
494 if other_dest != dest {
495 return None;
496 }
497 Some((case, rval))
498 })
499 .try_collect()?;
500 Some((*dest, rvals, otherwise))
501}
502
503fn candidate_const<'tcx, 'a>(
505 rvals: &'a [(u128, &'a Rvalue<'tcx>)],
506 otherwise: Option<&'a Rvalue<'tcx>>,
507) -> Option<(Vec<(u128, &'a ConstOperand<'tcx>)>, Option<&'a ConstOperand<'tcx>>)> {
508 let otherwise = if let Some(otherwise) = otherwise {
510 let Rvalue::Use(Operand::Constant(const_), _) = otherwise else {
511 return None;
512 };
513 Some(&**const_)
514 } else {
515 None
516 };
517 let consts = rvals
518 .into_iter()
519 .map(|&(case, rval)| {
520 let Rvalue::Use(Operand::Constant(const_), _) = rval else { return None };
521 Some((case, &**const_))
522 })
523 .try_collect()?;
524 Some((consts, otherwise))
525}
526
527fn split_first_case<'a, T>(
529 stmts: &'a [(u128, &'a T)],
530 otherwise: Option<&'a T>,
531) -> (u128, &'a T, impl Iterator<Item = &'a T>) {
532 let (first_case, first) = stmts[0];
533 (first_case, first, stmts[1..].into_iter().map(|&(_, val)| val).chain(otherwise))
534}
535
536fn identical_stmts<'tcx>(
538 stmts: &[(u128, &StatementKind<'tcx>)],
539 otherwise: Option<&StatementKind<'tcx>>,
540) -> Option<StatementKind<'tcx>> {
541 use itertools::Itertools;
542 let (_, first_stmt, others) = split_first_case(stmts, otherwise);
543 if std::iter::once(first_stmt).chain(others).all_equal() {
544 return Some(first_stmt.clone());
545 }
546 None
547}