|
9 | 9 | //! |
10 | 10 | //! into just `x`. |
11 | 11 |
|
12 | | -use crate::transform::{simplify, MirPass}; |
13 | | -use itertools::Itertools as _; |
| 12 | +use crate::transform::MirPass; |
14 | 13 | use rustc_index::{bit_set::BitSet, vec::IndexVec}; |
15 | 14 | use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor}; |
16 | 15 | use rustc_middle::mir::*; |
17 | | -use rustc_middle::ty::{self, List, Ty, TyCtxt}; |
| 16 | +use rustc_middle::ty::{List, Ty, TyCtxt}; |
18 | 17 | use rustc_target::abi::VariantIdx; |
19 | | -use std::iter::{once, Enumerate, Peekable}; |
| 18 | +use std::iter::{Enumerate, Peekable}; |
20 | 19 | use std::slice::Iter; |
21 | 20 |
|
22 | 21 | /// 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 |
523 | 522 | _ => None, |
524 | 523 | } |
525 | 524 | } |
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 | | -} |
0 commit comments