4 use self::cache::ProvisionalEntry;
5 use super::{CanonicalGoal, Certainty, MaybeCause, QueryResult};
6 use cache::ProvisionalCache;
7 use overflow::OverflowData;
8 use rustc_index::vec::IndexVec;
9 use rustc_middle::ty::TyCtxt;
10 use std::collections::hash_map::Entry;
12 rustc_index::newtype_index! {
13 pub struct StackDepth {}
16 struct StackElem<'tcx> {
17 goal: CanonicalGoal<'tcx>,
21 pub(super) struct SearchGraph<'tcx> {
22 /// The stack of goals currently being computed.
24 /// An element is *deeper* in the stack if its index is *lower*.
25 stack: IndexVec<StackDepth, StackElem<'tcx>>,
26 overflow_data: OverflowData,
27 provisional_cache: ProvisionalCache<'tcx>,
30 impl<'tcx> SearchGraph<'tcx> {
31 pub(super) fn new(tcx: TyCtxt<'tcx>) -> SearchGraph<'tcx> {
33 stack: Default::default(),
34 overflow_data: OverflowData::new(tcx),
35 provisional_cache: ProvisionalCache::empty(),
39 /// Tries putting the new goal on the stack, returning an error if it is already cached.
41 /// This correctly updates the provisional cache if there is a cycle.
42 pub(super) fn try_push_stack(
45 goal: CanonicalGoal<'tcx>,
46 ) -> Result<(), QueryResult<'tcx>> {
47 // FIXME: start by checking the global cache
49 // Look at the provisional cache to check for cycles.
50 let cache = &mut self.provisional_cache;
51 match cache.lookup_table.entry(goal) {
52 // No entry, simply push this goal on the stack after dealing with overflow.
54 if self.overflow_data.has_overflow(self.stack.len()) {
55 return Err(self.deal_with_overflow(tcx, goal));
58 let depth = self.stack.push(StackElem { goal, has_been_used: false });
59 let response = super::response_no_constraints(tcx, goal, Certainty::Yes);
60 let entry_index = cache.entries.push(ProvisionalEntry { response, depth, goal });
61 v.insert(entry_index);
64 // We have a nested goal which relies on a goal `root` deeper in the stack.
66 // We first store that we may have to rerun `evaluate_goal` for `root` in case the
67 // provisional response is not equal to the final response. We also update the depth
68 // of all goals which recursively depend on our current goal to depend on `root`
71 // Finally we can return either the provisional response for that goal if we have a
72 // coinductive cycle or an ambiguous result if the cycle is inductive.
73 Entry::Occupied(entry_index) => {
74 let entry_index = *entry_index.get();
76 cache.add_dependency_of_leaf_on(entry_index);
77 let stack_depth = cache.depth(entry_index);
79 self.stack[stack_depth].has_been_used = true;
80 // NOTE: The goals on the stack aren't the only goals involved in this cycle.
81 // We can also depend on goals which aren't part of the stack but coinductively
82 // depend on the stack themselves. We already checked whether all the goals
83 // between these goals and their root on the stack. This means that as long as
84 // each goal in a cycle is checked for coinductivity by itself, simply checking
85 // the stack is enough.
86 if self.stack.raw[stack_depth.index()..]
88 .all(|g| g.goal.value.predicate.is_coinductive(tcx))
90 Err(cache.provisional_result(entry_index))
92 Err(super::response_no_constraints(
95 Certainty::Maybe(MaybeCause::Overflow),
102 /// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with
103 /// coinductive cycles.
105 /// When we encounter a coinductive cycle, we have to prove the final result of that cycle
106 /// while we are still computing that result. Because of this we continously recompute the
107 /// cycle until the result of the previous iteration is equal to the final result, at which
108 /// point we are done.
110 /// This function returns `true` if we were able to finalize the goal and `false` if it has
111 /// updated the provisional cache and we have to recompute the current goal.
113 /// FIXME: Refer to the rustc-dev-guide entry once it exists.
114 pub(super) fn try_finalize_goal(
117 actual_goal: CanonicalGoal<'tcx>,
118 response: QueryResult<'tcx>,
120 let StackElem { goal, has_been_used } = self.stack.pop().unwrap();
121 assert_eq!(goal, actual_goal);
123 let cache = &mut self.provisional_cache;
124 let provisional_entry_index = *cache.lookup_table.get(&goal).unwrap();
125 let provisional_entry = &mut cache.entries[provisional_entry_index];
126 let depth = provisional_entry.depth;
127 // Was the current goal the root of a cycle and was the provisional response
128 // different from the final one.
129 if has_been_used && provisional_entry.response != response {
130 // If so, update the provisional reponse for this goal...
131 provisional_entry.response = response;
132 // ...remove all entries whose result depends on this goal
133 // from the provisional cache...
135 // That's not completely correct, as a nested goal can also
136 // depend on a goal which is lower in the stack so it doesn't
137 // actually depend on the current goal. This should be fairly
138 // rare and is hopefully not relevant for performance.
139 #[allow(rustc::potential_query_instability)]
140 cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index);
141 cache.entries.truncate(provisional_entry_index.index() + 1);
143 // ...and finally push our goal back on the stack and reevaluate it.
144 self.stack.push(StackElem { goal, has_been_used: false });
147 // If not, we're done with this goal.
149 // Check whether that this goal doesn't depend on a goal deeper on the stack
150 // and if so, move it and all nested goals to the global cache.
152 // Note that if any nested goal were to depend on something deeper on the stack,
153 // this would have also updated the depth of the current goal.
154 if depth == self.stack.next_index() {
155 for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..)
157 let actual_index = cache.lookup_table.remove(&entry.goal);
158 debug_assert_eq!(Some(i), actual_index);
159 debug_assert!(entry.depth == depth);
160 cache::try_move_finished_goal_to_global_cache(
162 &mut self.overflow_data,