@@ -448,6 +448,43 @@ struct ProvisionalCacheEntry<X: Cx> {
448
448
result : X :: Result ,
449
449
}
450
450
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
+
451
488
pub struct SearchGraph < D : Delegate < Cx = X > , X : Cx = <D as Delegate >:: Cx > {
452
489
root_depth : AvailableDepth ,
453
490
/// The stack of goals currently being computed.
@@ -614,12 +651,12 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
614
651
input,
615
652
step_kind_from_parent,
616
653
available_depth,
654
+ provisional_result : None ,
617
655
required_depth : 0 ,
618
656
heads : Default :: default ( ) ,
619
657
encountered_overflow : false ,
620
658
has_been_used : None ,
621
659
nested_goals : Default :: default ( ) ,
622
- provisional_result : None ,
623
660
} ) ;
624
661
625
662
// This is for global caching, so we properly track query dependencies.
@@ -628,35 +665,42 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
628
665
// not tracked by the cache key and from outside of this anon task, it
629
666
// must not be added to the global cache. Notably, this is the case for
630
667
// 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 ( || {
632
669
self . evaluate_goal_in_task ( cx, input, inspect, & mut evaluate_goal)
633
670
} ) ;
634
671
635
672
// We've finished computing the goal and have popped it from the stack,
636
673
// lazily update its parent goal.
637
674
Self :: update_parent_goal (
638
675
& 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 ) ,
644
681
) ;
682
+ let result = evaluation_result. result ;
645
683
646
684
// We're now done with this goal. We only add the root of cycles to the global cache.
647
685
// 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 ( ) {
649
687
if let Some ( ( _scope, expected) ) = validate_cache {
650
688
// Do not try to move a goal into the cache again if we're testing
651
689
// the global cache.
652
- assert_eq ! ( result, expected, "input={input:?}" ) ;
690
+ assert_eq ! ( evaluation_result . result, expected, "input={input:?}" ) ;
653
691
} 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)
655
693
}
656
694
} else if D :: ENABLE_PROVISIONAL_CACHE {
657
695
debug_assert ! ( validate_cache. is_none( ) , "unexpected non-root: {input:?}" ) ;
658
696
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;
660
704
let path_from_head = Self :: cycle_path_kind (
661
705
& self . stack ,
662
706
step_kind_from_parent,
@@ -1022,18 +1066,24 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1022
1066
input : X :: Input ,
1023
1067
inspect : & mut D :: ProofTreeBuilder ,
1024
1068
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 ;
1026
1075
let mut i = 0 ;
1027
1076
loop {
1028
1077
let result = evaluate_goal ( self , inspect) ;
1029
1078
let stack_entry = self . stack . pop ( ) ;
1079
+ encountered_overflow |= stack_entry. encountered_overflow ;
1030
1080
debug_assert_eq ! ( stack_entry. input, input) ;
1031
1081
1032
1082
// If the current goal is not the root of a cycle, we are done.
1033
1083
//
1034
1084
// There are no provisional cache entries which depend on this goal.
1035
1085
let Some ( usage_kind) = stack_entry. has_been_used else {
1036
- return ( stack_entry, result) ;
1086
+ return EvaluationResult :: finalize ( stack_entry, encountered_overflow , result) ;
1037
1087
} ;
1038
1088
1039
1089
// 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> {
1049
1099
// final result is equal to the initial response for that case.
1050
1100
if self . reached_fixpoint ( cx, & stack_entry, usage_kind, result) {
1051
1101
self . rebase_provisional_cache_entries ( & stack_entry, |_, result| result) ;
1052
- return ( stack_entry, result) ;
1102
+ return EvaluationResult :: finalize ( stack_entry, encountered_overflow , result) ;
1053
1103
}
1054
1104
1055
1105
// If computing this goal results in ambiguity with no constraints,
@@ -1068,7 +1118,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1068
1118
self . rebase_provisional_cache_entries ( & stack_entry, |input, _| {
1069
1119
D :: propagate_ambiguity ( cx, input, result)
1070
1120
} ) ;
1071
- return ( stack_entry, result) ;
1121
+ return EvaluationResult :: finalize ( stack_entry, encountered_overflow , result) ;
1072
1122
} ;
1073
1123
1074
1124
// 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> {
1080
1130
self . rebase_provisional_cache_entries ( & stack_entry, |input, _| {
1081
1131
D :: on_fixpoint_overflow ( cx, input)
1082
1132
} ) ;
1083
- return ( stack_entry, result) ;
1133
+ return EvaluationResult :: finalize ( stack_entry, encountered_overflow , result) ;
1084
1134
}
1085
1135
1086
1136
// Clear all provisional cache entries which depend on a previous provisional
@@ -1089,9 +1139,22 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1089
1139
1090
1140
debug ! ( ?result, "fixpoint changed provisional results" ) ;
1091
1141
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 ,
1093
1145
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 ,
1095
1158
} ) ;
1096
1159
}
1097
1160
}
@@ -1107,21 +1170,11 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1107
1170
fn insert_global_cache (
1108
1171
& mut self ,
1109
1172
cx : X ,
1110
- final_entry : StackEntry < X > ,
1111
- result : X :: Result ,
1173
+ input : X :: Input ,
1174
+ evaluation_result : EvaluationResult < X > ,
1112
1175
dep_node : X :: DepNodeIndex ,
1113
1176
) {
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) )
1126
1179
}
1127
1180
}
0 commit comments