@@ -654,22 +654,13 @@ impl<'tcx> Body<'tcx> {
654
654
/// exactly match the number of blocks in the body so that `contains`
655
655
/// checks can be done without worrying about panicking.
656
656
///
657
- /// The main case this supports is filtering out `if <T as Trait>::CONST`
658
- /// bodies that can't be removed in generic MIR, but *can* be removed once
659
- /// the specific `T` is known.
660
- ///
661
- /// This is used in the monomorphization collector as well as in codegen.
657
+ /// This is mostly useful because it lets us skip lowering the `false` side
658
+ /// of `if <T as Trait>::CONST`, as well as [`std::intrinsics::debug_assertions`].
662
659
pub fn reachable_blocks_in_mono (
663
660
& self ,
664
661
tcx : TyCtxt < ' tcx > ,
665
662
instance : Instance < ' tcx > ,
666
663
) -> BitSet < BasicBlock > {
667
- if instance. args . non_erasable_generics ( tcx, instance. def_id ( ) ) . next ( ) . is_none ( ) {
668
- // If it's non-generic, then mir-opt const prop has already run, meaning it's
669
- // probably not worth doing any further filtering. So call everything reachable.
670
- return BitSet :: new_filled ( self . basic_blocks . len ( ) ) ;
671
- }
672
-
673
664
let mut set = BitSet :: new_empty ( self . basic_blocks . len ( ) ) ;
674
665
self . reachable_blocks_in_mono_from ( tcx, instance, & mut set, START_BLOCK ) ;
675
666
set
@@ -688,25 +679,91 @@ impl<'tcx> Body<'tcx> {
688
679
689
680
let data = & self . basic_blocks [ bb] ;
690
681
691
- if let TerminatorKind :: SwitchInt { discr : Operand :: Constant ( constant) , targets } =
692
- & data. terminator ( ) . kind
693
- {
682
+ if let Some ( ( bits, targets) ) = Self :: try_const_mono_switchint ( tcx, instance, data) {
683
+ let target = targets. target_for_value ( bits) ;
684
+ return self . reachable_blocks_in_mono_from ( tcx, instance, set, target) ;
685
+ }
686
+
687
+ for target in data. terminator ( ) . successors ( ) {
688
+ self . reachable_blocks_in_mono_from ( tcx, instance, set, target) ;
689
+ }
690
+ }
691
+
692
+ /// If this basic block ends with a [`TerminatorKind::SwitchInt`] for which we can evaluate the
693
+ /// dimscriminant in monomorphization, we return the discriminant bits and the
694
+ /// [`SwitchTargets`], just so the caller doesn't also have to match on the terminator.
695
+ fn try_const_mono_switchint < ' a > (
696
+ tcx : TyCtxt < ' tcx > ,
697
+ instance : Instance < ' tcx > ,
698
+ block : & ' a BasicBlockData < ' tcx > ,
699
+ ) -> Option < ( u128 , & ' a SwitchTargets ) > {
700
+ // There are two places here we need to evaluate a constant.
701
+ let eval_mono_const = |constant : & ConstOperand < ' tcx > | {
694
702
let env = ty:: ParamEnv :: reveal_all ( ) ;
695
703
let mono_literal = instance. instantiate_mir_and_normalize_erasing_regions (
696
704
tcx,
697
705
env,
698
706
crate :: ty:: EarlyBinder :: bind ( constant. const_ ) ,
699
707
) ;
700
- if let Some ( bits) = mono_literal. try_eval_bits ( tcx, env) {
701
- let target = targets. target_for_value ( bits) ;
702
- return self . reachable_blocks_in_mono_from ( tcx, instance, set, target) ;
703
- } else {
708
+ let Some ( bits) = mono_literal. try_eval_bits ( tcx, env) else {
704
709
bug ! ( "Couldn't evaluate constant {:?} in mono {:?}" , constant, instance) ;
710
+ } ;
711
+ bits
712
+ } ;
713
+
714
+ let TerminatorKind :: SwitchInt { discr, targets } = & block. terminator ( ) . kind else {
715
+ return None ;
716
+ } ;
717
+
718
+ // If this is a SwitchInt(const _), then we can just evaluate the constant and return.
719
+ let discr = match discr {
720
+ Operand :: Constant ( constant) => {
721
+ let bits = eval_mono_const ( constant) ;
722
+ return Some ( ( bits, targets) ) ;
705
723
}
724
+ Operand :: Move ( place) | Operand :: Copy ( place) => place,
725
+ } ;
726
+
727
+ // MIR for `if false` actually looks like this:
728
+ // _1 = const _
729
+ // SwitchInt(_1)
730
+ //
731
+ // And MIR for if intrinsics::debug_assertions() looks like this:
732
+ // _1 = cfg!(debug_assertions)
733
+ // SwitchInt(_1)
734
+ //
735
+ // So we're going to try to recognize this pattern.
736
+ //
737
+ // If we have a SwitchInt on a non-const place, we find the most recent statement that
738
+ // isn't a storage marker. If that statement is an assignment of a const to our
739
+ // discriminant place, we evaluate and return the const, as if we've const-propagated it
740
+ // into the SwitchInt.
741
+
742
+ let last_stmt = block
743
+ . statements
744
+ . iter ( )
745
+ . rev ( )
746
+ . skip_while ( |stmt| {
747
+ matches ! ( stmt. kind, StatementKind :: StorageDead ( _) | StatementKind :: StorageLive ( _) )
748
+ } )
749
+ . next ( ) ?;
750
+ let StatementKind :: Assign ( box ( place, rvalue) ) = & last_stmt. kind else {
751
+ return None ;
752
+ } ;
753
+
754
+ if discr != place {
755
+ return None ;
706
756
}
707
757
708
- for target in data. terminator ( ) . successors ( ) {
709
- self . reachable_blocks_in_mono_from ( tcx, instance, set, target) ;
758
+ match rvalue {
759
+ Rvalue :: NullaryOp ( NullOp :: DebugAssertions , _) => {
760
+ Some ( ( tcx. sess . opts . debug_assertions as u128 , targets) )
761
+ }
762
+ Rvalue :: Use ( Operand :: Constant ( constant) ) => {
763
+ let bits = eval_mono_const ( constant) ;
764
+ Some ( ( bits, targets) )
765
+ }
766
+ _ => None ,
710
767
}
711
768
}
712
769
0 commit comments