1313//! move analysis runs after promotion on broken MIR.
1414
1515use either:: { Left , Right } ;
16+ use rustc_data_structures:: fx:: FxHashSet ;
1617use rustc_hir as hir;
1718use rustc_middle:: mir;
1819use rustc_middle:: mir:: visit:: { MutVisitor , MutatingUseContext , PlaceContext , Visitor } ;
@@ -175,6 +176,11 @@ fn collect_temps_and_candidates<'tcx>(
175176struct Validator < ' a , ' tcx > {
176177 ccx : & ' a ConstCx < ' a , ' tcx > ,
177178 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 > > ,
178184}
179185
180186impl < ' a , ' tcx > std:: ops:: Deref for Validator < ' a , ' tcx > {
@@ -260,7 +266,9 @@ impl<'tcx> Validator<'_, 'tcx> {
260266 self . validate_rvalue ( rhs)
261267 }
262268 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+ }
264272 TerminatorKind :: Yield { .. } => Err ( Unpromotable ) ,
265273 kind => {
266274 span_bug ! ( terminator. source_info. span, "{:?} not promotable" , kind) ;
@@ -587,42 +595,92 @@ impl<'tcx> Validator<'_, 'tcx> {
587595 Ok ( ( ) )
588596 }
589597
598+ /// Computes the sets of blocks of this MIR that are definitely going to be executed
599+ /// if the function returns successfully. That makes it safe to promote calls in them
600+ /// that might fail.
601+ fn promotion_safe_blocks ( body : & mir:: Body < ' tcx > ) -> FxHashSet < BasicBlock > {
602+ let mut safe_blocks = FxHashSet :: default ( ) ;
603+ let mut safe_block = START_BLOCK ;
604+ loop {
605+ safe_blocks. insert ( safe_block) ;
606+ // Let's see if we can find another safe block.
607+ safe_block = match body. basic_blocks [ safe_block] . terminator ( ) . kind {
608+ TerminatorKind :: Goto { target } => target,
609+ TerminatorKind :: Call { target : Some ( target) , .. }
610+ | TerminatorKind :: Drop { target, .. } => {
611+ // This calls a function or the destructor. `target` does not get executed if
612+ // the callee loops or panics. But in both cases the const already fails to
613+ // evaluate, so we are fine considering `target` a safe block for promotion.
614+ target
615+ }
616+ TerminatorKind :: Assert { target, .. } => {
617+ // Similar to above, we only consider successful execution.
618+ target
619+ }
620+ _ => {
621+ // No next safe block.
622+ break ;
623+ }
624+ } ;
625+ }
626+ safe_blocks
627+ }
628+
629+ /// Returns whether the block is "safe" for promotion, which means it cannot be dead code.
630+ fn is_promotion_safe_block ( & mut self , block : BasicBlock ) -> bool {
631+ let body = self . body ;
632+ let safe_blocks =
633+ self . promotion_safe_blocks . get_or_insert_with ( || Self :: promotion_safe_blocks ( body) ) ;
634+ safe_blocks. contains ( & block)
635+ }
636+
590637 fn validate_call (
591638 & mut self ,
592639 callee : & Operand < ' tcx > ,
593640 args : & [ Spanned < Operand < ' tcx > > ] ,
641+ block : BasicBlock ,
594642 ) -> Result < ( ) , Unpromotable > {
643+ // Validate the operands. If they fail, there's no question -- we cannot promote.
644+ self . validate_operand ( callee) ?;
645+ for arg in args {
646+ self . validate_operand ( & arg. node ) ?;
647+ }
648+
649+ // Functions marked `#[rustc_promotable]` are explicitly allowed to be promoted, so we can
650+ // accept them at this point.
595651 let fn_ty = callee. ty ( self . body , self . tcx ) ;
652+ if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
653+ if self . tcx . is_promotable_const_fn ( def_id) {
654+ return Ok ( ( ) ) ;
655+ }
656+ }
596657
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 ! (
658+ // Ideally, we'd stop here and reject the rest.
659+ // But for backward compatibility, we have to accept some promotion in const/static
660+ // initializers. Inline consts are explicitly excluded, they are more recent so we have no
661+ // backwards compatibility reason to allow more promotion inside of them.
662+ let promote_all_fn = matches ! (
600663 self . const_kind,
601664 Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
602665 ) ;
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- }
610- }
666+ if !promote_all_fn {
667+ return Err ( Unpromotable ) ;
611668 }
612-
669+ // Make sure the callee is a `const fn`.
613670 let is_const_fn = match * fn_ty. kind ( ) {
614671 ty:: FnDef ( def_id, _) => self . tcx . is_const_fn_raw ( def_id) ,
615672 _ => false ,
616673 } ;
617674 if !is_const_fn {
618675 return Err ( Unpromotable ) ;
619676 }
620-
621- self . validate_operand ( callee) ?;
622- for arg in args {
623- self . validate_operand ( & arg. node ) ?;
677+ // The problem is, this may promote calls to functions that panic.
678+ // We don't want to introduce compilation errors if there's a panic in a call in dead code.
679+ // So we ensure that this is not dead code.
680+ if !self . is_promotion_safe_block ( block) {
681+ return Err ( Unpromotable ) ;
624682 }
625-
683+ // This passed all checks, so let's accept.
626684 Ok ( ( ) )
627685 }
628686}
@@ -633,7 +691,7 @@ fn validate_candidates(
633691 temps : & mut IndexSlice < Local , TempState > ,
634692 candidates : & [ Candidate ] ,
635693) -> Vec < Candidate > {
636- let mut validator = Validator { ccx, temps } ;
694+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
637695
638696 candidates
639697 . iter ( )
0 commit comments