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) ;
@@ -564,42 +572,84 @@ impl<'tcx> Validator<'_, 'tcx> {
564572 Ok ( ( ) )
565573 }
566574
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+
567614 fn validate_call (
568615 & mut self ,
569616 callee : & Operand < ' tcx > ,
570617 args : & [ Spanned < Operand < ' tcx > > ] ,
618+ block : BasicBlock ,
571619 ) -> 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.
572628 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+ }
573634
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 ! (
577640 self . const_kind,
578641 Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
579642 ) ;
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 {
595644 return Err ( Unpromotable ) ;
596645 }
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 ) ;
601651 }
602-
652+ // This passed all checks, so let's accept.
603653 Ok ( ( ) )
604654 }
605655}
@@ -610,7 +660,7 @@ fn validate_candidates(
610660 temps : & mut IndexSlice < Local , TempState > ,
611661 candidates : & [ Candidate ] ,
612662) -> Vec < Candidate > {
613- let mut validator = Validator { ccx, temps } ;
663+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
614664
615665 candidates
616666 . iter ( )
0 commit comments