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 } ;
@@ -175,6 +176,11 @@ 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 otherwise would not have been
181
+ /// evaluated, so we only do thos in basic blocks that are guaranteed to evaluate. Here we cache
182
+ /// the result of computing that set of basic blocks.
183
+ promotion_safe_blocks : Option < FxHashSet < BasicBlock > > ,
178
184
}
179
185
180
186
impl < ' a , ' tcx > std:: ops:: Deref for Validator < ' a , ' tcx > {
@@ -260,7 +266,9 @@ impl<'tcx> Validator<'_, 'tcx> {
260
266
self . validate_rvalue ( rhs)
261
267
}
262
268
Right ( terminator) => match & terminator. kind {
263
- TerminatorKind :: Call { func, args, .. } => self . validate_call ( func, args) ,
269
+ TerminatorKind :: Call { func, args, .. } => {
270
+ self . validate_call ( func, args, loc. block )
271
+ }
264
272
TerminatorKind :: Yield { .. } => Err ( Unpromotable ) ,
265
273
kind => {
266
274
span_bug ! ( terminator. source_info. span, "{:?} not promotable" , kind) ;
@@ -564,42 +572,84 @@ impl<'tcx> Validator<'_, 'tcx> {
564
572
Ok ( ( ) )
565
573
}
566
574
575
+ /// Computes the sets of blocks of this MIR that are definitely going to be executed
576
+ /// if the function returns successfully. That makes it safe to promote calls in them
577
+ /// that might fail.
578
+ fn promotion_safe_blocks ( body : & mir:: Body < ' tcx > ) -> FxHashSet < BasicBlock > {
579
+ let mut safe_blocks = FxHashSet :: default ( ) ;
580
+ let mut safe_block = START_BLOCK ;
581
+ loop {
582
+ safe_blocks. insert ( safe_block) ;
583
+ // Let's see if we can find another safe block.
584
+ safe_block = match body. basic_blocks [ safe_block] . terminator ( ) . kind {
585
+ TerminatorKind :: Goto { target } => target,
586
+ TerminatorKind :: Call { target : Some ( target) , .. }
587
+ | TerminatorKind :: Drop { target, .. } => {
588
+ // This calls a function or the destructor. `target` does not get executed if
589
+ // the callee loops or panics. But in both cases the const already fails to
590
+ // evaluate, so we are fine considering `target` a safe block for promotion.
591
+ target
592
+ }
593
+ TerminatorKind :: Assert { target, .. } => {
594
+ // Similar to above, we only consider successful execution.
595
+ target
596
+ }
597
+ _ => {
598
+ // No next safe block.
599
+ break ;
600
+ }
601
+ } ;
602
+ }
603
+ safe_blocks
604
+ }
605
+
606
+ /// Returns whether the block is "safe" for promotion, which means it cannot be dead code.
607
+ fn is_promotion_safe_block ( & mut self , block : BasicBlock ) -> bool {
608
+ let body = self . body ;
609
+ let safe_blocks =
610
+ self . promotion_safe_blocks . get_or_insert_with ( || Self :: promotion_safe_blocks ( body) ) ;
611
+ safe_blocks. contains ( & block)
612
+ }
613
+
567
614
fn validate_call (
568
615
& mut self ,
569
616
callee : & Operand < ' tcx > ,
570
617
args : & [ Spanned < Operand < ' tcx > > ] ,
618
+ block : BasicBlock ,
571
619
) -> Result < ( ) , Unpromotable > {
620
+ // Validate the operands. If they fail, there's no question -- we cannot promote.
621
+ self . validate_operand ( callee) ?;
622
+ for arg in args {
623
+ self . validate_operand ( & arg. node ) ?;
624
+ }
625
+
626
+ // Functions marked `#[rustc_promotable]` are explicitly allowed to be promoted, so we can
627
+ // accept them at this point.
572
628
let fn_ty = callee. ty ( self . body , self . tcx ) ;
629
+ if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
630
+ if self . tcx . is_promotable_const_fn ( def_id) {
631
+ return Ok ( ( ) ) ;
632
+ }
633
+ }
573
634
574
- // Inside const/static items, we promote all (eligible) function calls.
575
- // Everywhere else, we require `#[rustc_promotable]` on the callee.
576
- let promote_all_const_fn = matches ! (
635
+ // Ideally, we'd stop here and reject the rest.
636
+ // But for backward compatibility, we have to accept some promotion in const/static
637
+ // initializers. Inline consts are explicitly excluded, they are more recent so we have no
638
+ // backwards compatibility reason to allow more promotion inside of them.
639
+ let promote_all_fn = matches ! (
577
640
self . const_kind,
578
641
Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
579
642
) ;
580
- if !promote_all_const_fn {
581
- if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
582
- // Never promote runtime `const fn` calls of
583
- // functions without `#[rustc_promotable]`.
584
- if !self . tcx . is_promotable_const_fn ( def_id) {
585
- return Err ( Unpromotable ) ;
586
- }
587
- }
588
- }
589
-
590
- let is_const_fn = match * fn_ty. kind ( ) {
591
- ty:: FnDef ( def_id, _) => self . tcx . is_const_fn_raw ( def_id) ,
592
- _ => false ,
593
- } ;
594
- if !is_const_fn {
643
+ if !promote_all_fn {
595
644
return Err ( Unpromotable ) ;
596
645
}
597
-
598
- self . validate_operand ( callee) ?;
599
- for arg in args {
600
- self . validate_operand ( & arg. node ) ?;
646
+ // The problem is, this may promote calls to functions that panic.
647
+ // We don't want to introduce compilation errors if there's a panic in a call in dead code.
648
+ // So we ensure that this is not dead code.
649
+ if !self . is_promotion_safe_block ( block) {
650
+ return Err ( Unpromotable ) ;
601
651
}
602
-
652
+ // This passed all checks, so let's accept.
603
653
Ok ( ( ) )
604
654
}
605
655
}
@@ -610,7 +660,7 @@ fn validate_candidates(
610
660
temps : & mut IndexSlice < Local , TempState > ,
611
661
candidates : & [ Candidate ] ,
612
662
) -> Vec < Candidate > {
613
- let mut validator = Validator { ccx, temps } ;
663
+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
614
664
615
665
candidates
616
666
. iter ( )
0 commit comments