Skip to content

Commit 3c7e14d

Browse files
committed
Auto merge of #157910 - jdonszelmann:stalled-goal-rerun, r=<try>
Dont rerun stalled goals if erased runs succeeded*
2 parents b998449 + 1844738 commit 3c7e14d

2 files changed

Lines changed: 109 additions & 21 deletions

File tree

  • compiler/rustc_next_trait_solver/src/solve

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ use crate::solve::ty::may_use_unstable_feature;
3535
use crate::solve::{
3636
CanonicalInput, CanonicalResponse, Certainty, ExternalConstraintsData, FIXPOINT_STEP_LIMIT,
3737
Goal, GoalEvaluation, GoalSource, GoalStalledOn, HasChanged, MaybeCause,
38-
NestedNormalizationGoals, NoSolution, QueryInput, QueryResult, Response, VisibleForLeakCheck,
39-
inspect,
38+
NestedNormalizationGoals, NoSolution, QueryInput, QueryResult, Response, SucceededInErased,
39+
VisibleForLeakCheck, inspect,
4040
};
4141

4242
mod probe;
@@ -276,6 +276,12 @@ where
276276
}
277277
}
278278

279+
#[derive(Debug, Clone, Copy)]
280+
enum RerunStalled {
281+
WontMakeProgress(Certainty),
282+
MayMakeProgress,
283+
}
284+
279285
impl<'a, D, I> EvalCtxt<'a, D>
280286
where
281287
D: SolverDelegate<Interner = I>,
@@ -474,6 +480,81 @@ where
474480
Ok(goal_evaluation)
475481
}
476482

483+
/// This is a fast path optimization:
484+
/// If we have run this goal before, and it was stalled, check that any of the goal's
485+
/// args have changed. This is a cheap way to determine that if we were to rerun this goal now,
486+
/// it will remain stalled since it'll canonicalize the same way and evaluation is pure.
487+
/// Therefore, we can skip this rerun
488+
fn rerunning_stalled_goal_may_make_progress(
489+
&self,
490+
stalled_on: Option<&GoalStalledOn<I>>,
491+
) -> RerunStalled {
492+
use RerunStalled::*;
493+
494+
// If fast paths are turned off, then we assume all goals can always make progress
495+
if self.delegate.disable_trait_solver_fast_paths() {
496+
return MayMakeProgress;
497+
}
498+
499+
// If the goal isn't stalled, we should definitely run it.
500+
let Some(&GoalStalledOn {
501+
num_opaques,
502+
ref stalled_vars,
503+
ref sub_roots,
504+
stalled_certainty,
505+
ref previously_succeeded_in_erased,
506+
}) = stalled_on
507+
else {
508+
return MayMakeProgress;
509+
};
510+
511+
// If any of the stalled goal's generic arguments changed,
512+
// rerunning might make progress so we should rerun.
513+
if stalled_vars.iter().any(|value| self.delegate.is_changed_arg(*value)) {
514+
return MayMakeProgress;
515+
}
516+
517+
// If some inference took place in any of the sub roots,
518+
// rerunning might make progress so we should rerun.
519+
if sub_roots.iter().any(|&vid| self.delegate.sub_unification_table_root_var(vid) != vid) {
520+
return MayMakeProgress;
521+
}
522+
523+
// If any opaques changed in the opaque type storage,
524+
// rerunning might make progress so we should rerun.
525+
if self.delegate.opaque_types_storage_num_entries().needs_reevaluation(num_opaques) {
526+
// Unless this goal previously succeeded in erased mode.
527+
// If the stalled goal successfully evaluated while erasing opaque types,
528+
// and the current state of the opaque type storage is not different in a way that is
529+
// relevant, this stalled goal cannot make any progress and we set this variable to true.
530+
let mut previous_erased_run_is_still_valid = false;
531+
532+
if let &SucceededInErased::Yes { accessed_opaques } = previously_succeeded_in_erased {
533+
match self.should_rerun_after_erased_canonicalization(
534+
accessed_opaques,
535+
self.typing_mode(),
536+
&self.delegate.clone_opaque_types_lookup_table(),
537+
) {
538+
RerunDecision::Yes => {}
539+
RerunDecision::EagerlyPropagateToParent => {
540+
unreachable!("we never retry stalled queries if the parent was erased")
541+
}
542+
RerunDecision::No => {
543+
previous_erased_run_is_still_valid = true;
544+
}
545+
}
546+
}
547+
548+
if !previous_erased_run_is_still_valid {
549+
return MayMakeProgress;
550+
}
551+
}
552+
553+
// Otherwise, we can be sure that this stalled goal cannot make any progress
554+
// and we can exit early.
555+
WontMakeProgress(stalled_certainty)
556+
}
557+
477558
/// Recursively evaluates `goal`, returning the nested goals in case
478559
/// the nested goal is a `NormalizesTo` goal.
479560
///
@@ -487,21 +568,8 @@ where
487568
goal: Goal<I, I::Predicate>,
488569
stalled_on: Option<GoalStalledOn<I>>,
489570
) -> Result<(NestedNormalizationGoals<I>, GoalEvaluation<I>), NoSolutionOrRerunNonErased> {
490-
// If we have run this goal before, and it was stalled, check that any of the goal's
491-
// args have changed. Otherwise, we don't need to re-run the goal because it'll remain
492-
// stalled, since it'll canonicalize the same way and evaluation is pure.
493-
if let Some(GoalStalledOn {
494-
num_opaques,
495-
ref stalled_vars,
496-
ref sub_roots,
497-
stalled_certainty,
498-
}) = stalled_on
499-
&& !self.delegate.disable_trait_solver_fast_paths()
500-
&& !stalled_vars.iter().any(|value| self.delegate.is_changed_arg(*value))
501-
&& !sub_roots
502-
.iter()
503-
.any(|&vid| self.delegate.sub_unification_table_root_var(vid) != vid)
504-
&& !self.delegate.opaque_types_storage_num_entries().needs_reevaluation(num_opaques)
571+
if let RerunStalled::WontMakeProgress(stalled_certainty) =
572+
self.rerunning_stalled_goal_may_make_progress(stalled_on.as_ref())
505573
{
506574
return Ok((
507575
NestedNormalizationGoals::empty(),
@@ -531,7 +599,7 @@ where
531599
)
532600
.entered();
533601

534-
let (result, orig_values, canonical_goal) = 'retry_canonicalize: {
602+
let (result, orig_values, canonical_goal, succeeded_in_erased) = 'retry_canonicalize: {
535603
let skip_erased_attempt = if typing_mode.is_coherence() {
536604
true
537605
} else {
@@ -587,11 +655,23 @@ where
587655
match should_rerun {
588656
RerunDecision::Yes => debug!("rerunning in original typing mode"),
589657
RerunDecision::No => {
590-
break 'retry_canonicalize (canonical_result, orig_values, canonical_goal);
658+
break 'retry_canonicalize (
659+
canonical_result,
660+
orig_values,
661+
canonical_goal,
662+
SucceededInErased::Yes { accessed_opaques },
663+
);
591664
}
592665
RerunDecision::EagerlyPropagateToParent => {
593666
self.opaque_accesses.update(accessed_opaques)?;
594-
break 'retry_canonicalize (canonical_result, orig_values, canonical_goal);
667+
break 'retry_canonicalize (
668+
canonical_result,
669+
orig_values,
670+
canonical_goal,
671+
// If we're propagating up, we should never retry the goal.
672+
// That means `No` is fine to return, it doesn't really matter.
673+
SucceededInErased::No,
674+
);
595675
}
596676
}
597677
}
@@ -610,7 +690,7 @@ where
610690
"we run without TypingMode::ErasedNotCoherence, so opaques are available, and we don't retry if the outer typing mode is ErasedNotCoherence: {accessed_opaques:?} after {goal:?}"
611691
);
612692

613-
(canonical_result, orig_values, canonical_goal)
693+
(canonical_result, orig_values, canonical_goal, SucceededInErased::No)
614694
};
615695

616696
debug!(?result);
@@ -718,6 +798,7 @@ where
718798
stalled_vars,
719799
sub_roots,
720800
stalled_certainty: certainty,
801+
previously_succeeded_in_erased: succeeded_in_erased,
721802
})
722803
}
723804
},

compiler/rustc_next_trait_solver/src/solve/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,11 @@ pub struct GoalStalledOn<I: Interner> {
442442
/// The certainty that will be returned on subsequent evaluations if this
443443
/// goal remains stalled.
444444
pub stalled_certainty: Certainty,
445+
pub previously_succeeded_in_erased: SucceededInErased<I>,
446+
}
447+
448+
#[derive_where(Clone, Debug; I: Interner)]
449+
pub enum SucceededInErased<I: Interner> {
450+
Yes { accessed_opaques: AccessedOpaques<I> },
451+
No,
445452
}

0 commit comments

Comments
 (0)