13
13
//! move analysis runs after promotion on broken MIR.
14
14
15
15
use either:: { Left , Right } ;
16
+ use rustc_data_structures:: fx:: FxHashSet ;
16
17
use rustc_hir as hir;
17
18
use rustc_middle:: mir;
18
19
use rustc_middle:: mir:: visit:: { MutVisitor , MutatingUseContext , PlaceContext , Visitor } ;
@@ -60,7 +61,7 @@ impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
60
61
61
62
let promotable_candidates = validate_candidates ( & ccx, & mut temps, & all_candidates) ;
62
63
63
- let promoted = promote_candidates ( body, tcx, temps, promotable_candidates) ;
64
+ let promoted = promote_candidates ( body, tcx, ccx . const_kind , temps, promotable_candidates) ;
64
65
self . promoted_fragments . set ( promoted) ;
65
66
}
66
67
}
@@ -175,6 +176,12 @@ fn collect_temps_and_candidates<'tcx>(
175
176
struct Validator < ' a , ' tcx > {
176
177
ccx : & ' a ConstCx < ' a , ' tcx > ,
177
178
temps : & ' a mut IndexSlice < Local , TempState > ,
179
+ /// For backwards compatibility, we are promoting function calls in `const`/`static`
180
+ /// initializers. But we want to avoid evaluating code that might panic and that otherwise would
181
+ /// not have been evaluated, so we only promote such calls in basic blocks that are guaranteed
182
+ /// to execute. In other words, we only promote such calls in basic blocks that are definitely
183
+ /// not dead code. Here we cache the result of computing that set of basic blocks.
184
+ promotion_safe_blocks : Option < FxHashSet < BasicBlock > > ,
178
185
}
179
186
180
187
impl < ' a , ' tcx > std:: ops:: Deref for Validator < ' a , ' tcx > {
@@ -260,7 +267,9 @@ impl<'tcx> Validator<'_, 'tcx> {
260
267
self . validate_rvalue ( rhs)
261
268
}
262
269
Right ( terminator) => match & terminator. kind {
263
- TerminatorKind :: Call { func, args, .. } => self . validate_call ( func, args) ,
270
+ TerminatorKind :: Call { func, args, .. } => {
271
+ self . validate_call ( func, args, loc. block )
272
+ }
264
273
TerminatorKind :: Yield { .. } => Err ( Unpromotable ) ,
265
274
kind => {
266
275
span_bug ! ( terminator. source_info. span, "{:?} not promotable" , kind) ;
@@ -587,53 +596,110 @@ impl<'tcx> Validator<'_, 'tcx> {
587
596
Ok ( ( ) )
588
597
}
589
598
599
+ /// Computes the sets of blocks of this MIR that are definitely going to be executed
600
+ /// if the function returns successfully. That makes it safe to promote calls in them
601
+ /// that might fail.
602
+ fn promotion_safe_blocks ( body : & mir:: Body < ' tcx > ) -> FxHashSet < BasicBlock > {
603
+ let mut safe_blocks = FxHashSet :: default ( ) ;
604
+ let mut safe_block = START_BLOCK ;
605
+ loop {
606
+ safe_blocks. insert ( safe_block) ;
607
+ // Let's see if we can find another safe block.
608
+ safe_block = match body. basic_blocks [ safe_block] . terminator ( ) . kind {
609
+ TerminatorKind :: Goto { target } => target,
610
+ TerminatorKind :: Call { target : Some ( target) , .. }
611
+ | TerminatorKind :: Drop { target, .. } => {
612
+ // This calls a function or the destructor. `target` does not get executed if
613
+ // the callee loops or panics. But in both cases the const already fails to
614
+ // evaluate, so we are fine considering `target` a safe block for promotion.
615
+ target
616
+ }
617
+ TerminatorKind :: Assert { target, .. } => {
618
+ // Similar to above, we only consider successful execution.
619
+ target
620
+ }
621
+ _ => {
622
+ // No next safe block.
623
+ break ;
624
+ }
625
+ } ;
626
+ }
627
+ safe_blocks
628
+ }
629
+
630
+ /// Returns whether the block is "safe" for promotion, which means it cannot be dead code.
631
+ /// We use this to avoid promoting operations that can fail in dead code.
632
+ fn is_promotion_safe_block ( & mut self , block : BasicBlock ) -> bool {
633
+ let body = self . body ;
634
+ let safe_blocks =
635
+ self . promotion_safe_blocks . get_or_insert_with ( || Self :: promotion_safe_blocks ( body) ) ;
636
+ safe_blocks. contains ( & block)
637
+ }
638
+
590
639
fn validate_call (
591
640
& mut self ,
592
641
callee : & Operand < ' tcx > ,
593
642
args : & [ Spanned < Operand < ' tcx > > ] ,
643
+ block : BasicBlock ,
594
644
) -> Result < ( ) , Unpromotable > {
595
- let fn_ty = callee. ty ( self . body , self . tcx ) ;
645
+ // Validate the operands. If they fail, there's no question -- we cannot promote.
646
+ self . validate_operand ( callee) ?;
647
+ for arg in args {
648
+ self . validate_operand ( & arg. node ) ?;
649
+ }
596
650
597
- // Inside const/static items, we promote all (eligible) function calls.
598
- // Everywhere else, we require `#[rustc_promotable]` on the callee.
599
- let promote_all_const_fn = matches ! (
600
- self . const_kind,
601
- Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
602
- ) ;
603
- if !promote_all_const_fn {
604
- if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
605
- // Never promote runtime `const fn` calls of
606
- // functions without `#[rustc_promotable]`.
607
- if !self . tcx . is_promotable_const_fn ( def_id) {
608
- return Err ( Unpromotable ) ;
609
- }
651
+ // Functions marked `#[rustc_promotable]` are explicitly allowed to be promoted, so we can
652
+ // accept them at this point.
653
+ let fn_ty = callee. ty ( self . body , self . tcx ) ;
654
+ if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
655
+ if self . tcx . is_promotable_const_fn ( def_id) {
656
+ return Ok ( ( ) ) ;
610
657
}
611
658
}
612
659
660
+ // Ideally, we'd stop here and reject the rest. But for backward compatibility, we have to
661
+ // accept some promotion in const/static initializers.
662
+ if !promote_all_fn ( self . ccx . const_kind ) {
663
+ return Err ( Unpromotable ) ;
664
+ }
665
+ // Make sure the callee is a `const fn`.
613
666
let is_const_fn = match * fn_ty. kind ( ) {
614
667
ty:: FnDef ( def_id, _) => self . tcx . is_const_fn_raw ( def_id) ,
615
668
_ => false ,
616
669
} ;
617
670
if !is_const_fn {
618
671
return Err ( Unpromotable ) ;
619
672
}
620
-
621
- self . validate_operand ( callee) ?;
622
- for arg in args {
623
- self . validate_operand ( & arg. node ) ?;
673
+ // The problem is, this may promote calls to functions that panic.
674
+ // We don't want to introduce compilation errors if there's a panic in a call in dead code.
675
+ // So we ensure that this is not dead code.
676
+ if !self . is_promotion_safe_block ( block) {
677
+ return Err ( Unpromotable ) ;
624
678
}
625
-
679
+ // This passed all checks, so let's accept.
626
680
Ok ( ( ) )
627
681
}
628
682
}
629
683
684
+ /// Decides whether in this context we are okay with promoting all functions (and not just
685
+ /// `#[rustc_promotable]`).
686
+ /// For backwards compatibility, we do that in static/const initializers.
687
+ fn promote_all_fn ( const_kind : Option < hir:: ConstContext > ) -> bool {
688
+ // Inline consts are explicitly excluded, they are more recent so we have no
689
+ // backwards compatibility reason to allow more promotion inside of them.
690
+ matches ! (
691
+ const_kind,
692
+ Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
693
+ )
694
+ }
695
+
630
696
// FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
631
697
fn validate_candidates (
632
698
ccx : & ConstCx < ' _ , ' _ > ,
633
699
temps : & mut IndexSlice < Local , TempState > ,
634
700
candidates : & [ Candidate ] ,
635
701
) -> Vec < Candidate > {
636
- let mut validator = Validator { ccx, temps } ;
702
+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
637
703
638
704
candidates
639
705
. iter ( )
@@ -652,6 +718,9 @@ struct Promoter<'a, 'tcx> {
652
718
/// If true, all nested temps are also kept in the
653
719
/// source MIR, not moved to the promoted MIR.
654
720
keep_original : bool ,
721
+
722
+ /// If true, add the new const (the promoted) to the required_consts of the parent MIR.
723
+ add_to_required : bool ,
655
724
}
656
725
657
726
impl < ' a , ' tcx > Promoter < ' a , ' tcx > {
@@ -798,11 +867,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
798
867
let args = tcx. erase_regions ( GenericArgs :: identity_for_item ( tcx, def) ) ;
799
868
let uneval = mir:: UnevaluatedConst { def, args, promoted : Some ( promoted_id) } ;
800
869
801
- Operand :: Constant ( Box :: new ( ConstOperand {
802
- span,
803
- user_ty : None ,
804
- const_ : Const :: Unevaluated ( uneval, ty) ,
805
- } ) )
870
+ ConstOperand { span, user_ty : None , const_ : Const :: Unevaluated ( uneval, ty) }
806
871
} ;
807
872
808
873
let blocks = self . source . basic_blocks . as_mut ( ) ;
@@ -838,11 +903,15 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
838
903
let promoted_ref = local_decls. push ( promoted_ref) ;
839
904
assert_eq ! ( self . temps. push( TempState :: Unpromotable ) , promoted_ref) ;
840
905
906
+ let promoted_operand = promoted_operand ( ref_ty, span) ;
907
+ if self . add_to_required {
908
+ self . source . required_consts . push ( promoted_operand) ;
909
+ }
841
910
let promoted_ref_statement = Statement {
842
911
source_info : statement. source_info ,
843
912
kind : StatementKind :: Assign ( Box :: new ( (
844
913
Place :: from ( promoted_ref) ,
845
- Rvalue :: Use ( promoted_operand ( ref_ty , span ) ) ,
914
+ Rvalue :: Use ( Operand :: Constant ( Box :: new ( promoted_operand ) ) ) ,
846
915
) ) ) ,
847
916
} ;
848
917
self . extra_statements . push ( ( loc, promoted_ref_statement) ) ;
@@ -885,6 +954,7 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
885
954
fn promote_candidates < ' tcx > (
886
955
body : & mut Body < ' tcx > ,
887
956
tcx : TyCtxt < ' tcx > ,
957
+ ccx_const_kind : Option < hir:: ConstContext > ,
888
958
mut temps : IndexVec < Local , TempState > ,
889
959
candidates : Vec < Candidate > ,
890
960
) -> IndexVec < Promoted , Body < ' tcx > > {
@@ -924,6 +994,11 @@ fn promote_candidates<'tcx>(
924
994
None ,
925
995
body. tainted_by_errors ,
926
996
) ;
997
+ // We keep `required_consts` of the new MIR body empty. All consts mentioned here have
998
+ // already been added to the parent MIR's `required_consts` (that is computed before
999
+ // promotion), and no matter where this promoted const ends up, our parent MIR must be
1000
+ // somewhere in the reachable dependency chain so we can rely on its required consts being
1001
+ // evaluated.
927
1002
promoted. phase = MirPhase :: Analysis ( AnalysisPhase :: Initial ) ;
928
1003
929
1004
let promoter = Promoter {
@@ -933,6 +1008,10 @@ fn promote_candidates<'tcx>(
933
1008
temps : & mut temps,
934
1009
extra_statements : & mut extra_statements,
935
1010
keep_original : false ,
1011
+ // If we promote all functions, some of these promoteds could fail, so we better make
1012
+ // sure they get all checked as `required_consts`. Otherwise, as an optimization we
1013
+ // *don't* add the promoteds to the parent's `required_consts`.
1014
+ add_to_required : promote_all_fn ( ccx_const_kind) ,
936
1015
} ;
937
1016
938
1017
let mut promoted = promoter. promote_candidate ( candidate, promotions. len ( ) ) ;
0 commit comments