Skip to content

Commit 281229a

Browse files
committed
Handle stalling within ObligationForest.
It is simpler if `ObligationForest` does this itself, rather than the caller having to manage it.
1 parent cdb446f commit 281229a

File tree

3 files changed

+70
-103
lines changed

3 files changed

+70
-103
lines changed

compiler/rustc_data_structures/src/obligation_forest/mod.rs

+61-74
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
//! now considered to be in error.
4343
//!
4444
//! When the call to `process_obligations` completes, you get back an `Outcome`,
45-
//! which includes three bits of information:
45+
//! which includes two bits of information:
4646
//!
4747
//! - `completed`: a list of obligations where processing was fully
4848
//! completed without error (meaning that all transitive subobligations
@@ -53,13 +53,10 @@
5353
//! all the obligations in `C` have been found completed.
5454
//! - `errors`: a list of errors that occurred and associated backtraces
5555
//! at the time of error, which can be used to give context to the user.
56-
//! - `stalled`: if true, then none of the existing obligations were
57-
//! *shallowly successful* (that is, no callback returned `Changed(_)`).
58-
//! This implies that all obligations were either errors or returned an
59-
//! ambiguous result, which means that any further calls to
60-
//! `process_obligations` would simply yield back further ambiguous
61-
//! results. This is used by the `FulfillmentContext` to decide when it
62-
//! has reached a steady state.
56+
//!
57+
//! Upon completion, none of the existing obligations were *shallowly
58+
//! successful* (that is, no callback returned `Changed(_)`). This implies that
59+
//! all obligations were either errors or returned an ambiguous result.
6360
//!
6461
//! ### Implementation details
6562
//!
@@ -260,8 +257,6 @@ pub trait OutcomeTrait {
260257
type Obligation;
261258

262259
fn new() -> Self;
263-
fn mark_not_stalled(&mut self);
264-
fn is_stalled(&self) -> bool;
265260
fn record_completed(&mut self, outcome: &Self::Obligation);
266261
fn record_error(&mut self, error: Self::Error);
267262
}
@@ -270,30 +265,14 @@ pub trait OutcomeTrait {
270265
pub struct Outcome<O, E> {
271266
/// Backtrace of obligations that were found to be in error.
272267
pub errors: Vec<Error<O, E>>,
273-
274-
/// If true, then we saw no successful obligations, which means
275-
/// there is no point in further iteration. This is based on the
276-
/// assumption that when trait matching returns `Error` or
277-
/// `Unchanged`, those results do not affect environmental
278-
/// inference state. (Note that if we invoke `process_obligations`
279-
/// with no pending obligations, stalled will be true.)
280-
pub stalled: bool,
281268
}
282269

283270
impl<O, E> OutcomeTrait for Outcome<O, E> {
284271
type Error = Error<O, E>;
285272
type Obligation = O;
286273

287274
fn new() -> Self {
288-
Self { stalled: true, errors: vec![] }
289-
}
290-
291-
fn mark_not_stalled(&mut self) {
292-
self.stalled = false;
293-
}
294-
295-
fn is_stalled(&self) -> bool {
296-
self.stalled
275+
Self { errors: vec![] }
297276
}
298277

299278
fn record_completed(&mut self, _outcome: &Self::Obligation) {
@@ -415,10 +394,7 @@ impl<O: ForestObligation> ObligationForest<O> {
415394
.insert(node.obligation.as_cache_key());
416395
}
417396

418-
/// Performs a pass through the obligation list. This must
419-
/// be called in a loop until `outcome.stalled` is false.
420-
///
421-
/// This _cannot_ be unrolled (presently, at least).
397+
/// Performs a fixpoint computation over the obligation list.
422398
#[inline(never)]
423399
pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT
424400
where
@@ -427,55 +403,66 @@ impl<O: ForestObligation> ObligationForest<O> {
427403
{
428404
let mut outcome = OUT::new();
429405

430-
// Note that the loop body can append new nodes, and those new nodes
431-
// will then be processed by subsequent iterations of the loop.
432-
//
433-
// We can't use an iterator for the loop because `self.nodes` is
434-
// appended to and the borrow checker would complain. We also can't use
435-
// `for index in 0..self.nodes.len() { ... }` because the range would
436-
// be computed with the initial length, and we would miss the appended
437-
// nodes. Therefore we use a `while` loop.
438-
let mut index = 0;
439-
while let Some(node) = self.nodes.get_mut(index) {
440-
// `processor.process_obligation` can modify the predicate within
441-
// `node.obligation`, and that predicate is the key used for
442-
// `self.active_cache`. This means that `self.active_cache` can get
443-
// out of sync with `nodes`. It's not very common, but it does
444-
// happen, and code in `compress` has to allow for it.
445-
if node.state.get() != NodeState::Pending {
446-
index += 1;
447-
continue;
448-
}
449-
450-
match processor.process_obligation(&mut node.obligation) {
451-
ProcessResult::Unchanged => {
452-
// No change in state.
406+
// Fixpoint computation: we repeat until the inner loop stalls.
407+
loop {
408+
let mut has_changed = false;
409+
410+
// Note that the loop body can append new nodes, and those new nodes
411+
// will then be processed by subsequent iterations of the loop.
412+
//
413+
// We can't use an iterator for the loop because `self.nodes` is
414+
// appended to and the borrow checker would complain. We also can't use
415+
// `for index in 0..self.nodes.len() { ... }` because the range would
416+
// be computed with the initial length, and we would miss the appended
417+
// nodes. Therefore we use a `while` loop.
418+
let mut index = 0;
419+
while let Some(node) = self.nodes.get_mut(index) {
420+
// `processor.process_obligation` can modify the predicate within
421+
// `node.obligation`, and that predicate is the key used for
422+
// `self.active_cache`. This means that `self.active_cache` can get
423+
// out of sync with `nodes`. It's not very common, but it does
424+
// happen, and code in `compress` has to allow for it.
425+
if node.state.get() != NodeState::Pending {
426+
index += 1;
427+
continue;
453428
}
454-
ProcessResult::Changed(children) => {
455-
// We are not (yet) stalled.
456-
outcome.mark_not_stalled();
457-
node.state.set(NodeState::Success);
458-
459-
for child in children {
460-
let st = self.register_obligation_at(child, Some(index));
461-
if let Err(()) = st {
462-
// Error already reported - propagate it
463-
// to our node.
464-
self.error_at(index);
429+
430+
match processor.process_obligation(&mut node.obligation) {
431+
ProcessResult::Unchanged => {
432+
// No change in state.
433+
}
434+
ProcessResult::Changed(children) => {
435+
// We are not (yet) stalled.
436+
has_changed = true;
437+
node.state.set(NodeState::Success);
438+
439+
for child in children {
440+
let st = self.register_obligation_at(child, Some(index));
441+
if let Err(()) = st {
442+
// Error already reported - propagate it
443+
// to our node.
444+
self.error_at(index);
445+
}
465446
}
466447
}
448+
ProcessResult::Error(err) => {
449+
has_changed = true;
450+
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
451+
}
467452
}
468-
ProcessResult::Error(err) => {
469-
outcome.mark_not_stalled();
470-
outcome.record_error(Error { error: err, backtrace: self.error_at(index) });
471-
}
453+
index += 1;
454+
}
455+
456+
// If unchanged, then we saw no successful obligations, which means
457+
// there is no point in further iteration. This is based on the
458+
// assumption that when trait matching returns `Error` or
459+
// `Unchanged`, those results do not affect environmental inference
460+
// state. (Note that this will occur if we invoke
461+
// `process_obligations` with no pending obligations.)
462+
if !has_changed {
463+
break;
472464
}
473-
index += 1;
474-
}
475465

476-
// There's no need to perform marking, cycle processing and compression when nothing
477-
// changed.
478-
if !outcome.is_stalled() {
479466
self.mark_successes();
480467
self.process_cycles(processor);
481468
self.compress(|obl| outcome.record_completed(obl));

compiler/rustc_data_structures/src/obligation_forest/tests.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ struct ClosureObligationProcessor<OF, BF, O, E> {
2020
struct TestOutcome<O, E> {
2121
pub completed: Vec<O>,
2222
pub errors: Vec<Error<O, E>>,
23-
pub stalled: bool,
2423
}
2524

2625
impl<O, E> OutcomeTrait for TestOutcome<O, E>
@@ -31,15 +30,7 @@ where
3130
type Obligation = O;
3231

3332
fn new() -> Self {
34-
Self { errors: vec![], stalled: false, completed: vec![] }
35-
}
36-
37-
fn mark_not_stalled(&mut self) {
38-
self.stalled = false;
39-
}
40-
41-
fn is_stalled(&self) -> bool {
42-
self.stalled
33+
Self { errors: vec![], completed: vec![] }
4334
}
4435

4536
fn record_completed(&mut self, outcome: &Self::Obligation) {

compiler/rustc_trait_selection/src/traits/fulfill.rs

+8-19
Original file line numberDiff line numberDiff line change
@@ -133,27 +133,16 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
133133

134134
let mut errors = Vec::new();
135135

136-
loop {
137-
debug!("select: starting another iteration");
136+
// Process pending obligations.
137+
let outcome: Outcome<_, _> = self.predicates.process_obligations(&mut FulfillProcessor {
138+
selcx,
139+
register_region_obligations: self.register_region_obligations,
140+
});
138141

139-
// Process pending obligations.
140-
let outcome: Outcome<_, _> =
141-
self.predicates.process_obligations(&mut FulfillProcessor {
142-
selcx,
143-
register_region_obligations: self.register_region_obligations,
144-
});
145-
debug!("select: outcome={:#?}", outcome);
142+
// FIXME: if we kept the original cache key, we could mark projection
143+
// obligations as complete for the projection cache here.
146144

147-
// FIXME: if we kept the original cache key, we could mark projection
148-
// obligations as complete for the projection cache here.
149-
150-
errors.extend(outcome.errors.into_iter().map(to_fulfillment_error));
151-
152-
// If nothing new was added, no need to keep looping.
153-
if outcome.stalled {
154-
break;
155-
}
156-
}
145+
errors.extend(outcome.errors.into_iter().map(to_fulfillment_error));
157146

158147
debug!(
159148
"select({} predicates remaining, {} errors) done",

0 commit comments

Comments
 (0)