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}