@@ -19,6 +19,9 @@ rustc_index::newtype_index! {
1919}
2020
2121bitflags:: bitflags! {
22+ /// Whether and how this goal has been used as the root of a
23+ /// cycle. We track the kind of cycle as we're otherwise forced
24+ /// to always rerun at least once.
2225 #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
2326 struct HasBeenUsed : u8 {
2427 const INDUCTIVE_CYCLE = 1 << 0 ;
@@ -29,23 +32,30 @@ bitflags::bitflags! {
2932#[ derive( Debug ) ]
3033struct StackEntry < ' tcx > {
3134 input : CanonicalInput < ' tcx > ,
35+
3236 available_depth : Limit ,
37+
3338 /// The maximum depth reached by this stack entry, only up-to date
3439 /// for the top of the stack and lazily updated for the rest.
3540 reached_depth : StackDepth ,
36- /// Whether this entry is a cycle participant which is not a root.
41+
42+ /// Whether this entry is a non-root cycle participant.
3743 ///
38- /// If so, it must not be moved to the global cache. See
39- /// [SearchGraph::cycle_participants] for more details.
44+ /// We must not move the result of non-root cycle participants to the
45+ /// global cache. See [SearchGraph::cycle_participants] for more details.
46+ /// We store the highest stack depth of a head of a cycle this goal is involved
47+ /// in. This necessary to soundly cache its provisional result.
4048 non_root_cycle_participant : Option < StackDepth > ,
4149
4250 encountered_overflow : bool ,
51+
4352 has_been_used : HasBeenUsed ,
4453 /// Starts out as `None` and gets set when rerunning this
4554 /// goal in case we encounter a cycle.
4655 provisional_result : Option < QueryResult < ' tcx > > ,
4756}
4857
58+ /// The provisional result for a goal which is not on the stack.
4959struct DetachedEntry < ' tcx > {
5060 /// The head of the smallest non-trivial cycle involving this entry.
5161 ///
@@ -59,6 +69,16 @@ struct DetachedEntry<'tcx> {
5969 result : QueryResult < ' tcx > ,
6070}
6171
72+ /// Stores the stack depth of a currently evaluated goal *and* already
73+ /// computed results for goals which depend on other goals still on the stack.
74+ ///
75+ /// The provisional result may depend on whether the stack above it is inductive
76+ /// or coinductive. Because of this, we store separate provisional results for
77+ /// each case. If an provisional entry is not applicable, it may be the case
78+ /// that we already have provisional result while computing a goal. In this case
79+ /// we prefer the provisional result to potentially avoid fixpoint iterations.
80+ ///
81+ /// See tests/ui/traits/next-solver/cycles/mixed-cycle-2.rs for an example.
6282#[ derive( Default ) ]
6383struct ProvisionalCacheEntry < ' tcx > {
6484 stack_depth : Option < StackDepth > ,
@@ -200,6 +220,16 @@ impl<'tcx> SearchGraph<'tcx> {
200220 . all ( |entry| entry. input . value . goal . predicate . is_coinductive ( tcx) )
201221 }
202222
223+ // When encountering a solver cycle, the result of the current goal
224+ // depends on goals lower on the stack.
225+ //
226+ // We have to therefore be careful when caching goals. Only the final result
227+ // of the cycle root, i.e. the lowest goal on the stack involved in this cycle,
228+ // is moved to the global cache while all others are stored in a provisional cache.
229+ //
230+ // We update both the head of this cycle to rerun its evaluation until
231+ // we reach a fixpoint and all other cycle participants to make sure that
232+ // their result does not get moved to the global cache.
203233 fn tag_cycle_participants (
204234 stack : & mut IndexVec < StackDepth , StackEntry < ' tcx > > ,
205235 cycle_participants : & mut FxHashSet < CanonicalInput < ' tcx > > ,
@@ -281,24 +311,20 @@ impl<'tcx> SearchGraph<'tcx> {
281311 }
282312
283313 // Check whether the goal is in the provisional cache.
314+ // The provisional result may rely on the path to its cycle roots,
315+ // so we have to check the path of the current goal matches that of
316+ // the cache entry.
284317 let cache_entry = self . provisional_cache . entry ( input) . or_default ( ) ;
285- if let Some ( with_coinductive_stack) = & cache_entry. with_coinductive_stack
286- && Self :: stack_coinductive_from ( tcx, & self . stack , with_coinductive_stack. head )
287- {
288- // We have a nested goal which is already in the provisional cache, use
289- // its result. We do not provide any usage kind as that should have been
290- // already set correctly while computing the cache entry.
291- inspect
292- . goal_evaluation_kind ( inspect:: WipCanonicalGoalEvaluationKind :: ProvisionalCacheHit ) ;
293- Self :: tag_cycle_participants (
294- & mut self . stack ,
295- & mut self . cycle_participants ,
296- HasBeenUsed :: empty ( ) ,
297- with_coinductive_stack. head ,
298- ) ;
299- return with_coinductive_stack. result ;
300- } else if let Some ( with_inductive_stack) = & cache_entry. with_inductive_stack
301- && !Self :: stack_coinductive_from ( tcx, & self . stack , with_inductive_stack. head )
318+ if let Some ( entry) = cache_entry
319+ . with_coinductive_stack
320+ . as_ref ( )
321+ . filter ( |p| Self :: stack_coinductive_from ( tcx, & self . stack , p. head ) )
322+ . or_else ( || {
323+ cache_entry
324+ . with_inductive_stack
325+ . as_ref ( )
326+ . filter ( |p| !Self :: stack_coinductive_from ( tcx, & self . stack , p. head ) )
327+ } )
302328 {
303329 // We have a nested goal which is already in the provisional cache, use
304330 // its result. We do not provide any usage kind as that should have been
@@ -309,20 +335,17 @@ impl<'tcx> SearchGraph<'tcx> {
309335 & mut self . stack ,
310336 & mut self . cycle_participants ,
311337 HasBeenUsed :: empty ( ) ,
312- with_inductive_stack . head ,
338+ entry . head ,
313339 ) ;
314- return with_inductive_stack . result ;
340+ return entry . result ;
315341 } else if let Some ( stack_depth) = cache_entry. stack_depth {
316342 debug ! ( "encountered cycle with depth {stack_depth:?}" ) ;
317- // We have a nested goal which relies on a goal `root` deeper in the stack.
343+ // We have a nested goal which directly relies on a goal deeper in the stack.
318344 //
319- // We first store that we may have to reprove `root` in case the provisional
320- // response is not equal to the final response. We also update the depth of all
321- // goals which recursively depend on our current goal to depend on `root`
322- // instead.
345+ // We start by tagging all cycle participants, as that's necessary for caching.
323346 //
324- // Finally we can return either the provisional response for that goal if we have a
325- // coinductive cycle or an ambiguous result if the cycle is inductive .
347+ // Finally we can return either the provisional response or the initial response
348+ // in case we're in the first fixpoint iteration for this goal .
326349 inspect. goal_evaluation_kind ( inspect:: WipCanonicalGoalEvaluationKind :: CycleInStack ) ;
327350 let is_coinductive_cycle = Self :: stack_coinductive_from ( tcx, & self . stack , stack_depth) ;
328351 let usage_kind = if is_coinductive_cycle {
@@ -410,10 +433,10 @@ impl<'tcx> SearchGraph<'tcx> {
410433 false
411434 } ;
412435
436+ // If we did not reach a fixpoint, update the provisional result and reevaluate.
413437 if reached_fixpoint {
414438 return ( stack_entry, result) ;
415439 } else {
416- // Did not reach a fixpoint, update the provisional result and reevaluate.
417440 let depth = self . stack . push ( StackEntry {
418441 has_been_used : HasBeenUsed :: empty ( ) ,
419442 provisional_result : Some ( result) ,
@@ -435,9 +458,6 @@ impl<'tcx> SearchGraph<'tcx> {
435458 // We're now done with this goal. In case this goal is involved in a larger cycle
436459 // do not remove it from the provisional cache and update its provisional result.
437460 // We only add the root of cycles to the global cache.
438- //
439- // It is not possible for any nested goal to depend on something deeper on the
440- // stack, as this would have also updated the depth of the current goal.
441461 if let Some ( head) = final_entry. non_root_cycle_participant {
442462 let coinductive_stack = Self :: stack_coinductive_from ( tcx, & self . stack , head) ;
443463
@@ -449,6 +469,9 @@ impl<'tcx> SearchGraph<'tcx> {
449469 entry. with_inductive_stack = Some ( DetachedEntry { head, result } ) ;
450470 }
451471 } else {
472+ self . provisional_cache . remove ( & input) ;
473+ let reached_depth = final_entry. reached_depth . as_usize ( ) - self . stack . len ( ) ;
474+ let cycle_participants = mem:: take ( & mut self . cycle_participants ) ;
452475 // When encountering a cycle, both inductive and coinductive, we only
453476 // move the root into the global cache. We also store all other cycle
454477 // participants involved.
@@ -457,9 +480,6 @@ impl<'tcx> SearchGraph<'tcx> {
457480 // participant is on the stack. This is necessary to prevent unstable
458481 // results. See the comment of `SearchGraph::cycle_participants` for
459482 // more details.
460- self . provisional_cache . remove ( & input) ;
461- let reached_depth = final_entry. reached_depth . as_usize ( ) - self . stack . len ( ) ;
462- let cycle_participants = mem:: take ( & mut self . cycle_participants ) ;
463483 self . global_cache ( tcx) . insert (
464484 tcx,
465485 input,
0 commit comments