Skip to main content

rustc_next_trait_solver/solve/eval_ctxt/
fast_path.rs

1//! This file contains a number of standalone functions useful for taking _fast paths_ in the trait
2//! solver. The exact place where we check for these fast paths changes, and matters a lot for
3//! performance. Ideally we'd only check them in `evaluate_goal`, but when evaluating root goals
4//! we can check them earlier and save some time creating an `EvalCtxt` in the first place.
5//!
6//! For debugging, fast paths can be disabled using `-Zdisable-fast-paths`.
7
8use rustc_type_ir::inherent::*;
9use rustc_type_ir::solve::{
10    Certainty, ComputeGoalFastPathOutcome, Goal, GoalStalledOn, GoalStalledOnOpaques,
11    SucceededInErased,
12};
13use rustc_type_ir::{InferCtxtLike, Interner};
14
15use crate::delegate::SolverDelegate;
16use crate::solve::eval_ctxt::{RerunDecision, should_rerun_after_erased_canonicalization};
17use crate::solve::{GoalEvaluation, HasChanged};
18
19#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RerunStalled {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            RerunStalled::WontMakeProgress(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "WontMakeProgress", &__self_0),
            RerunStalled::MayMakeProgress =>
                ::core::fmt::Formatter::write_str(f, "MayMakeProgress"),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for RerunStalled {
    #[inline]
    fn clone(&self) -> RerunStalled {
        let _: ::core::clone::AssertParamIsClone<Certainty>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for RerunStalled { }Copy)]
20pub(super) enum RerunStalled {
21    WontMakeProgress(Certainty),
22    MayMakeProgress,
23}
24
25/// If we have run a goal before, and it was stalled, check that any of the goal's
26/// args have changed. This is a cheap way to determine that if we were to rerun this goal now,
27/// it will remain stalled since it'll canonicalize the same way and evaluation is pure.
28/// Therefore, we can skip this rerun
29#[inline]
30pub(super) fn rerunning_stalled_goal_may_make_progress<D, I>(
31    delegate: &D,
32    stalled_on: Option<&GoalStalledOn<I>>,
33) -> RerunStalled
34where
35    D: SolverDelegate<Interner = I>,
36    I: Interner,
37{
38    use RerunStalled::*;
39
40    // If fast paths are turned off, then we assume all goals can always make progress
41    if delegate.disable_trait_solver_fast_paths() {
42        return MayMakeProgress;
43    }
44
45    // If the goal isn't stalled, we should definitely run it.
46    let Some(&GoalStalledOn { ref opaques, ref stalled_vars, ref sub_roots, stalled_certainty }) =
47        stalled_on
48    else {
49        return MayMakeProgress;
50    };
51
52    // If any of the stalled goal's generic arguments changed,
53    // rerunning might make progress so we should rerun.
54    if stalled_vars.iter().any(|value| delegate.is_changed_arg(*value)) {
55        return MayMakeProgress;
56    }
57
58    // If some inference took place in any of the sub roots,
59    // rerunning might make progress so we should rerun.
60    if sub_roots.iter().any(|&vid| delegate.sub_unification_table_root_var(vid) != vid) {
61        return MayMakeProgress;
62    }
63
64    match opaques {
65        GoalStalledOnOpaques::No => {}
66        &GoalStalledOnOpaques::Yes {
67            num_opaques_in_storage,
68            ref previously_succeeded_in_erased,
69        } => {
70            // If any opaques changed in the opaque type storage,
71            // rerunning might make progress so we should rerun.
72            if delegate
73                .opaque_types_storage_num_entries()
74                .needs_reevaluation(num_opaques_in_storage)
75            {
76                // Unless this goal previously succeeded in erased mode.
77                // If the stalled goal successfully evaluated while erasing opaque types,
78                // and the current state of the opaque type storage is not different in a way that is
79                // relevant, this stalled goal cannot make any progress and we set this variable to true.
80                let mut previous_erased_run_is_still_valid = false;
81
82                if let &SucceededInErased::Yes { accessed_opaques } = previously_succeeded_in_erased
83                {
84                    match should_rerun_after_erased_canonicalization(
85                        accessed_opaques,
86                        delegate.typing_mode_raw(),
87                        &delegate.clone_opaque_types_lookup_table(),
88                    ) {
89                        RerunDecision::Yes => {}
90                        RerunDecision::EagerlyPropagateToParent => {
91                            {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("we never retry stalled queries if the parent was erased")));
}unreachable!("we never retry stalled queries if the parent was erased")
92                        }
93                        RerunDecision::No => {
94                            previous_erased_run_is_still_valid = true;
95                        }
96                    }
97                }
98
99                if !previous_erased_run_is_still_valid {
100                    return MayMakeProgress;
101                }
102            }
103        }
104    }
105
106    // Otherwise, we can be sure that this stalled goal cannot make any progress
107    // and we can exit early.
108    WontMakeProgress(stalled_certainty)
109}
110
111/// `compute_goal_fast_path` is complicated enough that outling helps, so it gets optimized
112/// separately from the caller. `compute_goal_fast_path` is the inlined version,
113/// and most call sites (when adding goals) use it. However, when entering the root
114/// we also want to check the fast path, and there the outlining matters.
115///
116/// FIXME(perf) cold might not be worth it here, given that we shuffled some things around since it
117/// mattered.
118#[cold]
119#[inline(never)]
120pub(super) fn compute_goal_fast_path_cold<D, I>(
121    delegate: &D,
122    goal: Goal<I, I::Predicate>,
123    origin_span: I::Span,
124) -> Option<GoalEvaluation<I>>
125where
126    D: SolverDelegate<Interner = I>,
127    I: Interner,
128{
129    compute_goal_fast_path(delegate, goal, origin_span)
130}
131
132/// This is a fast path optimization:
133/// See the docs on [`ComputeGoalFastPathOutcome`]
134pub fn compute_goal_fast_path<D, I>(
135    delegate: &D,
136    goal: Goal<I, I::Predicate>,
137    origin_span: I::Span,
138) -> Option<GoalEvaluation<I>>
139where
140    D: SolverDelegate<Interner = I>,
141    I: Interner,
142{
143    if delegate.disable_trait_solver_fast_paths() {
144        return None;
145    }
146
147    match delegate.compute_goal_fast_path(goal, origin_span) {
148        ComputeGoalFastPathOutcome::NoFastPath => None,
149        ComputeGoalFastPathOutcome::TriviallyHolds => Some(GoalEvaluation {
150            goal,
151            certainty: Certainty::Yes,
152            has_changed: HasChanged::No,
153            stalled_on: None,
154        }),
155        ComputeGoalFastPathOutcome::TriviallyStalled { stalled_on } => Some(GoalEvaluation {
156            goal,
157            certainty: Certainty::AMBIGUOUS,
158            has_changed: HasChanged::No,
159            stalled_on: Some(stalled_on),
160        }),
161    }
162}