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, MaybeInfo,
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<MaybeInfo>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for RerunStalled { }Copy)]
20pub(super) enum RerunStalled {
21    WontMakeProgress(MaybeInfo),
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(never)]
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    inlined_rerunning_stalled_goal_may_make_progress(delegate, stalled_on)
39}
40
41// Always-inlined variant for the one hot call site.
42#[inline(always)]
43pub(super) fn inlined_rerunning_stalled_goal_may_make_progress<D, I>(
44    delegate: &D,
45    stalled_on: Option<&GoalStalledOn<I>>,
46) -> RerunStalled
47where
48    D: SolverDelegate<Interner = I>,
49    I: Interner,
50{
51    use RerunStalled::*;
52
53    // If fast paths are turned off, then we assume all goals can always make progress
54    if delegate.disable_trait_solver_fast_paths() {
55        return MayMakeProgress;
56    }
57
58    // If the goal isn't stalled, we should definitely run it.
59    let Some(&GoalStalledOn { ref opaques, ref stalled_vars, ref sub_roots, stalled_maybe_info }) =
60        stalled_on
61    else {
62        return MayMakeProgress;
63    };
64
65    // If any of the stalled goal's generic arguments changed,
66    // rerunning might make progress so we should rerun.
67    if stalled_vars.iter().any(|value| delegate.ty_or_const_infer_var_changed(*value)) {
68        return MayMakeProgress;
69    }
70
71    // If some inference took place in any of the sub roots,
72    // rerunning might make progress so we should rerun.
73    if sub_roots.iter().any(|&vid| !delegate.is_sub_unification_table_root_var(vid)) {
74        return MayMakeProgress;
75    }
76
77    match opaques {
78        GoalStalledOnOpaques::No => {}
79        &GoalStalledOnOpaques::Yes {
80            num_opaques_in_storage,
81            ref previously_succeeded_in_erased,
82        } => {
83            // If any opaques changed in the opaque type storage,
84            // rerunning might make progress so we should rerun.
85            if delegate
86                .opaque_types_storage_num_entries()
87                .needs_reevaluation(num_opaques_in_storage)
88            {
89                // Unless this goal previously succeeded in erased mode.
90                // If the stalled goal successfully evaluated while erasing opaque types,
91                // and the current state of the opaque type storage is not different in a way that is
92                // relevant, this stalled goal cannot make any progress and we set this variable to true.
93                let mut previous_erased_run_is_still_valid = false;
94
95                if let &SucceededInErased::Yes { accessed_opaques } = previously_succeeded_in_erased
96                {
97                    match should_rerun_after_erased_canonicalization(
98                        accessed_opaques,
99                        delegate.typing_mode_raw(),
100                        &delegate.clone_opaque_types_lookup_table(),
101                    ) {
102                        RerunDecision::Yes => {}
103                        RerunDecision::EagerlyPropagateToParent => {
104                            {
    ::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")
105                        }
106                        RerunDecision::No => {
107                            previous_erased_run_is_still_valid = true;
108                        }
109                    }
110                }
111
112                if !previous_erased_run_is_still_valid {
113                    return MayMakeProgress;
114                }
115            }
116        }
117    }
118
119    // Otherwise, we can be sure that this stalled goal cannot make any progress
120    // and we can exit early.
121    WontMakeProgress(stalled_maybe_info)
122}
123
124/// `compute_goal_fast_path` is complicated enough that outling helps, so it gets optimized
125/// separately from the caller. `compute_goal_fast_path` is the inlined version,
126/// and most call sites (when adding goals) use it. However, when entering the root
127/// we also want to check the fast path, and there the outlining matters.
128///
129/// FIXME(perf) cold might not be worth it here, given that we shuffled some things around since it
130/// mattered.
131#[cold]
132#[inline(never)]
133pub(super) fn compute_goal_fast_path_cold<D, I>(
134    delegate: &D,
135    goal: Goal<I, I::Predicate>,
136    origin_span: I::Span,
137) -> Option<GoalEvaluation<I>>
138where
139    D: SolverDelegate<Interner = I>,
140    I: Interner,
141{
142    compute_goal_fast_path(delegate, goal, origin_span)
143}
144
145/// This is a fast path optimization:
146/// See the docs on [`ComputeGoalFastPathOutcome`]
147pub fn compute_goal_fast_path<D, I>(
148    delegate: &D,
149    goal: Goal<I, I::Predicate>,
150    origin_span: I::Span,
151) -> Option<GoalEvaluation<I>>
152where
153    D: SolverDelegate<Interner = I>,
154    I: Interner,
155{
156    if delegate.disable_trait_solver_fast_paths() {
157        return None;
158    }
159
160    match delegate.compute_goal_fast_path(goal, origin_span) {
161        ComputeGoalFastPathOutcome::NoFastPath => None,
162        ComputeGoalFastPathOutcome::TriviallyHolds => Some(GoalEvaluation {
163            goal,
164            certainty: Certainty::Yes,
165            has_changed: HasChanged::No,
166            stalled_on: None,
167        }),
168        ComputeGoalFastPathOutcome::TriviallyStalled { stalled_on } => Some(GoalEvaluation {
169            goal,
170            certainty: Certainty::AMBIGUOUS,
171            has_changed: HasChanged::No,
172            stalled_on: Some(stalled_on),
173        }),
174    }
175}