1
Fork 0

avoid always rerunning in case of a cycle

This commit is contained in:
lcnr 2024-01-09 10:01:50 +01:00
parent 118453c7e1
commit 88271deac2
3 changed files with 73 additions and 34 deletions

View file

@ -4563,6 +4563,7 @@ checksum = "8ba09476327c4b70ccefb6180f046ef588c26a24cf5d269a9feba316eb4f029f"
name = "rustc_trait_selection" name = "rustc_trait_selection"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"bitflags 2.4.1",
"itertools", "itertools",
"rustc_ast", "rustc_ast",
"rustc_attr", "rustc_attr",

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
# tidy-alphabetical-start # tidy-alphabetical-start
bitflags = "2.4.1"
itertools = "0.11.0" itertools = "0.11.0"
rustc_ast = { path = "../rustc_ast" } rustc_ast = { path = "../rustc_ast" }
rustc_attr = { path = "../rustc_attr" } rustc_attr = { path = "../rustc_attr" }

View file

@ -18,6 +18,14 @@ rustc_index::newtype_index! {
pub struct StackDepth {} pub struct StackDepth {}
} }
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct HasBeenUsed: u8 {
const INDUCTIVE_CYCLE = 1 << 0;
const COINDUCTIVE_CYCLE = 1 << 1;
}
}
#[derive(Debug)] #[derive(Debug)]
struct StackEntry<'tcx> { struct StackEntry<'tcx> {
input: CanonicalInput<'tcx>, input: CanonicalInput<'tcx>,
@ -32,7 +40,7 @@ struct StackEntry<'tcx> {
non_root_cycle_participant: Option<StackDepth>, non_root_cycle_participant: Option<StackDepth>,
encountered_overflow: bool, encountered_overflow: bool,
has_been_used: bool, has_been_used: HasBeenUsed,
/// Starts out as `None` and gets set when rerunning this /// Starts out as `None` and gets set when rerunning this
/// goal in case we encounter a cycle. /// goal in case we encounter a cycle.
provisional_result: Option<QueryResult<'tcx>>, provisional_result: Option<QueryResult<'tcx>>,
@ -195,9 +203,11 @@ impl<'tcx> SearchGraph<'tcx> {
fn tag_cycle_participants( fn tag_cycle_participants(
stack: &mut IndexVec<StackDepth, StackEntry<'tcx>>, stack: &mut IndexVec<StackDepth, StackEntry<'tcx>>,
cycle_participants: &mut FxHashSet<CanonicalInput<'tcx>>, cycle_participants: &mut FxHashSet<CanonicalInput<'tcx>>,
usage_kind: HasBeenUsed,
head: StackDepth, head: StackDepth,
) { ) {
stack[head].has_been_used = true; stack[head].has_been_used |= usage_kind;
debug_assert!(!stack[head].has_been_used.is_empty());
for entry in &mut stack.raw[head.index() + 1..] { for entry in &mut stack.raw[head.index() + 1..] {
entry.non_root_cycle_participant = entry.non_root_cycle_participant.max(Some(head)); entry.non_root_cycle_participant = entry.non_root_cycle_participant.max(Some(head));
cycle_participants.insert(entry.input); cycle_participants.insert(entry.input);
@ -272,29 +282,33 @@ impl<'tcx> SearchGraph<'tcx> {
// Check whether the goal is in the provisional cache. // Check whether the goal is in the provisional cache.
let cache_entry = self.provisional_cache.entry(input).or_default(); let cache_entry = self.provisional_cache.entry(input).or_default();
if let Some(with_coinductive_stack) = &mut cache_entry.with_coinductive_stack if let Some(with_coinductive_stack) = &cache_entry.with_coinductive_stack
&& Self::stack_coinductive_from(tcx, &self.stack, with_coinductive_stack.head) && Self::stack_coinductive_from(tcx, &self.stack, with_coinductive_stack.head)
{ {
// We have a nested goal which is already in the provisional cache, use // We have a nested goal which is already in the provisional cache, use
// its result. // its result. We do not provide any usage kind as that should have been
// already set correctly while computing the cache entry.
inspect inspect
.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit); .goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit);
Self::tag_cycle_participants( Self::tag_cycle_participants(
&mut self.stack, &mut self.stack,
&mut self.cycle_participants, &mut self.cycle_participants,
HasBeenUsed::empty(),
with_coinductive_stack.head, with_coinductive_stack.head,
); );
return with_coinductive_stack.result; return with_coinductive_stack.result;
} else if let Some(with_inductive_stack) = &mut cache_entry.with_inductive_stack } else if let Some(with_inductive_stack) = &cache_entry.with_inductive_stack
&& !Self::stack_coinductive_from(tcx, &self.stack, with_inductive_stack.head) && !Self::stack_coinductive_from(tcx, &self.stack, with_inductive_stack.head)
{ {
// We have a nested goal which is already in the provisional cache, use // We have a nested goal which is already in the provisional cache, use
// its result. // its result. We do not provide any usage kind as that should have been
// already set correctly while computing the cache entry.
inspect inspect
.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit); .goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit);
Self::tag_cycle_participants( Self::tag_cycle_participants(
&mut self.stack, &mut self.stack,
&mut self.cycle_participants, &mut self.cycle_participants,
HasBeenUsed::empty(),
with_inductive_stack.head, with_inductive_stack.head,
); );
return with_inductive_stack.result; return with_inductive_stack.result;
@ -310,21 +324,27 @@ impl<'tcx> SearchGraph<'tcx> {
// Finally we can return either the provisional response for that goal if we have a // Finally we can return either the provisional response for that goal if we have a
// coinductive cycle or an ambiguous result if the cycle is inductive. // coinductive cycle or an ambiguous result if the cycle is inductive.
inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CycleInStack); inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CycleInStack);
let is_coinductive_cycle = Self::stack_coinductive_from(tcx, &self.stack, stack_depth);
let usage_kind = if is_coinductive_cycle {
HasBeenUsed::COINDUCTIVE_CYCLE
} else {
HasBeenUsed::INDUCTIVE_CYCLE
};
Self::tag_cycle_participants( Self::tag_cycle_participants(
&mut self.stack, &mut self.stack,
&mut self.cycle_participants, &mut self.cycle_participants,
usage_kind,
stack_depth, stack_depth,
); );
// Return the provisional result or, if we're in the first iteration,
// start with no constraints.
return if let Some(result) = self.stack[stack_depth].provisional_result { return if let Some(result) = self.stack[stack_depth].provisional_result {
result result
} else if is_coinductive_cycle {
Self::response_no_constraints(tcx, input, Certainty::Yes)
} else { } else {
// If we don't have a provisional result yet we're in the first iteration, Self::response_no_constraints(tcx, input, Certainty::OVERFLOW)
// so we start with no constraints.
if Self::stack_coinductive_from(tcx, &self.stack, stack_depth) {
Self::response_no_constraints(tcx, input, Certainty::Yes)
} else {
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW)
}
}; };
} else { } else {
// No entry, we push this goal on the stack and try to prove it. // No entry, we push this goal on the stack and try to prove it.
@ -335,7 +355,7 @@ impl<'tcx> SearchGraph<'tcx> {
reached_depth: depth, reached_depth: depth,
non_root_cycle_participant: None, non_root_cycle_participant: None,
encountered_overflow: false, encountered_overflow: false,
has_been_used: false, has_been_used: HasBeenUsed::empty(),
provisional_result: None, provisional_result: None,
}; };
assert_eq!(self.stack.push(entry), depth); assert_eq!(self.stack.push(entry), depth);
@ -354,41 +374,58 @@ impl<'tcx> SearchGraph<'tcx> {
// point we are done. // point we are done.
for _ in 0..self.local_overflow_limit() { for _ in 0..self.local_overflow_limit() {
let result = prove_goal(self, inspect); let result = prove_goal(self, inspect);
let stack_entry = self.pop_stack();
debug_assert_eq!(stack_entry.input, input);
// Check whether the current goal is the root of a cycle. // If the current goal is not the root of a cycle, we are done.
// If so, we have to retry proving the cycle head if stack_entry.has_been_used.is_empty() {
// until its result reaches a fixpoint. We need to do so for return (stack_entry, result);
// all cycle heads, not only for the root. }
// If it is a cycle head, we have to keep trying to prove it until
// we reach a fixpoint. We need to do so for all cycle heads,
// not only for the root.
// //
// See tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.rs // See tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.rs
// for an example. // for an example.
let stack_entry = self.pop_stack();
debug_assert_eq!(stack_entry.input, input);
if stack_entry.has_been_used {
Self::clear_dependent_provisional_results(
&mut self.provisional_cache,
self.stack.next_index(),
);
}
if stack_entry.has_been_used // Start by clearing all provisional cache entries which depend on this
&& stack_entry.provisional_result.map_or(true, |r| r != result) // the current goal.
{ Self::clear_dependent_provisional_results(
// If so, update its provisional result and reevaluate it. &mut self.provisional_cache,
self.stack.next_index(),
);
// Check whether we reached a fixpoint, either because the final result
// is equal to the provisional result of the previous iteration, or because
// this was only the root of either coinductive or inductive cycles, and the
// final result is equal to the initial response for that case.
let reached_fixpoint = if let Some(r) = stack_entry.provisional_result {
r == result
} else if stack_entry.has_been_used == HasBeenUsed::COINDUCTIVE_CYCLE {
Self::response_no_constraints(tcx, input, Certainty::Yes) == result
} else if stack_entry.has_been_used == HasBeenUsed::INDUCTIVE_CYCLE {
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW) == result
} else {
false
};
if reached_fixpoint {
return (stack_entry, result);
} else {
// Did not reach a fixpoint, update the provisional result and reevaluate.
let depth = self.stack.push(StackEntry { let depth = self.stack.push(StackEntry {
has_been_used: false, has_been_used: HasBeenUsed::empty(),
provisional_result: Some(result), provisional_result: Some(result),
..stack_entry ..stack_entry
}); });
debug_assert_eq!(self.provisional_cache[&input].stack_depth, Some(depth)); debug_assert_eq!(self.provisional_cache[&input].stack_depth, Some(depth));
} else {
return (stack_entry, result);
} }
} }
debug!("canonical cycle overflow"); debug!("canonical cycle overflow");
let current_entry = self.pop_stack(); let current_entry = self.pop_stack();
debug_assert!(!current_entry.has_been_used); debug_assert!(current_entry.has_been_used.is_empty());
let result = Self::response_no_constraints(tcx, input, Certainty::OVERFLOW); let result = Self::response_no_constraints(tcx, input, Certainty::OVERFLOW);
(current_entry, result) (current_entry, result)
}); });