Skip to content

Commit 9de6df6

Browse files
committed
Remove SimplifyBranchSame MIR optimization
This optimization never had any positive impact on compile or runtime performance. It is relatively complex and used to have bugs in the past. Its implementation contains significantly more lines of code than the number of blocks it happens to optimize out during rustc build process (including all dependencies and the standard library). Remove it.
1 parent f1dab24 commit 9de6df6

19 files changed

+253
-758
lines changed

compiler/rustc_mir/src/transform/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,6 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
403403
&early_otherwise_branch::EarlyOtherwiseBranch,
404404
&simplify_comparison_integral::SimplifyComparisonIntegral,
405405
&simplify_try::SimplifyArmIdentity,
406-
&simplify_try::SimplifyBranchSame,
407406
&dest_prop::DestinationPropagation,
408407
&copy_prop::CopyPropagation,
409408
&simplify_branches::SimplifyBranches::new("after-copy-prop"),

compiler/rustc_mir/src/transform/simplify_try.rs

+3-266
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
//!
1010
//! into just `x`.
1111
12-
use crate::transform::{simplify, MirPass};
13-
use itertools::Itertools as _;
12+
use crate::transform::MirPass;
1413
use rustc_index::{bit_set::BitSet, vec::IndexVec};
1514
use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
1615
use rustc_middle::mir::*;
17-
use rustc_middle::ty::{self, List, Ty, TyCtxt};
16+
use rustc_middle::ty::{List, Ty, TyCtxt};
1817
use rustc_target::abi::VariantIdx;
19-
use std::iter::{once, Enumerate, Peekable};
18+
use std::iter::{Enumerate, Peekable};
2019
use std::slice::Iter;
2120

2221
/// Simplifies arms of form `Variant(x) => Variant(x)` to just a move.
@@ -523,265 +522,3 @@ fn match_variant_field_place<'tcx>(place: Place<'tcx>) -> Option<(Local, VarFiel
523522
_ => None,
524523
}
525524
}
526-
527-
/// Simplifies `SwitchInt(_) -> [targets]`,
528-
/// where all the `targets` have the same form,
529-
/// into `goto -> target_first`.
530-
pub struct SimplifyBranchSame;
531-
532-
impl<'tcx> MirPass<'tcx> for SimplifyBranchSame {
533-
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
534-
trace!("Running SimplifyBranchSame on {:?}", body.source);
535-
let finder = SimplifyBranchSameOptimizationFinder { body, tcx };
536-
let opts = finder.find();
537-
538-
let did_remove_blocks = opts.len() > 0;
539-
for opt in opts.iter() {
540-
trace!("SUCCESS: Applying optimization {:?}", opt);
541-
// Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`.
542-
body.basic_blocks_mut()[opt.bb_to_opt_terminator].terminator_mut().kind =
543-
TerminatorKind::Goto { target: opt.bb_to_goto };
544-
}
545-
546-
if did_remove_blocks {
547-
// We have dead blocks now, so remove those.
548-
simplify::remove_dead_blocks(body);
549-
}
550-
}
551-
}
552-
553-
#[derive(Debug)]
554-
struct SimplifyBranchSameOptimization {
555-
/// All basic blocks are equal so go to this one
556-
bb_to_goto: BasicBlock,
557-
/// Basic block where the terminator can be simplified to a goto
558-
bb_to_opt_terminator: BasicBlock,
559-
}
560-
561-
struct SwitchTargetAndValue {
562-
target: BasicBlock,
563-
// None in case of the `otherwise` case
564-
value: Option<u128>,
565-
}
566-
567-
struct SimplifyBranchSameOptimizationFinder<'a, 'tcx> {
568-
body: &'a Body<'tcx>,
569-
tcx: TyCtxt<'tcx>,
570-
}
571-
572-
impl<'a, 'tcx> SimplifyBranchSameOptimizationFinder<'a, 'tcx> {
573-
fn find(&self) -> Vec<SimplifyBranchSameOptimization> {
574-
self.body
575-
.basic_blocks()
576-
.iter_enumerated()
577-
.filter_map(|(bb_idx, bb)| {
578-
let (discr_switched_on, targets_and_values) = match &bb.terminator().kind {
579-
TerminatorKind::SwitchInt { targets, discr, values, .. } => {
580-
// if values.len() == targets.len() - 1, we need to include None where no value is present
581-
// such that the zip does not throw away targets. If no `otherwise` case is in targets, the zip will simply throw away the added None
582-
let values_extended = values.iter().map(|x|Some(*x)).chain(once(None));
583-
let targets_and_values:Vec<_> = targets.iter().zip(values_extended)
584-
.map(|(target, value)| SwitchTargetAndValue{target:*target, value})
585-
.collect();
586-
assert_eq!(targets.len(), targets_and_values.len());
587-
(discr, targets_and_values)},
588-
_ => return None,
589-
};
590-
591-
// find the adt that has its discriminant read
592-
// assuming this must be the last statement of the block
593-
let adt_matched_on = match &bb.statements.last()?.kind {
594-
StatementKind::Assign(box (place, rhs))
595-
if Some(*place) == discr_switched_on.place() =>
596-
{
597-
match rhs {
598-
Rvalue::Discriminant(adt_place) if adt_place.ty(self.body, self.tcx).ty.is_enum() => adt_place,
599-
_ => {
600-
trace!("NO: expected a discriminant read of an enum instead of: {:?}", rhs);
601-
return None;
602-
}
603-
}
604-
}
605-
other => {
606-
trace!("NO: expected an assignment of a discriminant read to a place. Found: {:?}", other);
607-
return None
608-
},
609-
};
610-
611-
let mut iter_bbs_reachable = targets_and_values
612-
.iter()
613-
.map(|target_and_value| (target_and_value, &self.body.basic_blocks()[target_and_value.target]))
614-
.filter(|(_, bb)| {
615-
// Reaching `unreachable` is UB so assume it doesn't happen.
616-
bb.terminator().kind != TerminatorKind::Unreachable
617-
// But `asm!(...)` could abort the program,
618-
// so we cannot assume that the `unreachable` terminator itself is reachable.
619-
// FIXME(Centril): use a normalization pass instead of a check.
620-
|| bb.statements.iter().any(|stmt| match stmt.kind {
621-
StatementKind::LlvmInlineAsm(..) => true,
622-
_ => false,
623-
})
624-
})
625-
.peekable();
626-
627-
let bb_first = iter_bbs_reachable.peek().map(|(idx, _)| *idx).unwrap_or(&targets_and_values[0]);
628-
let mut all_successors_equivalent = StatementEquality::TrivialEqual;
629-
630-
// All successor basic blocks must be equal or contain statements that are pairwise considered equal.
631-
for ((target_and_value_l,bb_l), (target_and_value_r,bb_r)) in iter_bbs_reachable.tuple_windows() {
632-
let trivial_checks = bb_l.is_cleanup == bb_r.is_cleanup
633-
&& bb_l.terminator().kind == bb_r.terminator().kind
634-
&& bb_l.statements.len() == bb_r.statements.len();
635-
let statement_check = || {
636-
bb_l.statements.iter().zip(&bb_r.statements).try_fold(StatementEquality::TrivialEqual, |acc,(l,r)| {
637-
let stmt_equality = self.statement_equality(*adt_matched_on, &l, target_and_value_l, &r, target_and_value_r);
638-
if matches!(stmt_equality, StatementEquality::NotEqual) {
639-
// short circuit
640-
None
641-
} else {
642-
Some(acc.combine(&stmt_equality))
643-
}
644-
})
645-
.unwrap_or(StatementEquality::NotEqual)
646-
};
647-
if !trivial_checks {
648-
all_successors_equivalent = StatementEquality::NotEqual;
649-
break;
650-
}
651-
all_successors_equivalent = all_successors_equivalent.combine(&statement_check());
652-
};
653-
654-
match all_successors_equivalent{
655-
StatementEquality::TrivialEqual => {
656-
// statements are trivially equal, so just take first
657-
trace!("Statements are trivially equal");
658-
Some(SimplifyBranchSameOptimization {
659-
bb_to_goto: bb_first.target,
660-
bb_to_opt_terminator: bb_idx,
661-
})
662-
}
663-
StatementEquality::ConsideredEqual(bb_to_choose) => {
664-
trace!("Statements are considered equal");
665-
Some(SimplifyBranchSameOptimization {
666-
bb_to_goto: bb_to_choose,
667-
bb_to_opt_terminator: bb_idx,
668-
})
669-
}
670-
StatementEquality::NotEqual => {
671-
trace!("NO: not all successors of basic block {:?} were equivalent", bb_idx);
672-
None
673-
}
674-
}
675-
})
676-
.collect()
677-
}
678-
679-
/// Tests if two statements can be considered equal
680-
///
681-
/// Statements can be trivially equal if the kinds match.
682-
/// But they can also be considered equal in the following case A:
683-
/// ```
684-
/// discriminant(_0) = 0; // bb1
685-
/// _0 = move _1; // bb2
686-
/// ```
687-
/// In this case the two statements are equal iff
688-
/// 1: _0 is an enum where the variant index 0 is fieldless, and
689-
/// 2: bb1 was targeted by a switch where the discriminant of _1 was switched on
690-
fn statement_equality(
691-
&self,
692-
adt_matched_on: Place<'tcx>,
693-
x: &Statement<'tcx>,
694-
x_target_and_value: &SwitchTargetAndValue,
695-
y: &Statement<'tcx>,
696-
y_target_and_value: &SwitchTargetAndValue,
697-
) -> StatementEquality {
698-
let helper = |rhs: &Rvalue<'tcx>,
699-
place: &Place<'tcx>,
700-
variant_index: &VariantIdx,
701-
side_to_choose| {
702-
let place_type = place.ty(self.body, self.tcx).ty;
703-
let adt = match *place_type.kind() {
704-
ty::Adt(adt, _) if adt.is_enum() => adt,
705-
_ => return StatementEquality::NotEqual,
706-
};
707-
let variant_is_fieldless = adt.variants[*variant_index].fields.is_empty();
708-
if !variant_is_fieldless {
709-
trace!("NO: variant {:?} was not fieldless", variant_index);
710-
return StatementEquality::NotEqual;
711-
}
712-
713-
match rhs {
714-
Rvalue::Use(operand) if operand.place() == Some(adt_matched_on) => {
715-
StatementEquality::ConsideredEqual(side_to_choose)
716-
}
717-
_ => {
718-
trace!(
719-
"NO: RHS of assignment was {:?}, but expected it to match the adt being matched on in the switch, which is {:?}",
720-
rhs,
721-
adt_matched_on
722-
);
723-
StatementEquality::NotEqual
724-
}
725-
}
726-
};
727-
match (&x.kind, &y.kind) {
728-
// trivial case
729-
(x, y) if x == y => StatementEquality::TrivialEqual,
730-
731-
// check for case A
732-
(
733-
StatementKind::Assign(box (_, rhs)),
734-
StatementKind::SetDiscriminant { place, variant_index },
735-
)
736-
// we need to make sure that the switch value that targets the bb with SetDiscriminant (y), is the same as the variant index
737-
if Some(variant_index.index() as u128) == y_target_and_value.value => {
738-
// choose basic block of x, as that has the assign
739-
helper(rhs, place, variant_index, x_target_and_value.target)
740-
}
741-
(
742-
StatementKind::SetDiscriminant { place, variant_index },
743-
StatementKind::Assign(box (_, rhs)),
744-
)
745-
// we need to make sure that the switch value that targets the bb with SetDiscriminant (x), is the same as the variant index
746-
if Some(variant_index.index() as u128) == x_target_and_value.value => {
747-
// choose basic block of y, as that has the assign
748-
helper(rhs, place, variant_index, y_target_and_value.target)
749-
}
750-
_ => {
751-
trace!("NO: statements `{:?}` and `{:?}` not considered equal", x, y);
752-
StatementEquality::NotEqual
753-
}
754-
}
755-
}
756-
}
757-
758-
#[derive(Copy, Clone, Eq, PartialEq)]
759-
enum StatementEquality {
760-
/// The two statements are trivially equal; same kind
761-
TrivialEqual,
762-
/// The two statements are considered equal, but may be of different kinds. The BasicBlock field is the basic block to jump to when performing the branch-same optimization.
763-
/// For example, `_0 = _1` and `discriminant(_0) = discriminant(0)` are considered equal if 0 is a fieldless variant of an enum. But we don't want to jump to the basic block with the SetDiscriminant, as that is not legal if _1 is not the 0 variant index
764-
ConsideredEqual(BasicBlock),
765-
/// The two statements are not equal
766-
NotEqual,
767-
}
768-
769-
impl StatementEquality {
770-
fn combine(&self, other: &StatementEquality) -> StatementEquality {
771-
use StatementEquality::*;
772-
match (self, other) {
773-
(TrivialEqual, TrivialEqual) => TrivialEqual,
774-
(TrivialEqual, ConsideredEqual(b)) | (ConsideredEqual(b), TrivialEqual) => {
775-
ConsideredEqual(*b)
776-
}
777-
(ConsideredEqual(b1), ConsideredEqual(b2)) => {
778-
if b1 == b2 {
779-
ConsideredEqual(*b1)
780-
} else {
781-
NotEqual
782-
}
783-
}
784-
(_, NotEqual) | (NotEqual, _) => NotEqual,
785-
}
786-
}
787-
}

src/test/codegen/try_identity.rs

-17
This file was deleted.

src/test/mir-opt/76803_regression.encode.SimplifyBranchSame.diff

-28
This file was deleted.

src/test/mir-opt/76803_regression.rs

-19
This file was deleted.

src/test/mir-opt/simplify-arm.rs

-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
// compile-flags: -Z mir-opt-level=2 -Zunsound-mir-opts
22
// EMIT_MIR simplify_arm.id.SimplifyArmIdentity.diff
3-
// EMIT_MIR simplify_arm.id.SimplifyBranchSame.diff
43
// EMIT_MIR simplify_arm.id_result.SimplifyArmIdentity.diff
5-
// EMIT_MIR simplify_arm.id_result.SimplifyBranchSame.diff
64
// EMIT_MIR simplify_arm.id_try.SimplifyArmIdentity.diff
7-
// EMIT_MIR simplify_arm.id_try.SimplifyBranchSame.diff
85

96
fn id(o: Option<u8>) -> Option<u8> {
107
match o {

0 commit comments

Comments
 (0)