@@ -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 look
1154+ // similar to the previous iterations when reevaluating, it's better
1155+ // 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> {
11071170 fn 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