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,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) ;
@@ -588,53 +597,103 @@ impl<'tcx> Validator<'_, 'tcx> {
588
597
Ok ( ( ) )
589
598
}
590
599
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
+
591
640
fn validate_call (
592
641
& mut self ,
593
642
callee : & Operand < ' tcx > ,
594
643
args : & [ Spanned < Operand < ' tcx > > ] ,
644
+ block : BasicBlock ,
595
645
) -> Result < ( ) , Unpromotable > {
646
+ // Validate the operands. If they fail, there's no question -- we cannot promote.
647
+ self . validate_operand ( callee) ?;
648
+ for arg in args {
649
+ self . validate_operand ( & arg. node ) ?;
650
+ }
651
+
652
+ // Functions marked `#[rustc_promotable]` are explicitly allowed to be promoted, so we can
653
+ // accept them at this point.
596
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
+ }
597
660
598
- // Inside const/static items, we promote all (eligible) function calls.
599
- // Everywhere else, we require `#[rustc_promotable]` on the callee.
600
- let promote_all_const_fn = matches ! (
661
+ // Ideally, we'd stop here and reject the rest.
662
+ // But for backward compatibility, we have to accept some promotion in const/static
663
+ // initializers. Inline consts are explicitly excluded, they are more recent so we have no
664
+ // backwards compatibility reason to allow more promotion inside of them.
665
+ let promote_all_fn = matches ! (
601
666
self . const_kind,
602
667
Some ( hir:: ConstContext :: Static ( _) | hir:: ConstContext :: Const { inline: false } )
603
668
) ;
604
- if !promote_all_const_fn {
605
- if let ty:: FnDef ( def_id, _) = * fn_ty. kind ( ) {
606
- // Never promote runtime `const fn` calls of
607
- // functions without `#[rustc_promotable]`.
608
- if !self . tcx . is_promotable_const_fn ( def_id) {
609
- return Err ( Unpromotable ) ;
610
- }
611
- }
669
+ if !promote_all_fn {
670
+ return Err ( Unpromotable ) ;
612
671
}
613
-
672
+ // Make sure the callee is a `const fn`.
614
673
let is_const_fn = match * fn_ty. kind ( ) {
615
674
ty:: FnDef ( def_id, _) => self . tcx . is_const_fn_raw ( def_id) ,
616
675
_ => false ,
617
676
} ;
618
677
if !is_const_fn {
619
678
return Err ( Unpromotable ) ;
620
679
}
621
-
622
- self . validate_operand ( callee) ?;
623
- for arg in args {
624
- self . validate_operand ( & arg. node ) ?;
680
+ // The problem is, this may promote calls to functions that panic.
681
+ // We don't want to introduce compilation errors if there's a panic in a call in dead code.
682
+ // So we ensure that this is not dead code.
683
+ if !self . is_promotion_safe_block ( block) {
684
+ return Err ( Unpromotable ) ;
625
685
}
626
-
686
+ // This passed all checks, so let's accept.
627
687
Ok ( ( ) )
628
688
}
629
689
}
630
690
631
- // FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
632
691
fn validate_candidates (
633
692
ccx : & ConstCx < ' _ , ' _ > ,
634
693
temps : & mut IndexSlice < Local , TempState > ,
635
694
candidates : & [ Candidate ] ,
636
695
) -> Vec < Candidate > {
637
- let mut validator = Validator { ccx, temps } ;
696
+ let mut validator = Validator { ccx, temps, promotion_safe_blocks : None } ;
638
697
639
698
candidates
640
699
. iter ( )
@@ -653,6 +712,10 @@ struct Promoter<'a, 'tcx> {
653
712
/// If true, all nested temps are also kept in the
654
713
/// source MIR, not moved to the promoted MIR.
655
714
keep_original : bool ,
715
+
716
+ /// If true, add the new const (the promoted) to the required_consts of the parent MIR.
717
+ /// This is initially false and then set by the visitor when it encounters a `Call` terminator.
718
+ add_to_required : bool ,
656
719
}
657
720
658
721
impl < ' a , ' tcx > Promoter < ' a , ' tcx > {
@@ -755,6 +818,10 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
755
818
TerminatorKind :: Call {
756
819
mut func, mut args, call_source : desugar, fn_span, ..
757
820
} => {
821
+ // This promoted involves a function call, so it may fail to evaluate.
822
+ // Let's make sure it is added to `required_consts` so that that failure cannot get lost.
823
+ self . add_to_required = true ;
824
+
758
825
self . visit_operand ( & mut func, loc) ;
759
826
for arg in & mut args {
760
827
self . visit_operand ( & mut arg. node , loc) ;
@@ -789,7 +856,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
789
856
790
857
fn promote_candidate ( mut self , candidate : Candidate , next_promoted_id : usize ) -> Body < ' tcx > {
791
858
let def = self . source . source . def_id ( ) ;
792
- let mut rvalue = {
859
+ let ( mut rvalue, promoted_op ) = {
793
860
let promoted = & mut self . promoted ;
794
861
let promoted_id = Promoted :: new ( next_promoted_id) ;
795
862
let tcx = self . tcx ;
@@ -799,11 +866,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
799
866
let args = tcx. erase_regions ( GenericArgs :: identity_for_item ( tcx, def) ) ;
800
867
let uneval = mir:: UnevaluatedConst { def, args, promoted : Some ( promoted_id) } ;
801
868
802
- Operand :: Constant ( Box :: new ( ConstOperand {
803
- span,
804
- user_ty : None ,
805
- const_ : Const :: Unevaluated ( uneval, ty) ,
806
- } ) )
869
+ ConstOperand { span, user_ty : None , const_ : Const :: Unevaluated ( uneval, ty) }
807
870
} ;
808
871
809
872
let blocks = self . source . basic_blocks . as_mut ( ) ;
@@ -836,22 +899,26 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
836
899
let promoted_ref = local_decls. push ( promoted_ref) ;
837
900
assert_eq ! ( self . temps. push( TempState :: Unpromotable ) , promoted_ref) ;
838
901
902
+ let promoted_operand = promoted_operand ( ref_ty, span) ;
839
903
let promoted_ref_statement = Statement {
840
904
source_info : statement. source_info ,
841
905
kind : StatementKind :: Assign ( Box :: new ( (
842
906
Place :: from ( promoted_ref) ,
843
- Rvalue :: Use ( promoted_operand ( ref_ty , span ) ) ,
907
+ Rvalue :: Use ( Operand :: Constant ( Box :: new ( promoted_operand ) ) ) ,
844
908
) ) ) ,
845
909
} ;
846
910
self . extra_statements . push ( ( loc, promoted_ref_statement) ) ;
847
911
848
- Rvalue :: Ref (
849
- tcx. lifetimes . re_erased ,
850
- * borrow_kind,
851
- Place {
852
- local : mem:: replace ( & mut place. local , promoted_ref) ,
853
- projection : List :: empty ( ) ,
854
- } ,
912
+ (
913
+ Rvalue :: Ref (
914
+ tcx. lifetimes . re_erased ,
915
+ * borrow_kind,
916
+ Place {
917
+ local : mem:: replace ( & mut place. local , promoted_ref) ,
918
+ projection : List :: empty ( ) ,
919
+ } ,
920
+ ) ,
921
+ promoted_operand,
855
922
)
856
923
} ;
857
924
@@ -863,6 +930,12 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
863
930
864
931
let span = self . promoted . span ;
865
932
self . assign ( RETURN_PLACE , rvalue, span) ;
933
+
934
+ // Now that we did promotion, we know whether we'll want to add this to `required_consts`.
935
+ if self . add_to_required {
936
+ self . source . required_consts . push ( promoted_op) ;
937
+ }
938
+
866
939
self . promoted
867
940
}
868
941
}
@@ -878,6 +951,14 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
878
951
* local = self . promote_temp ( * local) ;
879
952
}
880
953
}
954
+
955
+ fn visit_constant ( & mut self , constant : & mut ConstOperand < ' tcx > , _location : Location ) {
956
+ if constant. const_ . is_required_const ( ) {
957
+ self . promoted . required_consts . push ( * constant) ;
958
+ }
959
+
960
+ // Skipping `super_constant` as the visitor is otherwise only looking for locals.
961
+ }
881
962
}
882
963
883
964
fn promote_candidates < ' tcx > (
@@ -931,8 +1012,10 @@ fn promote_candidates<'tcx>(
931
1012
temps : & mut temps,
932
1013
extra_statements : & mut extra_statements,
933
1014
keep_original : false ,
1015
+ add_to_required : false ,
934
1016
} ;
935
1017
1018
+ // `required_consts` of the promoted itself gets filled while building the MIR body.
936
1019
let mut promoted = promoter. promote_candidate ( candidate, promotions. len ( ) ) ;
937
1020
promoted. source . promoted = Some ( promotions. next_index ( ) ) ;
938
1021
promotions. push ( promoted) ;
0 commit comments