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