1111//! This pass assumes that every use is dominated by an
1212//! initialization and can otherwise silence errors, if
1313//! move analysis runs after promotion on broken MIR.
14+ #![ allow( unused) ]
1415
1516use either:: { Left , Right } ;
17+ use rustc_data_structures:: fx:: FxHashSet ;
1618use rustc_hir as hir;
1719use rustc_middle:: mir;
1820use rustc_middle:: mir:: visit:: { MutVisitor , MutatingUseContext , PlaceContext , Visitor } ;
@@ -175,6 +177,12 @@ fn collect_temps_and_candidates<'tcx>(
175177struct Validator < ' a , ' tcx > {
176178 ccx : & ' a ConstCx < ' a , ' tcx > ,
177179 temps : & ' a mut IndexSlice < Local , TempState > ,
180+ /// For backwards compatibility, we are promoting function calls in `const`/`static`
181+ /// initializers. But we want to avoid evaluating code that might panic and that otherwise would
182+ /// not have been evaluated, so we only promote such calls in basic blocks that are guaranteed
183+ /// to execute. In other words, we only promote such calls in basic blocks that are definitely
184+ /// not dead code. Here we cache the result of computing that set of basic blocks.
185+ promotion_safe_blocks : Option < FxHashSet < BasicBlock > > ,
178186}
179187
180188impl < ' a , ' tcx > std:: ops:: Deref for Validator < ' a , ' tcx > {
@@ -260,7 +268,9 @@ impl<'tcx> Validator<'_, 'tcx> {
260268 self . validate_rvalue ( rhs)
261269 }
262270 Right ( terminator) => match & terminator. kind {
263- TerminatorKind :: Call { func, args, .. } => self . validate_call ( func, args) ,
271+ TerminatorKind :: Call { func, args, .. } => {
272+ self . validate_call ( func, args, loc. block )
273+ }
264274 TerminatorKind :: Yield { .. } => Err ( Unpromotable ) ,
265275 kind => {
266276 span_bug ! ( terminator. source_info. span, "{:?} not promotable" , kind) ;
@@ -587,53 +597,78 @@ impl<'tcx> Validator<'_, 'tcx> {
587597 Ok ( ( ) )
588598 }
589599
600+ /// Computes the sets of blocks of this MIR that are definitely going to be executed
601+ /// if the function returns successfully. That makes it safe to promote calls in them
602+ /// that might fail.
603+ fn promotion_safe_blocks ( body : & mir:: Body < ' tcx > ) -> FxHashSet < BasicBlock > {
604+ let mut safe_blocks = FxHashSet :: default ( ) ;
605+ let mut safe_block = START_BLOCK ;
606+ loop {
607+ safe_blocks. insert ( safe_block) ;
608+ // Let's see if we can find another safe block.
609+ safe_block = match body. basic_blocks [ safe_block] . terminator ( ) . kind {
610+ TerminatorKind :: Goto { target } => target,
611+ TerminatorKind :: Call { target : Some ( target) , .. }
612+ | TerminatorKind :: Drop { target, .. } => {
613+ // This calls a function or the destructor. `target` does not get executed if
614+ // the callee loops or panics. But in both cases the const already fails to
615+ // evaluate, so we are fine considering `target` a safe block for promotion.
616+ target
617+ }
618+ TerminatorKind :: Assert { target, .. } => {
619+ // Similar to above, we only consider successful execution.
620+ target
621+ }
622+ _ => {
623+ // No next safe block.
624+ break ;
625+ }
626+ } ;
627+ }
628+ safe_blocks
629+ }
630+
631+ /// Returns whether the block is "safe" for promotion, which means it cannot be dead code.
632+ /// We use this to avoid promoting operations that can fail in dead code.
633+ fn is_promotion_safe_block ( & mut self , block : BasicBlock ) -> bool {
634+ let body = self . body ;
635+ let safe_blocks =
636+ self . promotion_safe_blocks . get_or_insert_with ( || Self :: promotion_safe_blocks ( body) ) ;
637+ safe_blocks. contains ( & block)
638+ }
639+
590640 fn validate_call (
591641 & mut self ,
592642 callee : & Operand < ' tcx > ,
593643 args : & [ Spanned < Operand < ' tcx > > ] ,
644+ block : BasicBlock ,
594645 ) -> Result < ( ) , Unpromotable > {
595- let fn_ty = callee. ty ( self . body , self . tcx ) ;
596-
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- }
610- }
611- }
612-
613- let is_const_fn = match * fn_ty. kind ( ) {
614- ty:: FnDef ( def_id, _) => self . tcx . is_const_fn_raw ( def_id) ,
615- _ => false ,
616- } ;
617- if !is_const_fn {
618- return Err ( Unpromotable ) ;
619- }
620-
646+ // Validate the operands. If they fail, there's no question -- we cannot promote.
621647 self . validate_operand ( callee) ?;
622648 for arg in args {
623649 self . validate_operand ( & arg. node ) ?;
624650 }
625651
626- Ok ( ( ) )
652+ // Functions marked `#[rustc_promotable]` are explicitly allowed to be promoted, so we can
653+ // accept them at this point.
654+ let fn_ty = callee. ty ( self . body , self . tcx ) ;
655+ if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
656+ if self . tcx . is_promotable_const_fn ( def_id) {
657+ return Ok ( ( ) ) ;
658+ }
659+ }
660+
661+ // Ideally, we'd stop here and reject the rest. So let's do that.
662+ return Err ( Unpromotable ) ;
627663 }
628664}
629665
630- // FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
631666fn validate_candidates (
632667 ccx : & ConstCx < ' _ , ' _ > ,
633668 temps : & mut IndexSlice < Local , TempState > ,
634669 candidates : & [ Candidate ] ,
635670) -> Vec < Candidate > {
636- let mut validator = Validator { ccx, temps } ;
671+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
637672
638673 candidates
639674 . iter ( )
@@ -652,6 +687,10 @@ struct Promoter<'a, 'tcx> {
652687 /// If true, all nested temps are also kept in the
653688 /// source MIR, not moved to the promoted MIR.
654689 keep_original : bool ,
690+
691+ /// If true, add the new const (the promoted) to the required_consts of the parent MIR.
692+ /// This is initially false and then set by the visitor when it encounters a `Call` terminator.
693+ add_to_required : bool ,
655694}
656695
657696impl < ' a , ' tcx > Promoter < ' a , ' tcx > {
@@ -754,6 +793,10 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
754793 TerminatorKind :: Call {
755794 mut func, mut args, call_source : desugar, fn_span, ..
756795 } => {
796+ // This promoted involves a function call, so it may fail to evaluate.
797+ // Let's make sure it is added to `required_consts` so that that failure cannot get lost.
798+ self . add_to_required = true ;
799+
757800 self . visit_operand ( & mut func, loc) ;
758801 for arg in & mut args {
759802 self . visit_operand ( & mut arg. node , loc) ;
@@ -788,7 +831,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
788831
789832 fn promote_candidate ( mut self , candidate : Candidate , next_promoted_id : usize ) -> Body < ' tcx > {
790833 let def = self . source . source . def_id ( ) ;
791- let mut rvalue = {
834+ let ( mut rvalue, promoted_op ) = {
792835 let promoted = & mut self . promoted ;
793836 let promoted_id = Promoted :: new ( next_promoted_id) ;
794837 let tcx = self . tcx ;
@@ -798,11 +841,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
798841 let args = tcx. erase_regions ( GenericArgs :: identity_for_item ( tcx, def) ) ;
799842 let uneval = mir:: UnevaluatedConst { def, args, promoted : Some ( promoted_id) } ;
800843
801- Operand :: Constant ( Box :: new ( ConstOperand {
802- span,
803- user_ty : None ,
804- const_ : Const :: Unevaluated ( uneval, ty) ,
805- } ) )
844+ ConstOperand { span, user_ty : None , const_ : Const :: Unevaluated ( uneval, ty) }
806845 } ;
807846
808847 let blocks = self . source . basic_blocks . as_mut ( ) ;
@@ -838,22 +877,26 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
838877 let promoted_ref = local_decls. push ( promoted_ref) ;
839878 assert_eq ! ( self . temps. push( TempState :: Unpromotable ) , promoted_ref) ;
840879
880+ let promoted_operand = promoted_operand ( ref_ty, span) ;
841881 let promoted_ref_statement = Statement {
842882 source_info : statement. source_info ,
843883 kind : StatementKind :: Assign ( Box :: new ( (
844884 Place :: from ( promoted_ref) ,
845- Rvalue :: Use ( promoted_operand ( ref_ty , span ) ) ,
885+ Rvalue :: Use ( Operand :: Constant ( Box :: new ( promoted_operand ) ) ) ,
846886 ) ) ) ,
847887 } ;
848888 self . extra_statements . push ( ( loc, promoted_ref_statement) ) ;
849889
850- Rvalue :: Ref (
851- tcx. lifetimes . re_erased ,
852- * borrow_kind,
853- Place {
854- local : mem:: replace ( & mut place. local , promoted_ref) ,
855- projection : List :: empty ( ) ,
856- } ,
890+ (
891+ Rvalue :: Ref (
892+ tcx. lifetimes . re_erased ,
893+ * borrow_kind,
894+ Place {
895+ local : mem:: replace ( & mut place. local , promoted_ref) ,
896+ projection : List :: empty ( ) ,
897+ } ,
898+ ) ,
899+ promoted_operand,
857900 )
858901 } ;
859902
@@ -865,6 +908,12 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
865908
866909 let span = self . promoted . span ;
867910 self . assign ( RETURN_PLACE , rvalue, span) ;
911+
912+ // Now that we did promotion, we know whether we'll want to add this to `required_consts`.
913+ if self . add_to_required {
914+ self . source . required_consts . push ( promoted_op) ;
915+ }
916+
868917 self . promoted
869918 }
870919}
@@ -880,6 +929,14 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
880929 * local = self . promote_temp ( * local) ;
881930 }
882931 }
932+
933+ fn visit_constant ( & mut self , constant : & mut ConstOperand < ' tcx > , _location : Location ) {
934+ if constant. const_ . is_required_const ( ) {
935+ self . promoted . required_consts . push ( * constant) ;
936+ }
937+
938+ // Skipping `super_constant` as the visitor is otherwise only looking for locals.
939+ }
883940}
884941
885942fn promote_candidates < ' tcx > (
@@ -933,8 +990,10 @@ fn promote_candidates<'tcx>(
933990 temps : & mut temps,
934991 extra_statements : & mut extra_statements,
935992 keep_original : false ,
993+ add_to_required : false ,
936994 } ;
937995
996+ // `required_consts` of the promoted itself gets filled while building the MIR body.
938997 let mut promoted = promoter. promote_candidate ( candidate, promotions. len ( ) ) ;
939998 promoted. source . promoted = Some ( promotions. next_index ( ) ) ;
940999 promotions. push ( promoted) ;
0 commit comments