@@ -10,7 +10,7 @@ use crate::ty::print::{pretty_print_const, with_no_trimmed_paths};
10
10
use crate :: ty:: print:: { FmtPrinter , Printer } ;
11
11
use crate :: ty:: visit:: TypeVisitableExt ;
12
12
use crate :: ty:: { self , List , Ty , TyCtxt } ;
13
- use crate :: ty:: { AdtDef , InstanceDef , UserTypeAnnotationIndex } ;
13
+ use crate :: ty:: { AdtDef , Instance , InstanceDef , UserTypeAnnotationIndex } ;
14
14
use crate :: ty:: { GenericArg , GenericArgsRef } ;
15
15
16
16
use rustc_data_structures:: captures:: Captures ;
@@ -29,6 +29,7 @@ pub use rustc_ast::Mutability;
29
29
use rustc_data_structures:: fx:: FxHashMap ;
30
30
use rustc_data_structures:: fx:: FxHashSet ;
31
31
use rustc_data_structures:: graph:: dominators:: Dominators ;
32
+ use rustc_index:: bit_set:: BitSet ;
32
33
use rustc_index:: { Idx , IndexSlice , IndexVec } ;
33
34
use rustc_serialize:: { Decodable , Encodable } ;
34
35
use rustc_span:: symbol:: Symbol ;
@@ -642,6 +643,130 @@ impl<'tcx> Body<'tcx> {
642
643
self . injection_phase . is_some ( )
643
644
}
644
645
646
+ /// Finds which basic blocks are actually reachable for a specific
647
+ /// monomorphization of this body.
648
+ ///
649
+ /// This is allowed to have false positives; just because this says a block
650
+ /// is reachable doesn't mean that's necessarily true. It's thus always
651
+ /// legal for this to return a filled set.
652
+ ///
653
+ /// Regardless, the [`BitSet::domain_size`] of the returned set will always
654
+ /// exactly match the number of blocks in the body so that `contains`
655
+ /// checks can be done without worrying about panicking.
656
+ ///
657
+ /// This is mostly useful because it lets us skip lowering the `false` side
658
+ /// of `if <T as Trait>::CONST`, as well as `intrinsics::debug_assertions`.
659
+ pub fn reachable_blocks_in_mono (
660
+ & self ,
661
+ tcx : TyCtxt < ' tcx > ,
662
+ instance : Instance < ' tcx > ,
663
+ ) -> BitSet < BasicBlock > {
664
+ let mut set = BitSet :: new_empty ( self . basic_blocks . len ( ) ) ;
665
+ self . reachable_blocks_in_mono_from ( tcx, instance, & mut set, START_BLOCK ) ;
666
+ set
667
+ }
668
+
669
+ fn reachable_blocks_in_mono_from (
670
+ & self ,
671
+ tcx : TyCtxt < ' tcx > ,
672
+ instance : Instance < ' tcx > ,
673
+ set : & mut BitSet < BasicBlock > ,
674
+ bb : BasicBlock ,
675
+ ) {
676
+ if !set. insert ( bb) {
677
+ return ;
678
+ }
679
+
680
+ let data = & self . basic_blocks [ bb] ;
681
+
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 > | {
702
+ let env = ty:: ParamEnv :: reveal_all ( ) ;
703
+ let mono_literal = instance. instantiate_mir_and_normalize_erasing_regions (
704
+ tcx,
705
+ env,
706
+ crate :: ty:: EarlyBinder :: bind ( constant. const_ ) ,
707
+ ) ;
708
+ let Some ( bits) = mono_literal. try_eval_bits ( tcx, env) else {
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) ) ;
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 ;
756
+ }
757
+
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 ,
767
+ }
768
+ }
769
+
645
770
/// For a `Location` in this scope, determine what the "caller location" at that point is. This
646
771
/// is interesting because of inlining: the `#[track_caller]` attribute of inlined functions
647
772
/// must be honored. Falls back to the `tracked_caller` value for `#[track_caller]` functions,
0 commit comments