Skip to content

Commit 4b7ec20

Browse files
committed
reevaluate: reset encountered_overflow
also return `EvaluationResult` instead of the final `StackEntry` to make sure we correctly track information between reruns.
1 parent 6b27b4f commit 4b7ec20

File tree

3 files changed

+97
-45
lines changed

3 files changed

+97
-45
lines changed

compiler/rustc_type_ir/src/search_graph/global_cache.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use derive_where::derive_where;
22

33
use super::{AvailableDepth, Cx, NestedGoals};
44
use crate::data_structures::HashMap;
5+
use crate::search_graph::EvaluationResult;
56

67
struct Success<X: Cx> {
78
required_depth: usize,
@@ -43,28 +44,26 @@ impl<X: Cx> GlobalCache<X> {
4344
&mut self,
4445
cx: X,
4546
input: X::Input,
46-
47-
origin_result: X::Result,
47+
evaluation_result: EvaluationResult<X>,
4848
dep_node: X::DepNodeIndex,
49-
50-
required_depth: usize,
51-
encountered_overflow: bool,
52-
nested_goals: NestedGoals<X>,
5349
) {
54-
let result = cx.mk_tracked(origin_result, dep_node);
50+
let EvaluationResult { encountered_overflow, required_depth, heads, nested_goals, result } =
51+
evaluation_result;
52+
debug_assert!(heads.is_empty());
53+
let result = cx.mk_tracked(result, dep_node);
5554
let entry = self.map.entry(input).or_default();
5655
if encountered_overflow {
5756
let with_overflow = WithOverflow { nested_goals, result };
5857
let prev = entry.with_overflow.insert(required_depth, with_overflow);
5958
if let Some(prev) = &prev {
6059
assert!(cx.evaluation_is_concurrent());
61-
assert_eq!(cx.get_tracked(&prev.result), origin_result);
60+
assert_eq!(cx.get_tracked(&prev.result), evaluation_result.result);
6261
}
6362
} else {
6463
let prev = entry.success.replace(Success { required_depth, nested_goals, result });
6564
if let Some(prev) = &prev {
6665
assert!(cx.evaluation_is_concurrent());
67-
assert_eq!(cx.get_tracked(&prev.result), origin_result);
66+
assert_eq!(cx.get_tracked(&prev.result), evaluation_result.result);
6867
}
6968
}
7069
}

compiler/rustc_type_ir/src/search_graph/mod.rs

Lines changed: 85 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
451488
pub 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
}

compiler/rustc_type_ir/src/search_graph/stack.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ pub(super) struct StackEntry<X: Cx> {
2626
/// The available depth of a given goal, immutable.
2727
pub available_depth: AvailableDepth,
2828

29+
/// Starts out as `None` and gets set when rerunning this
30+
/// goal in case we encounter a cycle.
31+
pub provisional_result: Option<X::Result>,
32+
2933
/// The maximum depth required while evaluating this goal.
3034
pub required_depth: usize,
3135

@@ -42,10 +46,6 @@ pub(super) struct StackEntry<X: Cx> {
4246

4347
/// The nested goals of this goal, see the doc comment of the type.
4448
pub nested_goals: NestedGoals<X>,
45-
46-
/// Starts out as `None` and gets set when rerunning this
47-
/// goal in case we encounter a cycle.
48-
pub provisional_result: Option<X::Result>,
4949
}
5050

5151
#[derive_where(Default; X: Cx)]

0 commit comments

Comments
 (0)