@@ -448,6 +448,43 @@ struct ProvisionalCacheEntry<X: Cx> {
448448    result :  X :: Result , 
449449} 
450450
451+ /// The final result of evaluating a goal. 
452+ /// 
453+ /// We reset `encountered_overflow` when reevaluating a goal, 
454+ /// but need to track whether we've hit the recursion limit at 
455+ /// all for correctness. 
456+ /// 
457+ /// We've previously simply returned the final `StackEntry` but this 
458+ /// made it easy to accidentally drop information from the previous 
459+ /// evaluation. 
460+ #[ derive_where( Debug ;  X :  Cx ) ]  
461+ struct  EvaluationResult < X :  Cx >  { 
462+     encountered_overflow :  bool , 
463+     required_depth :  usize , 
464+     heads :  CycleHeads , 
465+     nested_goals :  NestedGoals < X > , 
466+     result :  X :: Result , 
467+ } 
468+ 
469+ impl < X :  Cx >  EvaluationResult < X >  { 
470+     fn  finalize ( 
471+         final_entry :  StackEntry < X > , 
472+         encountered_overflow :  bool , 
473+         result :  X :: Result , 
474+     )  -> EvaluationResult < X >  { 
475+         EvaluationResult  { 
476+             encountered_overflow, 
477+             // Unlike `encountered_overflow`, we share `heads`, `required_depth`, 
478+             // and `nested_goals` between evaluations. 
479+             required_depth :  final_entry. required_depth , 
480+             heads :  final_entry. heads , 
481+             nested_goals :  final_entry. nested_goals , 
482+             // We only care about the final result. 
483+             result, 
484+         } 
485+     } 
486+ } 
487+ 
451488pub  struct  SearchGraph < D :  Delegate < Cx  = X > ,  X :  Cx  = <D  as  Delegate >:: Cx >  { 
452489    root_depth :  AvailableDepth , 
453490    /// The stack of goals currently being computed. 
@@ -614,12 +651,12 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
614651            input, 
615652            step_kind_from_parent, 
616653            available_depth, 
654+             provisional_result :  None , 
617655            required_depth :  0 , 
618656            heads :  Default :: default ( ) , 
619657            encountered_overflow :  false , 
620658            has_been_used :  None , 
621659            nested_goals :  Default :: default ( ) , 
622-             provisional_result :  None , 
623660        } ) ; 
624661
625662        // This is for global caching, so we properly track query dependencies. 
@@ -628,35 +665,42 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
628665        // not tracked by the cache key and from outside of this anon task, it 
629666        // must not be added to the global cache. Notably, this is the case for 
630667        // trait solver cycles participants. 
631-         let  ( ( final_entry ,  result ) ,  dep_node)  = cx. with_cached_task ( || { 
668+         let  ( evaluation_result ,  dep_node)  = cx. with_cached_task ( || { 
632669            self . evaluate_goal_in_task ( cx,  input,  inspect,  & mut  evaluate_goal) 
633670        } ) ; 
634671
635672        // We've finished computing the goal and have popped it from the stack, 
636673        // lazily update its parent goal. 
637674        Self :: update_parent_goal ( 
638675            & mut  self . stack , 
639-             final_entry . step_kind_from_parent , 
640-             final_entry . required_depth , 
641-             & final_entry . heads , 
642-             final_entry . encountered_overflow , 
643-             UpdateParentGoalCtxt :: Ordinary ( & final_entry . nested_goals ) , 
676+             step_kind_from_parent, 
677+             evaluation_result . required_depth , 
678+             & evaluation_result . heads , 
679+             evaluation_result . encountered_overflow , 
680+             UpdateParentGoalCtxt :: Ordinary ( & evaluation_result . nested_goals ) , 
644681        ) ; 
682+         let  result = evaluation_result. result ; 
645683
646684        // We're now done with this goal. We only add the root of cycles to the global cache. 
647685        // In case this goal is involved in a larger cycle add it to the provisional cache. 
648-         if  final_entry . heads . is_empty ( )  { 
686+         if  evaluation_result . heads . is_empty ( )  { 
649687            if  let  Some ( ( _scope,  expected) )  = validate_cache { 
650688                // Do not try to move a goal into the cache again if we're testing 
651689                // the global cache. 
652-                 assert_eq ! ( result,  expected,  "input={input:?}" ) ; 
690+                 assert_eq ! ( evaluation_result . result,  expected,  "input={input:?}" ) ; 
653691            }  else  if  D :: inspect_is_noop ( inspect)  { 
654-                 self . insert_global_cache ( cx,  final_entry ,  result ,  dep_node) 
692+                 self . insert_global_cache ( cx,  input ,  evaluation_result ,  dep_node) 
655693            } 
656694        }  else  if  D :: ENABLE_PROVISIONAL_CACHE  { 
657695            debug_assert ! ( validate_cache. is_none( ) ,  "unexpected non-root: {input:?}" ) ; 
658696            let  entry = self . provisional_cache . entry ( input) . or_default ( ) ; 
659-             let  StackEntry  {  heads,  encountered_overflow,  .. }  = final_entry; 
697+             let  EvaluationResult  { 
698+                 encountered_overflow, 
699+                 required_depth :  _, 
700+                 heads, 
701+                 nested_goals :  _, 
702+                 result, 
703+             }  = evaluation_result; 
660704            let  path_from_head = Self :: cycle_path_kind ( 
661705                & self . stack , 
662706                step_kind_from_parent, 
@@ -1022,18 +1066,24 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
10221066        input :  X :: Input , 
10231067        inspect :  & mut  D :: ProofTreeBuilder , 
10241068        mut  evaluate_goal :  impl  FnMut ( & mut  Self ,  & mut  D :: ProofTreeBuilder )  -> X :: Result , 
1025-     )  -> ( StackEntry < X > ,  X :: Result )  { 
1069+     )  -> EvaluationResult < X >  { 
1070+         // We reset `encountered_overflow` each time we rerun this goal 
1071+         // but need to make sure we currently propagate it to the global 
1072+         // cache even if only some of the evaluations actually reach the 
1073+         // recursion limit. 
1074+         let  mut  encountered_overflow = false ; 
10261075        let  mut  i = 0 ; 
10271076        loop  { 
10281077            let  result = evaluate_goal ( self ,  inspect) ; 
10291078            let  stack_entry = self . stack . pop ( ) ; 
1079+             encountered_overflow |= stack_entry. encountered_overflow ; 
10301080            debug_assert_eq ! ( stack_entry. input,  input) ; 
10311081
10321082            // If the current goal is not the root of a cycle, we are done. 
10331083            // 
10341084            // There are no provisional cache entries which depend on this goal. 
10351085            let  Some ( usage_kind)  = stack_entry. has_been_used  else  { 
1036-                 return  ( stack_entry,  result) ; 
1086+                 return  EvaluationResult :: finalize ( stack_entry,  encountered_overflow ,  result) ; 
10371087            } ; 
10381088
10391089            // If it is a cycle head, we have to keep trying to prove it until 
@@ -1049,7 +1099,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
10491099            // final result is equal to the initial response for that case. 
10501100            if  self . reached_fixpoint ( cx,  & stack_entry,  usage_kind,  result)  { 
10511101                self . rebase_provisional_cache_entries ( & stack_entry,  |_,  result| result) ; 
1052-                 return  ( stack_entry,  result) ; 
1102+                 return  EvaluationResult :: finalize ( stack_entry,  encountered_overflow ,  result) ; 
10531103            } 
10541104
10551105            // If computing this goal results in ambiguity with no constraints, 
@@ -1068,7 +1118,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
10681118                self . rebase_provisional_cache_entries ( & stack_entry,  |input,  _| { 
10691119                    D :: propagate_ambiguity ( cx,  input,  result) 
10701120                } ) ; 
1071-                 return  ( stack_entry,  result) ; 
1121+                 return  EvaluationResult :: finalize ( stack_entry,  encountered_overflow ,  result) ; 
10721122            } ; 
10731123
10741124            // If we've reached the fixpoint step limit, we bail with overflow and taint all 
@@ -1080,7 +1130,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
10801130                self . rebase_provisional_cache_entries ( & stack_entry,  |input,  _| { 
10811131                    D :: on_fixpoint_overflow ( cx,  input) 
10821132                } ) ; 
1083-                 return  ( stack_entry,  result) ; 
1133+                 return  EvaluationResult :: finalize ( stack_entry,  encountered_overflow ,  result) ; 
10841134            } 
10851135
10861136            // Clear all provisional cache entries which depend on a previous provisional 
@@ -1089,9 +1139,22 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
10891139
10901140            debug ! ( ?result,  "fixpoint changed provisional results" ) ; 
10911141            self . stack . push ( StackEntry  { 
1092-                 has_been_used :  None , 
1142+                 input, 
1143+                 step_kind_from_parent :  stack_entry. step_kind_from_parent , 
1144+                 available_depth :  stack_entry. available_depth , 
10931145                provisional_result :  Some ( result) , 
1094-                 ..stack_entry
1146+                 // We can keep these goals from previous iterations as they are only 
1147+                 // ever read after finalizing this evaluation. 
1148+                 required_depth :  stack_entry. required_depth , 
1149+                 heads :  stack_entry. heads , 
1150+                 nested_goals :  stack_entry. nested_goals , 
1151+                 // We reset these two fields when rerunning this goal. We could 
1152+                 // keep `encountered_overflow` as it's only used as a performance 
1153+                 // optimization. However, given that the proof tree will likely 
1154+                 // look similar to the previous iterations when reevaluating, it's 
1155+                 // better for caching if the reevaluation also starts out with `false` 
1156+                 encountered_overflow :  false , 
1157+                 has_been_used :  None , 
10951158            } ) ; 
10961159        } 
10971160    } 
@@ -1107,21 +1170,11 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
11071170fn  insert_global_cache ( 
11081171        & mut  self , 
11091172        cx :  X , 
1110-         final_entry :   StackEntry < X > , 
1111-         result :   X :: Result , 
1173+         input :   X :: Input , 
1174+         evaluation_result :   EvaluationResult < X > , 
11121175        dep_node :  X :: DepNodeIndex , 
11131176    )  { 
1114-         debug ! ( ?final_entry,  ?result,  "insert global cache" ) ; 
1115-         cx. with_global_cache ( |cache| { 
1116-             cache. insert ( 
1117-                 cx, 
1118-                 final_entry. input , 
1119-                 result, 
1120-                 dep_node, 
1121-                 final_entry. required_depth , 
1122-                 final_entry. encountered_overflow , 
1123-                 final_entry. nested_goals , 
1124-             ) 
1125-         } ) 
1177+         debug ! ( ?evaluation_result,  "insert global cache" ) ; 
1178+         cx. with_global_cache ( |cache| cache. insert ( cx,  input,  evaluation_result,  dep_node) ) 
11261179    } 
11271180} 
0 commit comments