@@ -16,25 +16,36 @@ use std::fmt;
1616use rustc_ast:: Mutability ;
1717use rustc_hir:: def_id:: DefId ;
1818use rustc_middle:: mir:: AssertMessage ;
19- use rustc_session:: Limit ;
2019use rustc_span:: symbol:: { sym, Symbol } ;
2120use rustc_target:: abi:: { Align , Size } ;
2221use rustc_target:: spec:: abi:: Abi as CallAbi ;
2322
23+ use crate :: errors:: { LongRunning , LongRunningWarn } ;
2424use crate :: interpret:: {
2525 self , compile_time_machine, AllocId , ConstAllocation , FnVal , Frame , ImmTy , InterpCx ,
2626 InterpResult , OpTy , PlaceTy , Pointer , Scalar ,
2727} ;
2828
2929use super :: error:: * ;
3030
31+ /// When hitting this many interpreted terminators we emit a deny by default lint
32+ /// that notfies the user that their constant takes a long time to evaluate. If that's
33+ /// what they intended, they can just allow the lint.
34+ const LINT_TERMINATOR_LIMIT : usize = 2_000_000 ;
35+ /// The limit used by `-Z tiny-const-eval-limit`. This smaller limit is useful for internal
36+ /// tests not needing to run 30s or more to show some behaviour.
37+ const TINY_LINT_TERMINATOR_LIMIT : usize = 20 ;
38+ /// After this many interpreted terminators, we start emitting progress indicators at every
39+ /// power of two of interpreted terminators.
40+ const PROGRESS_INDICATOR_START : usize = 4_000_000 ;
41+
3142/// Extra machine state for CTFE, and the Machine instance
3243pub struct CompileTimeInterpreter < ' mir , ' tcx > {
33- /// For now, the number of terminators that can be evaluated before we throw a resource
34- /// exhaustion error.
44+ /// The number of terminators that have been evaluated.
3545 ///
36- /// Setting this to `0` disables the limit and allows the interpreter to run forever.
37- pub ( super ) steps_remaining : usize ,
46+ /// This is used to produce lints informing the user that the compiler is not stuck.
47+ /// Set to `usize::MAX` to never report anything.
48+ pub ( super ) num_evaluated_steps : usize ,
3849
3950 /// The virtual call stack.
4051 pub ( super ) stack : Vec < Frame < ' mir , ' tcx , AllocId , ( ) > > ,
@@ -72,13 +83,9 @@ impl CheckAlignment {
7283}
7384
7485impl < ' mir , ' tcx > CompileTimeInterpreter < ' mir , ' tcx > {
75- pub ( crate ) fn new (
76- const_eval_limit : Limit ,
77- can_access_statics : bool ,
78- check_alignment : CheckAlignment ,
79- ) -> Self {
86+ pub ( crate ) fn new ( can_access_statics : bool , check_alignment : CheckAlignment ) -> Self {
8087 CompileTimeInterpreter {
81- steps_remaining : const_eval_limit . 0 ,
88+ num_evaluated_steps : 0 ,
8289 stack : Vec :: new ( ) ,
8390 can_access_statics,
8491 check_alignment,
@@ -569,13 +576,54 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
569576
570577 fn increment_const_eval_counter ( ecx : & mut InterpCx < ' mir , ' tcx , Self > ) -> InterpResult < ' tcx > {
571578 // The step limit has already been hit in a previous call to `increment_const_eval_counter`.
572- if ecx. machine . steps_remaining == 0 {
573- return Ok ( ( ) ) ;
574- }
575579
576- ecx. machine . steps_remaining -= 1 ;
577- if ecx. machine . steps_remaining == 0 {
578- throw_exhaust ! ( StepLimitReached )
580+ if let Some ( new_steps) = ecx. machine . num_evaluated_steps . checked_add ( 1 ) {
581+ let ( limit, start) = if ecx. tcx . sess . opts . unstable_opts . tiny_const_eval_limit {
582+ ( TINY_LINT_TERMINATOR_LIMIT , TINY_LINT_TERMINATOR_LIMIT )
583+ } else {
584+ ( LINT_TERMINATOR_LIMIT , PROGRESS_INDICATOR_START )
585+ } ;
586+
587+ ecx. machine . num_evaluated_steps = new_steps;
588+ // By default, we have a *deny* lint kicking in after some time
589+ // to ensure `loop {}` doesn't just go forever.
590+ // In case that lint got reduced, in particular for `--cap-lint` situations, we also
591+ // have a hard warning shown every now and then for really long executions.
592+ if new_steps == limit {
593+ // By default, we stop after a million steps, but the user can disable this lint
594+ // to be able to run until the heat death of the universe or power loss, whichever
595+ // comes first.
596+ let hir_id = ecx. best_lint_scope ( ) ;
597+ let is_error = ecx
598+ . tcx
599+ . lint_level_at_node (
600+ rustc_session:: lint:: builtin:: LONG_RUNNING_CONST_EVAL ,
601+ hir_id,
602+ )
603+ . 0
604+ . is_error ( ) ;
605+ let span = ecx. cur_span ( ) ;
606+ ecx. tcx . emit_spanned_lint (
607+ rustc_session:: lint:: builtin:: LONG_RUNNING_CONST_EVAL ,
608+ hir_id,
609+ span,
610+ LongRunning { item_span : ecx. tcx . span } ,
611+ ) ;
612+ // If this was a hard error, don't bother continuing evaluation.
613+ if is_error {
614+ let guard = ecx
615+ . tcx
616+ . sess
617+ . delay_span_bug ( span, "The deny lint should have already errored" ) ;
618+ throw_inval ! ( AlreadyReported ( guard. into( ) ) ) ;
619+ }
620+ } else if new_steps > start && new_steps. is_power_of_two ( ) {
621+ // Only report after a certain number of terminators have been evaluated and the
622+ // current number of evaluated terminators is a power of 2. The latter gives us a cheap
623+ // way to implement exponential backoff.
624+ let span = ecx. cur_span ( ) ;
625+ ecx. tcx . sess . emit_warning ( LongRunningWarn { span, item_span : ecx. tcx . span } ) ;
626+ }
579627 }
580628
581629 Ok ( ( ) )
0 commit comments