Skip to content

Commit 4162c32

Browse files
committed
fix cycle head usages tracking
1 parent 5b9007b commit 4162c32

File tree

2 files changed

+143
-29
lines changed

2 files changed

+143
-29
lines changed

compiler/rustc_type_ir/src/search_graph/mod.rs

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,27 @@ impl HeadUsages {
215215
let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = self;
216216
inductive == 0 && unknown == 0 && coinductive == 0 && forced_ambiguity == 0
217217
}
218+
219+
fn is_single(self, path_kind: PathKind) -> bool {
220+
match path_kind {
221+
PathKind::Inductive => matches!(
222+
self,
223+
HeadUsages { inductive: _, unknown: 0, coinductive: 0, forced_ambiguity: 0 },
224+
),
225+
PathKind::Unknown => matches!(
226+
self,
227+
HeadUsages { inductive: 0, unknown: _, coinductive: 0, forced_ambiguity: 0 },
228+
),
229+
PathKind::Coinductive => matches!(
230+
self,
231+
HeadUsages { inductive: 0, unknown: 0, coinductive: _, forced_ambiguity: 0 },
232+
),
233+
PathKind::ForcedAmbiguity => matches!(
234+
self,
235+
HeadUsages { inductive: 0, unknown: 0, coinductive: 0, forced_ambiguity: _ },
236+
),
237+
}
238+
}
218239
}
219240

220241
#[derive(Debug, Default)]
@@ -888,7 +909,20 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
888909
!entries.is_empty()
889910
});
890911
}
912+
}
891913

914+
/// We need to rebase provisional cache entries when popping one of their cycle
915+
/// heads from the stack. This may not necessarily mean that we've actually
916+
/// reached a fixpoint for that cycle head, which impacts the way we rebase
917+
/// provisional cache entries.
918+
enum RebaseReason {
919+
NoCycleUsages,
920+
Ambiguity,
921+
Overflow,
922+
ReachedFixpoint(Option<PathKind>),
923+
}
924+
925+
impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> {
892926
/// A necessary optimization to handle complex solver cycles. A provisional cache entry
893927
/// relies on a set of cycle heads and the path towards these heads. When popping a cycle
894928
/// head from the stack after we've finished computing it, we can't be sure that the
@@ -908,8 +942,9 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
908942
/// to me.
909943
fn rebase_provisional_cache_entries(
910944
&mut self,
945+
cx: X,
911946
stack_entry: &StackEntry<X>,
912-
mut mutate_result: impl FnMut(X::Input, X::Result) -> X::Result,
947+
rebase_reason: RebaseReason,
913948
) {
914949
let popped_head_index = self.stack.next_index();
915950
#[allow(rustc::potential_query_instability)]
@@ -927,6 +962,10 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
927962
return true;
928963
};
929964

965+
let Some(new_highest_head_index) = heads.opt_highest_cycle_head_index() else {
966+
return false;
967+
};
968+
930969
// We're rebasing an entry `e` over a head `p`. This head
931970
// has a number of own heads `h` it depends on.
932971
//
@@ -941,6 +980,22 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
941980
heads.insert(head_index, PathsToNested::EMPTY, HeadUsages::default());
942981
}
943982
} else {
983+
// We may need to mutate the result of our cycle head in case we did not reach
984+
// a fixpoint.
985+
*result = match rebase_reason {
986+
RebaseReason::NoCycleUsages => return false,
987+
RebaseReason::Ambiguity => D::propagate_ambiguity(cx, input, *result),
988+
RebaseReason::Overflow => D::on_fixpoint_overflow(cx, input),
989+
RebaseReason::ReachedFixpoint(None) => *result,
990+
RebaseReason::ReachedFixpoint(Some(path_kind)) => {
991+
if popped_head.usages.is_single(path_kind) {
992+
*result
993+
} else {
994+
return false;
995+
}
996+
}
997+
};
998+
944999
// The entry `e` actually depends on the value of `p`. We need
9451000
// to make sure that the value of `p` wouldn't change even if we
9461001
// were to reevaluate it as a nested goal of `e` instead. For this
@@ -979,20 +1034,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
9791034
}
9801035
}
9811036

982-
let Some(head_index) = heads.opt_highest_cycle_head_index() else {
983-
return false;
984-
};
985-
9861037
// We now care about the path from the next highest cycle head to the
9871038
// provisional cache entry.
9881039
*path_from_head = path_from_head.extend(Self::cycle_path_kind(
9891040
&self.stack,
9901041
stack_entry.step_kind_from_parent,
991-
head_index,
1042+
new_highest_head_index,
9921043
));
993-
// Mutate the result of the provisional cache entry in case we did
994-
// not reach a fixpoint.
995-
*result = mutate_result(input, *result);
1044+
9961045
true
9971046
});
9981047
!entries.is_empty()
@@ -1213,28 +1262,31 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12131262
stack_entry: &StackEntry<X>,
12141263
usages: HeadUsages,
12151264
result: X::Result,
1216-
) -> bool {
1265+
) -> Result<Option<PathKind>, ()> {
12171266
let provisional_result = stack_entry.provisional_result;
1218-
if usages.is_empty() {
1219-
true
1220-
} else if let Some(provisional_result) = provisional_result {
1221-
provisional_result == result
1267+
if let Some(provisional_result) = provisional_result {
1268+
if provisional_result == result { Ok(None) } else { Err(()) }
12221269
} else {
1223-
let check = |k| D::is_initial_provisional_result(cx, k, stack_entry.input, result);
1224-
match usages {
1270+
let path_kind = match usages {
12251271
HeadUsages { inductive: _, unknown: 0, coinductive: 0, forced_ambiguity: 0 } => {
1226-
check(PathKind::Inductive)
1272+
PathKind::Inductive
12271273
}
12281274
HeadUsages { inductive: 0, unknown: _, coinductive: 0, forced_ambiguity: 0 } => {
1229-
check(PathKind::Unknown)
1275+
PathKind::Unknown
12301276
}
12311277
HeadUsages { inductive: 0, unknown: 0, coinductive: _, forced_ambiguity: 0 } => {
1232-
check(PathKind::Coinductive)
1278+
PathKind::Coinductive
12331279
}
12341280
HeadUsages { inductive: 0, unknown: 0, coinductive: 0, forced_ambiguity: _ } => {
1235-
check(PathKind::ForcedAmbiguity)
1281+
PathKind::ForcedAmbiguity
12361282
}
1237-
_ => false,
1283+
_ => return Err(()),
1284+
};
1285+
1286+
if D::is_initial_provisional_result(cx, path_kind, stack_entry.input, result) {
1287+
Ok(Some(path_kind))
1288+
} else {
1289+
Err(())
12381290
}
12391291
}
12401292
}
@@ -1280,8 +1332,19 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12801332
// is equal to the provisional result of the previous iteration, or because
12811333
// this was only the head of either coinductive or inductive cycles, and the
12821334
// final result is equal to the initial response for that case.
1283-
if self.reached_fixpoint(cx, &stack_entry, usages, result) {
1284-
self.rebase_provisional_cache_entries(&stack_entry, |_, result| result);
1335+
if let Ok(fixpoint) = self.reached_fixpoint(cx, &stack_entry, usages, result) {
1336+
self.rebase_provisional_cache_entries(
1337+
cx,
1338+
&stack_entry,
1339+
RebaseReason::ReachedFixpoint(fixpoint),
1340+
);
1341+
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1342+
} else if usages.is_empty() {
1343+
self.rebase_provisional_cache_entries(
1344+
cx,
1345+
&stack_entry,
1346+
RebaseReason::NoCycleUsages,
1347+
);
12851348
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
12861349
}
12871350

@@ -1298,9 +1361,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12981361
// we also taint all provisional cache entries which depend on the
12991362
// current goal.
13001363
if D::is_ambiguous_result(result) {
1301-
self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1302-
D::propagate_ambiguity(cx, input, result)
1303-
});
1364+
self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Ambiguity);
13041365
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
13051366
};
13061367

@@ -1310,9 +1371,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
13101371
if i >= D::FIXPOINT_STEP_LIMIT {
13111372
debug!("canonical cycle overflow");
13121373
let result = D::on_fixpoint_overflow(cx, input);
1313-
self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1314-
D::on_fixpoint_overflow(cx, input)
1315-
});
1374+
self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Overflow);
13161375
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
13171376
}
13181377

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
4+
// A regression test for trait-system-refactor-initiative#232. We've
5+
// previously incorrectly rebased provisional cache entries even if
6+
// the cycle head didn't reach a fixpoint as it did not depend on any
7+
// cycles itself.
8+
//
9+
// Just because the result of a goal does not depend on its own provisional
10+
// result, it does not mean its nested goals don't depend on its result.
11+
struct B;
12+
struct C;
13+
struct D;
14+
15+
pub trait Trait {
16+
type Output;
17+
}
18+
macro_rules! k {
19+
($t:ty) => {
20+
<$t as Trait>::Output
21+
};
22+
}
23+
24+
trait CallB<T1, T2> {
25+
type Output;
26+
type Return;
27+
}
28+
29+
trait CallC<T1> {
30+
type Output;
31+
type Return;
32+
}
33+
34+
trait CallD<T1, T2> {
35+
type Output;
36+
}
37+
38+
fn foo<X, Y>()
39+
where
40+
X: Trait,
41+
Y: Trait,
42+
D: CallD<k![X], k![Y]>,
43+
C: CallC<<D as CallD<k![X], k![Y]>>::Output>,
44+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output: Trait,
45+
B: CallB<
46+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Return,
47+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output,
48+
>,
49+
<B as CallB<
50+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Return,
51+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output,
52+
>>::Output: Trait<Output = ()>,
53+
{
54+
}
55+
fn main() {}

0 commit comments

Comments
 (0)