Skip to content

Commit 1c0f175

Browse files
committed
Auto merge of #157910 - jdonszelmann:stalled-goal-rerun, r=lcnr
Dont rerun stalled goals if erased runs succeeded* \* Unless the succesful erased run was conditional on the state of opaque type storage, and the opaque type storage changed in a significant way r? @lcnr
2 parents 9e2abe0 + 1844738 commit 1c0f175

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;
@@ -284,6 +284,12 @@ where
284284
}
285285
}
286286

287+
#[derive(Debug, Clone, Copy)]
288+
enum RerunStalled {
289+
WontMakeProgress(Certainty),
290+
MayMakeProgress,
291+
}
292+
287293
impl<'a, D, I> EvalCtxt<'a, D>
288294
where
289295
D: SolverDelegate<Interner = I>,
@@ -482,6 +488,81 @@ where
482488
Ok(goal_evaluation)
483489
}
484490

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

542-
let (result, orig_values, canonical_goal) = 'retry_canonicalize: {
610+
let (result, orig_values, canonical_goal, succeeded_in_erased) = 'retry_canonicalize: {
543611
let skip_erased_attempt = if typing_mode.is_coherence() {
544612
true
545613
} else {
@@ -595,11 +663,23 @@ where
595663
match should_rerun {
596664
RerunDecision::Yes => debug!("rerunning in original typing mode"),
597665
RerunDecision::No => {
598-
break 'retry_canonicalize (canonical_result, orig_values, canonical_goal);
666+
break 'retry_canonicalize (
667+
canonical_result,
668+
orig_values,
669+
canonical_goal,
670+
SucceededInErased::Yes { accessed_opaques },
671+
);
599672
}
600673
RerunDecision::EagerlyPropagateToParent => {
601674
self.opaque_accesses.update(accessed_opaques)?;
602-
break 'retry_canonicalize (canonical_result, orig_values, canonical_goal);
675+
break 'retry_canonicalize (
676+
canonical_result,
677+
orig_values,
678+
canonical_goal,
679+
// If we're propagating up, we should never retry the goal.
680+
// That means `No` is fine to return, it doesn't really matter.
681+
SucceededInErased::No,
682+
);
603683
}
604684
}
605685
}
@@ -618,7 +698,7 @@ where
618698
"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:?}"
619699
);
620700

621-
(canonical_result, orig_values, canonical_goal)
701+
(canonical_result, orig_values, canonical_goal, SucceededInErased::No)
622702
};
623703

624704
debug!(?result);
@@ -714,6 +794,7 @@ where
714794
stalled_vars,
715795
sub_roots,
716796
stalled_certainty: certainty,
797+
previously_succeeded_in_erased: succeeded_in_erased,
717798
})
718799
}
719800
},

compiler/rustc_next_trait_solver/src/solve/mod.rs

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

0 commit comments

Comments
 (0)