rustc_type_ir/search_graph/
mod.rs

1//! The search graph is responsible for caching and cycle detection in the trait
2//! solver. Making sure that caching doesn't result in soundness bugs or unstable
3//! query results is very challenging and makes this one of the most-involved
4//! self-contained components of the compiler.
5//!
6//! We added fuzzing support to test its correctness. The fuzzers used to verify
7//! the current implementation can be found in <https://github.com/lcnr/search_graph_fuzz>.
8//!
9//! This is just a quick overview of the general design, please check out the relevant
10//! [rustc-dev-guide chapter](https://rustc-dev-guide.rust-lang.org/solve/caching.html) for
11//! more details. Caching is split between a global cache and the per-cycle `provisional_cache`.
12//! The global cache has to be completely unobservable, while the per-cycle cache may impact
13//! behavior as long as the resulting behavior is still correct.
14use std::cmp::Ordering;
15use std::collections::hash_map::Entry;
16use std::collections::{BTreeMap, btree_map};
17use std::fmt::Debug;
18use std::hash::Hash;
19use std::iter;
20use std::marker::PhantomData;
21
22use derive_where::derive_where;
23#[cfg(feature = "nightly")]
24use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext};
25use rustc_type_ir::data_structures::HashMap;
26use tracing::{debug, instrument};
27
28mod stack;
29use stack::{Stack, StackDepth, StackEntry};
30mod global_cache;
31use global_cache::CacheData;
32pub use global_cache::GlobalCache;
33
34/// The search graph does not simply use `Interner` directly
35/// to enable its fuzzing without having to stub the rest of
36/// the interner. We don't make this a super trait of `Interner`
37/// as users of the shared type library shouldn't have to care
38/// about `Input` and `Result` as they are implementation details
39/// of the search graph.
40pub trait Cx: Copy {
41    type Input: Debug + Eq + Hash + Copy;
42    type Result: Debug + Eq + Hash + Copy;
43
44    type DepNodeIndex;
45    type Tracked<T: Debug + Clone>: Debug;
46    fn mk_tracked<T: Debug + Clone>(
47        self,
48        data: T,
49        dep_node_index: Self::DepNodeIndex,
50    ) -> Self::Tracked<T>;
51    fn get_tracked<T: Debug + Clone>(self, tracked: &Self::Tracked<T>) -> T;
52    fn with_cached_task<T>(self, task: impl FnOnce() -> T) -> (T, Self::DepNodeIndex);
53
54    fn with_global_cache<R>(self, f: impl FnOnce(&mut GlobalCache<Self>) -> R) -> R;
55
56    fn evaluation_is_concurrent(&self) -> bool;
57}
58
59pub trait Delegate: Sized {
60    type Cx: Cx;
61    /// Whether to use the provisional cache. Set to `false` by a fuzzer when
62    /// validating the search graph.
63    const ENABLE_PROVISIONAL_CACHE: bool;
64    type ValidationScope;
65    /// Returning `Some` disables the global cache for the current goal.
66    ///
67    /// The `ValidationScope` is used when fuzzing the search graph to track
68    /// for which goals the global cache has been disabled. This is necessary
69    /// as we may otherwise ignore the global cache entry for some goal `G`
70    /// only to later use it, failing to detect a cycle goal and potentially
71    /// changing the result.
72    fn enter_validation_scope(
73        cx: Self::Cx,
74        input: <Self::Cx as Cx>::Input,
75    ) -> Option<Self::ValidationScope>;
76
77    const FIXPOINT_STEP_LIMIT: usize;
78
79    type ProofTreeBuilder;
80    fn inspect_is_noop(inspect: &mut Self::ProofTreeBuilder) -> bool;
81
82    const DIVIDE_AVAILABLE_DEPTH_ON_OVERFLOW: usize;
83
84    fn initial_provisional_result(
85        cx: Self::Cx,
86        kind: PathKind,
87        input: <Self::Cx as Cx>::Input,
88    ) -> <Self::Cx as Cx>::Result;
89    fn is_initial_provisional_result(
90        cx: Self::Cx,
91        kind: PathKind,
92        input: <Self::Cx as Cx>::Input,
93        result: <Self::Cx as Cx>::Result,
94    ) -> bool;
95    fn on_stack_overflow(cx: Self::Cx, input: <Self::Cx as Cx>::Input) -> <Self::Cx as Cx>::Result;
96    fn on_fixpoint_overflow(
97        cx: Self::Cx,
98        input: <Self::Cx as Cx>::Input,
99    ) -> <Self::Cx as Cx>::Result;
100
101    fn is_ambiguous_result(result: <Self::Cx as Cx>::Result) -> bool;
102    fn propagate_ambiguity(
103        cx: Self::Cx,
104        for_input: <Self::Cx as Cx>::Input,
105        from_result: <Self::Cx as Cx>::Result,
106    ) -> <Self::Cx as Cx>::Result;
107
108    fn compute_goal(
109        search_graph: &mut SearchGraph<Self>,
110        cx: Self::Cx,
111        input: <Self::Cx as Cx>::Input,
112        inspect: &mut Self::ProofTreeBuilder,
113    ) -> <Self::Cx as Cx>::Result;
114}
115
116/// In the initial iteration of a cycle, we do not yet have a provisional
117/// result. In the case we return an initial provisional result depending
118/// on the kind of cycle.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120#[cfg_attr(
121    feature = "nightly",
122    derive(Decodable_NoContext, Encodable_NoContext, HashStable_NoContext)
123)]
124pub enum PathKind {
125    /// A path consisting of only inductive/unproductive steps. Their initial
126    /// provisional result is `Err(NoSolution)`. We currently treat them as
127    /// `PathKind::Unknown` during coherence until we're fully confident in
128    /// our approach.
129    Inductive,
130    /// A path which is not be coinductive right now but we may want
131    /// to change of them to be so in the future. We return an ambiguous
132    /// result in this case to prevent people from relying on this.
133    Unknown,
134    /// A path with at least one coinductive step. Such cycles hold.
135    Coinductive,
136    /// A path which is treated as ambiguous. Once a path has this path kind
137    /// any other segment does not change its kind.
138    ///
139    /// This is currently only used when fuzzing to support negative reasoning.
140    /// For more details, see #143054.
141    ForcedAmbiguity,
142}
143
144impl PathKind {
145    /// Returns the path kind when merging `self` with `rest`.
146    ///
147    /// Given an inductive path `self` and a coinductive path `rest`,
148    /// the path `self -> rest` would be coinductive.
149    ///
150    /// This operation represents an ordering and would be equivalent
151    /// to `max(self, rest)`.
152    fn extend(self, rest: PathKind) -> PathKind {
153        match (self, rest) {
154            (PathKind::ForcedAmbiguity, _) | (_, PathKind::ForcedAmbiguity) => {
155                PathKind::ForcedAmbiguity
156            }
157            (PathKind::Coinductive, _) | (_, PathKind::Coinductive) => PathKind::Coinductive,
158            (PathKind::Unknown, _) | (_, PathKind::Unknown) => PathKind::Unknown,
159            (PathKind::Inductive, PathKind::Inductive) => PathKind::Inductive,
160        }
161    }
162}
163
164/// The kinds of cycles a cycle head was involved in.
165///
166/// This is used to avoid rerunning a cycle if there's
167/// just a single usage kind and the final result matches
168/// its provisional result.
169///
170/// While it tracks the amount of usages using `u32`, we only ever
171/// care whether there are any. We only count them to be able to ignore
172/// usages from irrelevant candidates while evaluating a goal.
173///
174/// This cares about how nested goals relied on a cycle head. It does
175/// not care about how frequently the nested goal relied on it.
176#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
177struct HeadUsages {
178    inductive: u32,
179    unknown: u32,
180    coinductive: u32,
181    forced_ambiguity: u32,
182}
183
184impl HeadUsages {
185    fn add_usage(&mut self, path: PathKind) {
186        match path {
187            PathKind::Inductive => self.inductive += 1,
188            PathKind::Unknown => self.unknown += 1,
189            PathKind::Coinductive => self.coinductive += 1,
190            PathKind::ForcedAmbiguity => self.forced_ambiguity += 1,
191        }
192    }
193
194    /// This adds the usages which occurred while computing a nested goal.
195    ///
196    /// We don't actually care about how frequently the nested goal relied
197    /// on its cycle heads, only whether it did.
198    fn add_usages_from_nested(&mut self, usages: HeadUsages) {
199        let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = usages;
200        self.inductive += if inductive == 0 { 0 } else { 1 };
201        self.unknown += if unknown == 0 { 0 } else { 1 };
202        self.coinductive += if coinductive == 0 { 0 } else { 1 };
203        self.forced_ambiguity += if forced_ambiguity == 0 { 0 } else { 1 };
204    }
205
206    fn ignore_usages(&mut self, usages: HeadUsages) {
207        let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = usages;
208        self.inductive = self.inductive.checked_sub(inductive).unwrap();
209        self.unknown = self.unknown.checked_sub(unknown).unwrap();
210        self.coinductive = self.coinductive.checked_sub(coinductive).unwrap();
211        self.forced_ambiguity = self.forced_ambiguity.checked_sub(forced_ambiguity).unwrap();
212    }
213
214    fn is_empty(self) -> bool {
215        let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = self;
216        inductive == 0 && unknown == 0 && coinductive == 0 && forced_ambiguity == 0
217    }
218}
219
220#[derive(Debug, Default)]
221pub struct CandidateHeadUsages {
222    usages: Option<Box<HashMap<StackDepth, HeadUsages>>>,
223}
224impl CandidateHeadUsages {
225    pub fn merge_usages(&mut self, other: CandidateHeadUsages) {
226        if let Some(other_usages) = other.usages {
227            if let Some(ref mut self_usages) = self.usages {
228                #[allow(rustc::potential_query_instability)]
229                for (head_index, head) in other_usages.into_iter() {
230                    let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = head;
231                    let self_usages = self_usages.entry(head_index).or_default();
232                    self_usages.inductive += inductive;
233                    self_usages.unknown += unknown;
234                    self_usages.coinductive += coinductive;
235                    self_usages.forced_ambiguity += forced_ambiguity;
236                }
237            } else {
238                self.usages = Some(other_usages);
239            }
240        }
241    }
242}
243
244#[derive(Debug, Clone, Copy)]
245struct AvailableDepth(usize);
246impl AvailableDepth {
247    /// Returns the remaining depth allowed for nested goals.
248    ///
249    /// This is generally simply one less than the current depth.
250    /// However, if we encountered overflow, we significantly reduce
251    /// the remaining depth of all nested goals to prevent hangs
252    /// in case there is exponential blowup.
253    fn allowed_depth_for_nested<D: Delegate>(
254        root_depth: AvailableDepth,
255        stack: &Stack<D::Cx>,
256    ) -> Option<AvailableDepth> {
257        if let Some(last) = stack.last() {
258            if last.available_depth.0 == 0 {
259                return None;
260            }
261
262            Some(if last.encountered_overflow {
263                AvailableDepth(last.available_depth.0 / D::DIVIDE_AVAILABLE_DEPTH_ON_OVERFLOW)
264            } else {
265                AvailableDepth(last.available_depth.0 - 1)
266            })
267        } else {
268            Some(root_depth)
269        }
270    }
271
272    /// Whether we're allowed to use a global cache entry which required
273    /// the given depth.
274    fn cache_entry_is_applicable(self, additional_depth: usize) -> bool {
275        self.0 >= additional_depth
276    }
277}
278
279#[derive(Clone, Copy, Debug)]
280struct CycleHead {
281    paths_to_head: PathsToNested,
282    /// If the `usages` are empty, the result of that head does not matter
283    /// for the current goal. However, we still don't completely drop this
284    /// cycle head as whether or not it exists impacts which queries we
285    /// access, so ignoring it would cause incremental compilation verification
286    /// failures or hide query cycles.
287    usages: HeadUsages,
288}
289
290/// All cycle heads a given goal depends on, ordered by their stack depth.
291///
292/// We also track all paths from this goal to that head. This is necessary
293/// when rebasing provisional cache results.
294#[derive(Clone, Debug, Default)]
295struct CycleHeads {
296    heads: BTreeMap<StackDepth, CycleHead>,
297}
298
299impl CycleHeads {
300    fn is_empty(&self) -> bool {
301        self.heads.is_empty()
302    }
303
304    fn highest_cycle_head(&self) -> (StackDepth, CycleHead) {
305        self.heads.last_key_value().map(|(k, v)| (*k, *v)).unwrap()
306    }
307
308    fn highest_cycle_head_index(&self) -> StackDepth {
309        self.opt_highest_cycle_head_index().unwrap()
310    }
311
312    fn opt_highest_cycle_head_index(&self) -> Option<StackDepth> {
313        self.heads.last_key_value().map(|(k, _)| *k)
314    }
315
316    fn opt_lowest_cycle_head_index(&self) -> Option<StackDepth> {
317        self.heads.first_key_value().map(|(k, _)| *k)
318    }
319
320    fn remove_highest_cycle_head(&mut self) -> CycleHead {
321        let last = self.heads.pop_last();
322        last.unwrap().1
323    }
324
325    fn insert(
326        &mut self,
327        head_index: StackDepth,
328        path_from_entry: impl Into<PathsToNested> + Copy,
329        usages: HeadUsages,
330    ) {
331        match self.heads.entry(head_index) {
332            btree_map::Entry::Vacant(entry) => {
333                entry.insert(CycleHead { paths_to_head: path_from_entry.into(), usages });
334            }
335            btree_map::Entry::Occupied(entry) => {
336                let head = entry.into_mut();
337                head.paths_to_head |= path_from_entry.into();
338                head.usages.add_usages_from_nested(usages);
339            }
340        }
341    }
342
343    fn ignore_usages(&mut self, head_index: StackDepth, usages: HeadUsages) {
344        self.heads.get_mut(&head_index).unwrap().usages.ignore_usages(usages)
345    }
346
347    fn iter(&self) -> impl Iterator<Item = (StackDepth, CycleHead)> + '_ {
348        self.heads.iter().map(|(k, v)| (*k, *v))
349    }
350}
351
352bitflags::bitflags! {
353    /// Tracks how nested goals have been accessed. This is necessary to disable
354    /// global cache entries if computing them would otherwise result in a cycle or
355    /// access a provisional cache entry.
356    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
357    pub struct PathsToNested: u8 {
358        /// The initial value when adding a goal to its own nested goals.
359        const EMPTY                      = 1 << 0;
360        const INDUCTIVE                  = 1 << 1;
361        const UNKNOWN                    = 1 << 2;
362        const COINDUCTIVE                = 1 << 3;
363        const FORCED_AMBIGUITY           = 1 << 4;
364    }
365}
366impl From<PathKind> for PathsToNested {
367    fn from(path: PathKind) -> PathsToNested {
368        match path {
369            PathKind::Inductive => PathsToNested::INDUCTIVE,
370            PathKind::Unknown => PathsToNested::UNKNOWN,
371            PathKind::Coinductive => PathsToNested::COINDUCTIVE,
372            PathKind::ForcedAmbiguity => PathsToNested::FORCED_AMBIGUITY,
373        }
374    }
375}
376impl PathsToNested {
377    /// The implementation of this function is kind of ugly. We check whether
378    /// there currently exist 'weaker' paths in the set, if so we upgrade these
379    /// paths to at least `path`.
380    #[must_use]
381    fn extend_with(mut self, path: PathKind) -> Self {
382        match path {
383            PathKind::Inductive => {
384                if self.intersects(PathsToNested::EMPTY) {
385                    self.remove(PathsToNested::EMPTY);
386                    self.insert(PathsToNested::INDUCTIVE);
387                }
388            }
389            PathKind::Unknown => {
390                if self.intersects(PathsToNested::EMPTY | PathsToNested::INDUCTIVE) {
391                    self.remove(PathsToNested::EMPTY | PathsToNested::INDUCTIVE);
392                    self.insert(PathsToNested::UNKNOWN);
393                }
394            }
395            PathKind::Coinductive => {
396                if self.intersects(
397                    PathsToNested::EMPTY | PathsToNested::INDUCTIVE | PathsToNested::UNKNOWN,
398                ) {
399                    self.remove(
400                        PathsToNested::EMPTY | PathsToNested::INDUCTIVE | PathsToNested::UNKNOWN,
401                    );
402                    self.insert(PathsToNested::COINDUCTIVE);
403                }
404            }
405            PathKind::ForcedAmbiguity => {
406                if self.intersects(
407                    PathsToNested::EMPTY
408                        | PathsToNested::INDUCTIVE
409                        | PathsToNested::UNKNOWN
410                        | PathsToNested::COINDUCTIVE,
411                ) {
412                    self.remove(
413                        PathsToNested::EMPTY
414                            | PathsToNested::INDUCTIVE
415                            | PathsToNested::UNKNOWN
416                            | PathsToNested::COINDUCTIVE,
417                    );
418                    self.insert(PathsToNested::FORCED_AMBIGUITY);
419                }
420            }
421        }
422
423        self
424    }
425
426    #[must_use]
427    fn extend_with_paths(self, path: PathsToNested) -> Self {
428        let mut new = PathsToNested::empty();
429        for p in path.iter_paths() {
430            new |= self.extend_with(p);
431        }
432        new
433    }
434
435    fn iter_paths(self) -> impl Iterator<Item = PathKind> {
436        let (PathKind::Inductive
437        | PathKind::Unknown
438        | PathKind::Coinductive
439        | PathKind::ForcedAmbiguity);
440        [PathKind::Inductive, PathKind::Unknown, PathKind::Coinductive, PathKind::ForcedAmbiguity]
441            .into_iter()
442            .filter(move |&p| self.contains(p.into()))
443    }
444}
445
446/// The nested goals of each stack entry and the path from the
447/// stack entry to that nested goal.
448///
449/// They are used when checking whether reevaluating a global cache
450/// would encounter a cycle or use a provisional cache entry given the
451/// current search graph state. We need to disable the global cache
452/// in this case as it could otherwise result in behavioral differences.
453/// Cycles can impact behavior. The cycle ABA may have different final
454/// results from a the cycle BAB depending on the cycle root.
455///
456/// We only start tracking nested goals once we've either encountered
457/// overflow or a solver cycle. This is a performance optimization to
458/// avoid tracking nested goals on the happy path.
459#[derive_where(Debug, Default, Clone; X: Cx)]
460struct NestedGoals<X: Cx> {
461    nested_goals: HashMap<X::Input, PathsToNested>,
462}
463impl<X: Cx> NestedGoals<X> {
464    fn is_empty(&self) -> bool {
465        self.nested_goals.is_empty()
466    }
467
468    fn insert(&mut self, input: X::Input, paths_to_nested: PathsToNested) {
469        match self.nested_goals.entry(input) {
470            Entry::Occupied(mut entry) => *entry.get_mut() |= paths_to_nested,
471            Entry::Vacant(entry) => drop(entry.insert(paths_to_nested)),
472        }
473    }
474
475    /// Adds the nested goals of a nested goal, given that the path `step_kind` from this goal
476    /// to the parent goal.
477    ///
478    /// If the path from this goal to the nested goal is inductive, the paths from this goal
479    /// to all nested goals of that nested goal are also inductive. Otherwise the paths are
480    /// the same as for the child.
481    fn extend_from_child(&mut self, step_kind: PathKind, nested_goals: &NestedGoals<X>) {
482        #[allow(rustc::potential_query_instability)]
483        for (input, paths_to_nested) in nested_goals.iter() {
484            let paths_to_nested = paths_to_nested.extend_with(step_kind);
485            self.insert(input, paths_to_nested);
486        }
487    }
488
489    #[cfg_attr(feature = "nightly", rustc_lint_query_instability)]
490    #[allow(rustc::potential_query_instability)]
491    fn iter(&self) -> impl Iterator<Item = (X::Input, PathsToNested)> + '_ {
492        self.nested_goals.iter().map(|(i, p)| (*i, *p))
493    }
494
495    fn contains(&self, input: X::Input) -> bool {
496        self.nested_goals.contains_key(&input)
497    }
498}
499
500/// A provisional result of an already computed goals which depends on other
501/// goals still on the stack.
502#[derive_where(Debug; X: Cx)]
503struct ProvisionalCacheEntry<X: Cx> {
504    /// Whether evaluating the goal encountered overflow. This is used to
505    /// disable the cache entry except if the last goal on the stack is
506    /// already involved in this cycle.
507    encountered_overflow: bool,
508    /// All cycle heads this cache entry depends on.
509    heads: CycleHeads,
510    /// The path from the highest cycle head to this goal. This differs from
511    /// `heads` which tracks the path to the cycle head *from* this goal.
512    path_from_head: PathKind,
513    result: X::Result,
514}
515
516/// The final result of evaluating a goal.
517///
518/// We reset `encountered_overflow` when reevaluating a goal,
519/// but need to track whether we've hit the recursion limit at
520/// all for correctness.
521///
522/// We've previously simply returned the final `StackEntry` but this
523/// made it easy to accidentally drop information from the previous
524/// evaluation.
525#[derive_where(Debug; X: Cx)]
526struct EvaluationResult<X: Cx> {
527    encountered_overflow: bool,
528    required_depth: usize,
529    heads: CycleHeads,
530    nested_goals: NestedGoals<X>,
531    result: X::Result,
532}
533
534impl<X: Cx> EvaluationResult<X> {
535    fn finalize(
536        final_entry: StackEntry<X>,
537        encountered_overflow: bool,
538        result: X::Result,
539    ) -> EvaluationResult<X> {
540        EvaluationResult {
541            encountered_overflow,
542            // Unlike `encountered_overflow`, we share `heads`, `required_depth`,
543            // and `nested_goals` between evaluations.
544            required_depth: final_entry.required_depth,
545            heads: final_entry.heads,
546            nested_goals: final_entry.nested_goals,
547            // We only care about the final result.
548            result,
549        }
550    }
551}
552
553pub struct SearchGraph<D: Delegate<Cx = X>, X: Cx = <D as Delegate>::Cx> {
554    root_depth: AvailableDepth,
555    stack: Stack<X>,
556    /// The provisional cache contains entries for already computed goals which
557    /// still depend on goals higher-up in the stack. We don't move them to the
558    /// global cache and track them locally instead. A provisional cache entry
559    /// is only valid until the result of one of its cycle heads changes.
560    provisional_cache: HashMap<X::Input, Vec<ProvisionalCacheEntry<X>>>,
561
562    _marker: PhantomData<D>,
563}
564
565/// While [`SearchGraph::update_parent_goal`] can be mostly shared between
566/// ordinary nested goals/global cache hits and provisional cache hits,
567/// using the provisional cache should not add any nested goals.
568///
569/// `nested_goals` are only used when checking whether global cache entries
570/// are applicable. This only cares about whether a goal is actually accessed.
571/// Given that the usage of the provisional cache is fully deterministic, we
572/// don't need to track the nested goals used while computing a provisional
573/// cache entry.
574enum UpdateParentGoalCtxt<'a, X: Cx> {
575    Ordinary(&'a NestedGoals<X>),
576    CycleOnStack(X::Input),
577    ProvisionalCacheHit,
578}
579
580impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
581    pub fn new(root_depth: usize) -> SearchGraph<D> {
582        Self {
583            root_depth: AvailableDepth(root_depth),
584            stack: Default::default(),
585            provisional_cache: Default::default(),
586            _marker: PhantomData,
587        }
588    }
589
590    /// Lazily update the stack entry for the parent goal.
591    /// This behavior is shared between actually evaluating goals
592    /// and using existing global cache entries to make sure they
593    /// have the same impact on the remaining evaluation.
594    fn update_parent_goal(
595        stack: &mut Stack<X>,
596        step_kind_from_parent: PathKind,
597        required_depth_for_nested: usize,
598        heads: impl Iterator<Item = (StackDepth, CycleHead)>,
599        encountered_overflow: bool,
600        context: UpdateParentGoalCtxt<'_, X>,
601    ) {
602        if let Some((parent_index, parent)) = stack.last_mut_with_index() {
603            parent.required_depth = parent.required_depth.max(required_depth_for_nested + 1);
604            parent.encountered_overflow |= encountered_overflow;
605
606            for (head_index, head) in heads {
607                if let Some(candidate_usages) = &mut parent.candidate_usages {
608                    candidate_usages
609                        .usages
610                        .get_or_insert_default()
611                        .entry(head_index)
612                        .or_default()
613                        .add_usages_from_nested(head.usages);
614                }
615                match head_index.cmp(&parent_index) {
616                    Ordering::Less => parent.heads.insert(
617                        head_index,
618                        head.paths_to_head.extend_with(step_kind_from_parent),
619                        head.usages,
620                    ),
621                    Ordering::Equal => {
622                        parent.usages.get_or_insert_default().add_usages_from_nested(head.usages);
623                    }
624                    Ordering::Greater => unreachable!(),
625                }
626            }
627            let parent_depends_on_cycle = match context {
628                UpdateParentGoalCtxt::Ordinary(nested_goals) => {
629                    parent.nested_goals.extend_from_child(step_kind_from_parent, nested_goals);
630                    !nested_goals.is_empty()
631                }
632                UpdateParentGoalCtxt::CycleOnStack(head) => {
633                    // We lookup provisional cache entries before detecting cycles.
634                    // We therefore can't use a global cache entry if it contains a cycle
635                    // whose head is in the provisional cache.
636                    parent.nested_goals.insert(head, step_kind_from_parent.into());
637                    true
638                }
639                UpdateParentGoalCtxt::ProvisionalCacheHit => true,
640            };
641            // Once we've got goals which encountered overflow or a cycle,
642            // we track all goals whose behavior may depend depend on these
643            // goals as this change may cause them to now depend on additional
644            // goals, resulting in new cycles. See the dev-guide for examples.
645            if parent_depends_on_cycle {
646                parent.nested_goals.insert(parent.input, PathsToNested::EMPTY);
647            }
648        }
649    }
650
651    pub fn is_empty(&self) -> bool {
652        if self.stack.is_empty() {
653            debug_assert!(self.provisional_cache.is_empty());
654            true
655        } else {
656            false
657        }
658    }
659
660    /// The number of goals currently in the search graph. This should only be
661    /// used for debugging purposes.
662    pub fn debug_current_depth(&self) -> usize {
663        self.stack.len()
664    }
665
666    /// Whether the path from `head` to the current stack entry is inductive or coinductive.
667    ///
668    /// The `step_kind_to_head` is used to add a single additional path segment to the path on
669    /// the stack which completes the cycle. This given an inductive step AB which then cycles
670    /// coinductively with A, we need to treat this cycle as coinductive.
671    fn cycle_path_kind(
672        stack: &Stack<X>,
673        step_kind_to_head: PathKind,
674        head: StackDepth,
675    ) -> PathKind {
676        stack.cycle_step_kinds(head).fold(step_kind_to_head, |curr, step| curr.extend(step))
677    }
678
679    pub fn enter_single_candidate(&mut self) {
680        let prev = self.stack.last_mut().unwrap().candidate_usages.replace(Default::default());
681        debug_assert!(prev.is_none(), "existing candidate_usages: {prev:?}");
682    }
683
684    pub fn finish_single_candidate(&mut self) -> CandidateHeadUsages {
685        self.stack.last_mut().unwrap().candidate_usages.take().unwrap()
686    }
687
688    pub fn ignore_candidate_head_usages(&mut self, usages: CandidateHeadUsages) {
689        if let Some(usages) = usages.usages {
690            let (entry_index, entry) = self.stack.last_mut_with_index().unwrap();
691            #[allow(rustc::potential_query_instability)]
692            for (head_index, usages) in usages.into_iter() {
693                if head_index == entry_index {
694                    entry.usages.unwrap().ignore_usages(usages);
695                } else {
696                    entry.heads.ignore_usages(head_index, usages);
697                }
698            }
699        }
700    }
701
702    pub fn evaluate_root_goal_for_proof_tree(
703        cx: X,
704        root_depth: usize,
705        input: X::Input,
706        inspect: &mut D::ProofTreeBuilder,
707    ) -> X::Result {
708        let mut this = SearchGraph::<D>::new(root_depth);
709        let available_depth = AvailableDepth(root_depth);
710        let step_kind_from_parent = PathKind::Inductive; // is never used
711        this.stack.push(StackEntry {
712            input,
713            step_kind_from_parent,
714            available_depth,
715            provisional_result: None,
716            required_depth: 0,
717            heads: Default::default(),
718            encountered_overflow: false,
719            usages: None,
720            candidate_usages: None,
721            nested_goals: Default::default(),
722        });
723        let evaluation_result = this.evaluate_goal_in_task(cx, input, inspect);
724        evaluation_result.result
725    }
726
727    /// Probably the most involved method of the whole solver.
728    ///
729    /// While goals get computed via `D::compute_goal`, this function handles
730    /// caching, overflow, and cycles.
731    #[instrument(level = "debug", skip(self, cx, inspect), ret)]
732    pub fn evaluate_goal(
733        &mut self,
734        cx: X,
735        input: X::Input,
736        step_kind_from_parent: PathKind,
737        inspect: &mut D::ProofTreeBuilder,
738    ) -> X::Result {
739        let Some(available_depth) =
740            AvailableDepth::allowed_depth_for_nested::<D>(self.root_depth, &self.stack)
741        else {
742            return self.handle_overflow(cx, input);
743        };
744
745        // We check the provisional cache before checking the global cache. This simplifies
746        // the implementation as we can avoid worrying about cases where both the global and
747        // provisional cache may apply, e.g. consider the following example
748        //
749        // - xxBA overflow
750        // - A
751        //     - BA cycle
752        //     - CB :x:
753        if let Some(result) = self.lookup_provisional_cache(input, step_kind_from_parent) {
754            return result;
755        }
756
757        // Lookup the global cache unless we're building proof trees or are currently
758        // fuzzing.
759        let validate_cache = if !D::inspect_is_noop(inspect) {
760            None
761        } else if let Some(scope) = D::enter_validation_scope(cx, input) {
762            // When validating the global cache we need to track the goals for which the
763            // global cache has been disabled as it may otherwise change the result for
764            // cyclic goals. We don't care about goals which are not on the current stack
765            // so it's fine to drop their scope eagerly.
766            self.lookup_global_cache_untracked(cx, input, step_kind_from_parent, available_depth)
767                .inspect(|expected| debug!(?expected, "validate cache entry"))
768                .map(|r| (scope, r))
769        } else if let Some(result) =
770            self.lookup_global_cache(cx, input, step_kind_from_parent, available_depth)
771        {
772            return result;
773        } else {
774            None
775        };
776
777        // Detect cycles on the stack. We do this after the global cache lookup to
778        // avoid iterating over the stack in case a goal has already been computed.
779        // This may not have an actual performance impact and we could reorder them
780        // as it may reduce the number of `nested_goals` we need to track.
781        if let Some(result) = self.check_cycle_on_stack(cx, input, step_kind_from_parent) {
782            debug_assert!(validate_cache.is_none(), "global cache and cycle on stack: {input:?}");
783            return result;
784        }
785
786        // Unfortunate, it looks like we actually have to compute this goal.
787        self.stack.push(StackEntry {
788            input,
789            step_kind_from_parent,
790            available_depth,
791            provisional_result: None,
792            required_depth: 0,
793            heads: Default::default(),
794            encountered_overflow: false,
795            usages: None,
796            candidate_usages: None,
797            nested_goals: Default::default(),
798        });
799
800        // This is for global caching, so we properly track query dependencies.
801        // Everything that affects the `result` should be performed within this
802        // `with_cached_task` closure. If computing this goal depends on something
803        // not tracked by the cache key and from outside of this anon task, it
804        // must not be added to the global cache. Notably, this is the case for
805        // trait solver cycles participants.
806        let (evaluation_result, dep_node) =
807            cx.with_cached_task(|| self.evaluate_goal_in_task(cx, input, inspect));
808
809        // We've finished computing the goal and have popped it from the stack,
810        // lazily update its parent goal.
811        Self::update_parent_goal(
812            &mut self.stack,
813            step_kind_from_parent,
814            evaluation_result.required_depth,
815            evaluation_result.heads.iter(),
816            evaluation_result.encountered_overflow,
817            UpdateParentGoalCtxt::Ordinary(&evaluation_result.nested_goals),
818        );
819        let result = evaluation_result.result;
820
821        // We're now done with this goal. We only add the root of cycles to the global cache.
822        // In case this goal is involved in a larger cycle add it to the provisional cache.
823        if evaluation_result.heads.is_empty() {
824            if let Some((_scope, expected)) = validate_cache {
825                // Do not try to move a goal into the cache again if we're testing
826                // the global cache.
827                assert_eq!(expected, evaluation_result.result, "input={input:?}");
828            } else if D::inspect_is_noop(inspect) {
829                self.insert_global_cache(cx, input, evaluation_result, dep_node)
830            }
831        } else if D::ENABLE_PROVISIONAL_CACHE {
832            debug_assert!(validate_cache.is_none(), "unexpected non-root: {input:?}");
833            let entry = self.provisional_cache.entry(input).or_default();
834            let EvaluationResult {
835                encountered_overflow,
836                required_depth: _,
837                heads,
838                nested_goals: _,
839                result,
840            } = evaluation_result;
841            let path_from_head = Self::cycle_path_kind(
842                &self.stack,
843                step_kind_from_parent,
844                heads.highest_cycle_head_index(),
845            );
846            let provisional_cache_entry =
847                ProvisionalCacheEntry { encountered_overflow, heads, path_from_head, result };
848            debug!(?provisional_cache_entry);
849            entry.push(provisional_cache_entry);
850        } else {
851            debug_assert!(validate_cache.is_none(), "unexpected non-root: {input:?}");
852        }
853
854        result
855    }
856
857    fn handle_overflow(&mut self, cx: X, input: X::Input) -> X::Result {
858        if let Some(last) = self.stack.last_mut() {
859            last.encountered_overflow = true;
860            // If computing a goal `B` depends on another goal `A` and
861            // `A` has a nested goal which overflows, then computing `B`
862            // at the same depth, but with `A` already on the stack,
863            // would encounter a solver cycle instead, potentially
864            // changing the result.
865            //
866            // We must therefore not use the global cache entry for `B` in that case.
867            // See tests/ui/traits/next-solver/cycles/hidden-by-overflow.rs
868            last.nested_goals.insert(last.input, PathsToNested::EMPTY);
869        }
870
871        debug!("encountered stack overflow");
872        D::on_stack_overflow(cx, input)
873    }
874
875    /// When reevaluating a goal with a changed provisional result, all provisional cache entry
876    /// which depend on this goal get invalidated.
877    ///
878    /// Note that we keep provisional cache entries which accessed this goal as a cycle head, but
879    /// don't depend on its value.
880    fn clear_dependent_provisional_results_for_rerun(&mut self) {
881        let rerun_index = self.stack.next_index();
882        #[allow(rustc::potential_query_instability)]
883        self.provisional_cache.retain(|_, entries| {
884            entries.retain(|entry| {
885                let (head_index, head) = entry.heads.highest_cycle_head();
886                head_index != rerun_index || head.usages.is_empty()
887            });
888            !entries.is_empty()
889        });
890    }
891
892    /// A necessary optimization to handle complex solver cycles. A provisional cache entry
893    /// relies on a set of cycle heads and the path towards these heads. When popping a cycle
894    /// head from the stack after we've finished computing it, we can't be sure that the
895    /// provisional cache entry is still applicable. We need to keep the cache entries to
896    /// prevent hangs.
897    ///
898    /// This can be thought of as pretending to reevaluate the popped head as nested goals
899    /// of this provisional result. For this to be correct, all cycles encountered while
900    /// we'd reevaluate the cycle head as a nested goal must keep the same cycle kind.
901    /// [rustc-dev-guide chapter](https://rustc-dev-guide.rust-lang.org/solve/caching.html).
902    ///
903    /// In case the popped cycle head failed to reach a fixpoint anything which depends on
904    /// its provisional result is invalid. Actually discarding provisional cache entries in
905    /// this case would cause hangs, so we instead change the result of dependant provisional
906    /// cache entries to also be ambiguous. This causes some undesirable ambiguity for nested
907    /// goals whose result doesn't actually depend on this cycle head, but that's acceptable
908    /// to me.
909    fn rebase_provisional_cache_entries(
910        &mut self,
911        stack_entry: &StackEntry<X>,
912        mut mutate_result: impl FnMut(X::Input, X::Result) -> X::Result,
913    ) {
914        let popped_head_index = self.stack.next_index();
915        #[allow(rustc::potential_query_instability)]
916        self.provisional_cache.retain(|&input, entries| {
917            entries.retain_mut(|entry| {
918                let ProvisionalCacheEntry {
919                    encountered_overflow: _,
920                    heads,
921                    path_from_head,
922                    result,
923                } = entry;
924                let popped_head = if heads.highest_cycle_head_index() == popped_head_index {
925                    heads.remove_highest_cycle_head()
926                } else {
927                    return true;
928                };
929
930                // We're rebasing an entry `e` over a head `p`. This head
931                // has a number of own heads `h` it depends on.
932                //
933                // This causes our provisional result to depend on the heads
934                // of `p` to avoid moving any goal which uses this cache entry to
935                // the global cache.
936                if popped_head.usages.is_empty() {
937                    // The result of `e` does not depend on the value of `p`. This we can
938                    // keep using the result of this provisional cache entry even if evaluating
939                    // `p` as a nested goal of `e` would have a different result.
940                    for (head_index, _) in stack_entry.heads.iter() {
941                        heads.insert(head_index, PathsToNested::EMPTY, HeadUsages::default());
942                    }
943                } else {
944                    // The entry `e` actually depends on the value of `p`. We need
945                    // to make sure that the value of `p` wouldn't change even if we
946                    // were to reevaluate it as a nested goal of `e` instead. For this
947                    // we check that the path kind of all paths `hph` remain the
948                    // same after rebasing.
949                    //
950                    // After rebasing the cycles `hph` will go through `e`. We need to make
951                    // sure that forall possible paths `hep`, `heph` is equal to `hph.`
952                    let ep = popped_head.paths_to_head;
953                    for (head_index, head) in stack_entry.heads.iter() {
954                        let ph = head.paths_to_head;
955                        let hp = Self::cycle_path_kind(
956                            &self.stack,
957                            stack_entry.step_kind_from_parent,
958                            head_index,
959                        );
960                        // We first validate that all cycles while computing `p` would stay
961                        // the same if we were to recompute it as a nested goal of `e`.
962                        let he = hp.extend(*path_from_head);
963                        for ph in ph.iter_paths() {
964                            let hph = hp.extend(ph);
965                            for ep in ep.iter_paths() {
966                                let hep = ep.extend(he);
967                                let heph = hep.extend(ph);
968                                if hph != heph {
969                                    return false;
970                                }
971                            }
972                        }
973
974                        // If so, all paths reached while computing `p` have to get added
975                        // the heads of `e` to make sure that rebasing `e` again also considers
976                        // them.
977                        let eph = ep.extend_with_paths(ph);
978                        heads.insert(head_index, eph, head.usages);
979                    }
980                }
981
982                let Some(head_index) = heads.opt_highest_cycle_head_index() else {
983                    return false;
984                };
985
986                // We now care about the path from the next highest cycle head to the
987                // provisional cache entry.
988                *path_from_head = path_from_head.extend(Self::cycle_path_kind(
989                    &self.stack,
990                    stack_entry.step_kind_from_parent,
991                    head_index,
992                ));
993                // Mutate the result of the provisional cache entry in case we did
994                // not reach a fixpoint.
995                *result = mutate_result(input, *result);
996                true
997            });
998            !entries.is_empty()
999        });
1000    }
1001
1002    fn lookup_provisional_cache(
1003        &mut self,
1004        input: X::Input,
1005        step_kind_from_parent: PathKind,
1006    ) -> Option<X::Result> {
1007        if !D::ENABLE_PROVISIONAL_CACHE {
1008            return None;
1009        }
1010
1011        let entries = self.provisional_cache.get(&input)?;
1012        for &ProvisionalCacheEntry { encountered_overflow, ref heads, path_from_head, result } in
1013            entries
1014        {
1015            let head_index = heads.highest_cycle_head_index();
1016            if encountered_overflow {
1017                // This check is overly strict and very subtle. We need to make sure that if
1018                // a global cache entry depends on some goal without adding it to its
1019                // `nested_goals`, that goal must never have an applicable provisional
1020                // cache entry to avoid incorrectly applying the cache entry.
1021                //
1022                // As we'd have to otherwise track literally all nested goals, we only
1023                // apply provisional cache entries which encountered overflow once the
1024                // current goal is already part of the same cycle. This check could be
1025                // improved but seems to be good enough for now.
1026                let last = self.stack.last().unwrap();
1027                if last.heads.opt_lowest_cycle_head_index().is_none_or(|lowest| lowest > head_index)
1028                {
1029                    continue;
1030                }
1031            }
1032
1033            // A provisional cache entry is only valid if the current path from its
1034            // highest cycle head to the goal is the same.
1035            if path_from_head
1036                == Self::cycle_path_kind(&self.stack, step_kind_from_parent, head_index)
1037            {
1038                Self::update_parent_goal(
1039                    &mut self.stack,
1040                    step_kind_from_parent,
1041                    0,
1042                    heads.iter(),
1043                    encountered_overflow,
1044                    UpdateParentGoalCtxt::ProvisionalCacheHit,
1045                );
1046                debug!(?head_index, ?path_from_head, "provisional cache hit");
1047                return Some(result);
1048            }
1049        }
1050
1051        None
1052    }
1053
1054    /// Even if there is a global cache entry for a given goal, we need to make sure
1055    /// evaluating this entry would not have ended up depending on either a goal
1056    /// already on the stack or a provisional cache entry.
1057    fn candidate_is_applicable(
1058        &self,
1059        step_kind_from_parent: PathKind,
1060        nested_goals: &NestedGoals<X>,
1061    ) -> bool {
1062        // If the global cache entry didn't depend on any nested goals, it always
1063        // applies.
1064        if nested_goals.is_empty() {
1065            return true;
1066        }
1067
1068        // If a nested goal of the global cache entry is on the stack, we would
1069        // definitely encounter a cycle.
1070        if self.stack.iter().any(|e| nested_goals.contains(e.input)) {
1071            debug!("cache entry not applicable due to stack");
1072            return false;
1073        }
1074
1075        // The global cache entry is also invalid if there's a provisional cache entry
1076        // would apply for any of its nested goals.
1077        #[allow(rustc::potential_query_instability)]
1078        for (input, path_from_global_entry) in nested_goals.iter() {
1079            let Some(entries) = self.provisional_cache.get(&input) else {
1080                continue;
1081            };
1082
1083            debug!(?input, ?path_from_global_entry, ?entries, "candidate_is_applicable");
1084            // A provisional cache entry is applicable if the path to
1085            // its highest cycle head is equal to the expected path.
1086            for &ProvisionalCacheEntry {
1087                encountered_overflow,
1088                ref heads,
1089                path_from_head: head_to_provisional,
1090                result: _,
1091            } in entries.iter()
1092            {
1093                // We don't have to worry about provisional cache entries which encountered
1094                // overflow, see the relevant comment in `lookup_provisional_cache`.
1095                if encountered_overflow {
1096                    continue;
1097                }
1098
1099                // A provisional cache entry only applies if the path from its highest head
1100                // matches the path when encountering the goal.
1101                //
1102                // We check if any of the paths taken while computing the global goal
1103                // would end up with an applicable provisional cache entry.
1104                let head_index = heads.highest_cycle_head_index();
1105                let head_to_curr =
1106                    Self::cycle_path_kind(&self.stack, step_kind_from_parent, head_index);
1107                let full_paths = path_from_global_entry.extend_with(head_to_curr);
1108                if full_paths.contains(head_to_provisional.into()) {
1109                    debug!(
1110                        ?full_paths,
1111                        ?head_to_provisional,
1112                        "cache entry not applicable due to matching paths"
1113                    );
1114                    return false;
1115                }
1116            }
1117        }
1118
1119        true
1120    }
1121
1122    /// Used when fuzzing the global cache. Accesses the global cache without
1123    /// updating the state of the search graph.
1124    fn lookup_global_cache_untracked(
1125        &self,
1126        cx: X,
1127        input: X::Input,
1128        step_kind_from_parent: PathKind,
1129        available_depth: AvailableDepth,
1130    ) -> Option<X::Result> {
1131        cx.with_global_cache(|cache| {
1132            cache
1133                .get(cx, input, available_depth, |nested_goals| {
1134                    self.candidate_is_applicable(step_kind_from_parent, nested_goals)
1135                })
1136                .map(|c| c.result)
1137        })
1138    }
1139
1140    /// Try to fetch a previously computed result from the global cache,
1141    /// making sure to only do so if it would match the result of reevaluating
1142    /// this goal.
1143    fn lookup_global_cache(
1144        &mut self,
1145        cx: X,
1146        input: X::Input,
1147        step_kind_from_parent: PathKind,
1148        available_depth: AvailableDepth,
1149    ) -> Option<X::Result> {
1150        cx.with_global_cache(|cache| {
1151            let CacheData { result, required_depth, encountered_overflow, nested_goals } = cache
1152                .get(cx, input, available_depth, |nested_goals| {
1153                    self.candidate_is_applicable(step_kind_from_parent, nested_goals)
1154                })?;
1155
1156            // We don't move cycle participants to the global cache, so the
1157            // cycle heads are always empty.
1158            let heads = iter::empty();
1159            Self::update_parent_goal(
1160                &mut self.stack,
1161                step_kind_from_parent,
1162                required_depth,
1163                heads,
1164                encountered_overflow,
1165                UpdateParentGoalCtxt::Ordinary(nested_goals),
1166            );
1167
1168            debug!(?required_depth, "global cache hit");
1169            Some(result)
1170        })
1171    }
1172
1173    fn check_cycle_on_stack(
1174        &mut self,
1175        cx: X,
1176        input: X::Input,
1177        step_kind_from_parent: PathKind,
1178    ) -> Option<X::Result> {
1179        let head_index = self.stack.find(input)?;
1180        // We have a nested goal which directly relies on a goal deeper in the stack.
1181        //
1182        // We start by tagging all cycle participants, as that's necessary for caching.
1183        //
1184        // Finally we can return either the provisional response or the initial response
1185        // in case we're in the first fixpoint iteration for this goal.
1186        let path_kind = Self::cycle_path_kind(&self.stack, step_kind_from_parent, head_index);
1187        debug!(?path_kind, "encountered cycle with depth {head_index:?}");
1188        let mut usages = HeadUsages::default();
1189        usages.add_usage(path_kind);
1190        let head = CycleHead { paths_to_head: step_kind_from_parent.into(), usages };
1191        Self::update_parent_goal(
1192            &mut self.stack,
1193            step_kind_from_parent,
1194            0,
1195            iter::once((head_index, head)),
1196            false,
1197            UpdateParentGoalCtxt::CycleOnStack(input),
1198        );
1199
1200        // Return the provisional result or, if we're in the first iteration,
1201        // start with no constraints.
1202        if let Some(result) = self.stack[head_index].provisional_result {
1203            Some(result)
1204        } else {
1205            Some(D::initial_provisional_result(cx, path_kind, input))
1206        }
1207    }
1208
1209    /// Whether we've reached a fixpoint when evaluating a cycle head.
1210    fn reached_fixpoint(
1211        &mut self,
1212        cx: X,
1213        stack_entry: &StackEntry<X>,
1214        usages: HeadUsages,
1215        result: X::Result,
1216    ) -> bool {
1217        let provisional_result = stack_entry.provisional_result;
1218        if usages.is_empty() {
1219            true
1220        } else if let Some(provisional_result) = provisional_result {
1221            provisional_result == result
1222        } else {
1223            let check = |k| D::is_initial_provisional_result(cx, k, stack_entry.input, result);
1224            match usages {
1225                HeadUsages { inductive: _, unknown: 0, coinductive: 0, forced_ambiguity: 0 } => {
1226                    check(PathKind::Inductive)
1227                }
1228                HeadUsages { inductive: 0, unknown: _, coinductive: 0, forced_ambiguity: 0 } => {
1229                    check(PathKind::Unknown)
1230                }
1231                HeadUsages { inductive: 0, unknown: 0, coinductive: _, forced_ambiguity: 0 } => {
1232                    check(PathKind::Coinductive)
1233                }
1234                HeadUsages { inductive: 0, unknown: 0, coinductive: 0, forced_ambiguity: _ } => {
1235                    check(PathKind::ForcedAmbiguity)
1236                }
1237                _ => false,
1238            }
1239        }
1240    }
1241
1242    /// When we encounter a coinductive cycle, we have to fetch the
1243    /// result of that cycle while we are still computing it. Because
1244    /// of this we continuously recompute the cycle until the result
1245    /// of the previous iteration is equal to the final result, at which
1246    /// point we are done.
1247    fn evaluate_goal_in_task(
1248        &mut self,
1249        cx: X,
1250        input: X::Input,
1251        inspect: &mut D::ProofTreeBuilder,
1252    ) -> EvaluationResult<X> {
1253        // We reset `encountered_overflow` each time we rerun this goal
1254        // but need to make sure we currently propagate it to the global
1255        // cache even if only some of the evaluations actually reach the
1256        // recursion limit.
1257        let mut encountered_overflow = false;
1258        let mut i = 0;
1259        loop {
1260            let result = D::compute_goal(self, cx, input, inspect);
1261            let stack_entry = self.stack.pop();
1262            encountered_overflow |= stack_entry.encountered_overflow;
1263            debug_assert_eq!(stack_entry.input, input);
1264
1265            // If the current goal is not a cycle head, we are done.
1266            //
1267            // There are no provisional cache entries which depend on this goal.
1268            let Some(usages) = stack_entry.usages else {
1269                return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1270            };
1271
1272            // If it is a cycle head, we have to keep trying to prove it until
1273            // we reach a fixpoint. We need to do so for all cycle heads,
1274            // not only for the root.
1275            //
1276            // See tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.rs
1277            // for an example.
1278            //
1279            // Check whether we reached a fixpoint, either because the final result
1280            // is equal to the provisional result of the previous iteration, or because
1281            // this was only the head of either coinductive or inductive cycles, and the
1282            // final result is equal to the initial response for that case.
1283            if self.reached_fixpoint(cx, &stack_entry, usages, result) {
1284                self.rebase_provisional_cache_entries(&stack_entry, |_, result| result);
1285                return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1286            }
1287
1288            // If computing this goal results in ambiguity with no constraints,
1289            // we do not rerun it. It's incredibly difficult to get a different
1290            // response in the next iteration in this case. These changes would
1291            // likely either be caused by incompleteness or can change the maybe
1292            // cause from ambiguity to overflow. Returning ambiguity always
1293            // preserves soundness and completeness even if the goal is be known
1294            // to succeed or fail.
1295            //
1296            // This prevents exponential blowup affecting multiple major crates.
1297            // As we only get to this branch if we haven't yet reached a fixpoint,
1298            // we also taint all provisional cache entries which depend on the
1299            // current goal.
1300            if D::is_ambiguous_result(result) {
1301                self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1302                    D::propagate_ambiguity(cx, input, result)
1303                });
1304                return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1305            };
1306
1307            // If we've reached the fixpoint step limit, we bail with overflow and taint all
1308            // provisional cache entries which depend on the current goal.
1309            i += 1;
1310            if i >= D::FIXPOINT_STEP_LIMIT {
1311                debug!("canonical cycle overflow");
1312                let result = D::on_fixpoint_overflow(cx, input);
1313                self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1314                    D::on_fixpoint_overflow(cx, input)
1315                });
1316                return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1317            }
1318
1319            // Clear all provisional cache entries which depend on a previous provisional
1320            // result of this goal and rerun.
1321            self.clear_dependent_provisional_results_for_rerun();
1322
1323            debug!(?result, "fixpoint changed provisional results");
1324            self.stack.push(StackEntry {
1325                input,
1326                step_kind_from_parent: stack_entry.step_kind_from_parent,
1327                available_depth: stack_entry.available_depth,
1328                provisional_result: Some(result),
1329                // We can keep these goals from previous iterations as they are only
1330                // ever read after finalizing this evaluation.
1331                required_depth: stack_entry.required_depth,
1332                heads: stack_entry.heads,
1333                nested_goals: stack_entry.nested_goals,
1334                // We reset these two fields when rerunning this goal. We could
1335                // keep `encountered_overflow` as it's only used as a performance
1336                // optimization. However, given that the proof tree will likely look
1337                // similar to the previous iterations when reevaluating, it's better
1338                // for caching if the reevaluation also starts out with `false`.
1339                encountered_overflow: false,
1340                usages: None,
1341                candidate_usages: None,
1342            });
1343        }
1344    }
1345
1346    /// When encountering a cycle, both inductive and coinductive, we only
1347    /// move the root into the global cache. We also store all other cycle
1348    /// participants involved.
1349    ///
1350    /// We must not use the global cache entry of a root goal if a cycle
1351    /// participant is on the stack. This is necessary to prevent unstable
1352    /// results. See the comment of `StackEntry::nested_goals` for
1353    /// more details.
1354    fn insert_global_cache(
1355        &mut self,
1356        cx: X,
1357        input: X::Input,
1358        evaluation_result: EvaluationResult<X>,
1359        dep_node: X::DepNodeIndex,
1360    ) {
1361        debug!(?evaluation_result, "insert global cache");
1362        cx.with_global_cache(|cache| cache.insert(cx, input, evaluation_result, dep_node))
1363    }
1364}