Skip to content

Commit 1d60108

Browse files
committed
Auto merge of #97674 - nnethercote:oblig-forest-tweaks, r=nikomatsakis
Obligation forest tweaks A few minor improvements to the code. r? `@nikomatsakis`
2 parents 4104596 + 32741d5 commit 1d60108

File tree

3 files changed

+115
-153
lines changed

3 files changed

+115
-153
lines changed

compiler/rustc_data_structures/src/obligation_forest/mod.rs

+72-83
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
//!
@@ -99,6 +96,8 @@ pub trait ObligationProcessor {
9996
type Obligation: ForestObligation;
10097
type Error: Debug;
10198

99+
fn needs_process_obligation(&self, obligation: &Self::Obligation) -> bool;
100+
102101
fn process_obligation(
103102
&mut self,
104103
obligation: &mut Self::Obligation,
@@ -146,7 +145,7 @@ pub struct ObligationForest<O: ForestObligation> {
146145

147146
/// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately,
148147
/// its contents are not guaranteed to match those of `nodes`. See the
149-
/// comments in [`Self::process_obligation` for details.
148+
/// comments in `Self::process_obligation` for details.
150149
active_cache: FxHashMap<O::CacheKey, usize>,
151150

152151
/// A vector reused in [Self::compress()] and [Self::find_cycles_from_node()],
@@ -260,8 +259,6 @@ pub trait OutcomeTrait {
260259
type Obligation;
261260

262261
fn new() -> Self;
263-
fn mark_not_stalled(&mut self);
264-
fn is_stalled(&self) -> bool;
265262
fn record_completed(&mut self, outcome: &Self::Obligation);
266263
fn record_error(&mut self, error: Self::Error);
267264
}
@@ -270,30 +267,14 @@ pub trait OutcomeTrait {
270267
pub struct Outcome<O, E> {
271268
/// Backtrace of obligations that were found to be in error.
272269
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,
281270
}
282271

283272
impl<O, E> OutcomeTrait for Outcome<O, E> {
284273
type Error = Error<O, E>;
285274
type Obligation = O;
286275

287276
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
277+
Self { errors: vec![] }
297278
}
298279

299280
fn record_completed(&mut self, _outcome: &Self::Obligation) {
@@ -415,10 +396,7 @@ impl<O: ForestObligation> ObligationForest<O> {
415396
.insert(node.obligation.as_cache_key());
416397
}
417398

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).
399+
/// Performs a fixpoint computation over the obligation list.
422400
#[inline(never)]
423401
pub fn process_obligations<P, OUT>(&mut self, processor: &mut P) -> OUT
424402
where
@@ -427,55 +405,69 @@ impl<O: ForestObligation> ObligationForest<O> {
427405
{
428406
let mut outcome = OUT::new();
429407

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

476-
// There's no need to perform marking, cycle processing and compression when nothing
477-
// changed.
478-
if !outcome.is_stalled() {
479471
self.mark_successes();
480472
self.process_cycles(processor);
481473
self.compress(|obl| outcome.record_completed(obl));
@@ -634,17 +626,14 @@ impl<O: ForestObligation> ObligationForest<O> {
634626
}
635627
}
636628
NodeState::Done => {
637-
// This lookup can fail because the contents of
629+
// The removal lookup might fail because the contents of
638630
// `self.active_cache` are not guaranteed to match those of
639631
// `self.nodes`. See the comment in `process_obligation`
640632
// for more details.
641-
if let Some((predicate, _)) =
642-
self.active_cache.remove_entry(&node.obligation.as_cache_key())
643-
{
644-
self.done_cache.insert(predicate);
645-
} else {
646-
self.done_cache.insert(node.obligation.as_cache_key().clone());
647-
}
633+
let cache_key = node.obligation.as_cache_key();
634+
self.active_cache.remove(&cache_key);
635+
self.done_cache.insert(cache_key);
636+
648637
// Extract the success stories.
649638
outcome_cb(&node.obligation);
650639
node_rewrites[index] = orig_nodes_len;

compiler/rustc_data_structures/src/obligation_forest/tests.rs

+5-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) {
@@ -74,6 +65,10 @@ where
7465
type Obligation = O;
7566
type Error = E;
7667

68+
fn needs_process_obligation(&self, _obligation: &Self::Obligation) -> bool {
69+
true
70+
}
71+
7772
fn process_obligation(
7873
&mut self,
7974
obligation: &mut Self::Obligation,

0 commit comments

Comments
 (0)