Skip to content

Commit

Permalink
Split process_obligation in two.
Browse files Browse the repository at this point in the history
Because it really has two halves:
- A read-only part that checks if further work is needed.
- The further work part, which is much less hot.

This makes things a bit clearer and nicer.
  • Loading branch information
nnethercote committed Jun 5, 2022
1 parent 281229a commit 32741d5
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 46 deletions.
15 changes: 10 additions & 5 deletions compiler/rustc_data_structures/src/obligation_forest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ pub trait ObligationProcessor {
type Obligation: ForestObligation;
type Error: Debug;

fn needs_process_obligation(&self, obligation: &Self::Obligation) -> bool;

fn process_obligation(
&mut self,
obligation: &mut Self::Obligation,
Expand Down Expand Up @@ -143,7 +145,7 @@ pub struct ObligationForest<O: ForestObligation> {

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

/// A vector reused in [Self::compress()] and [Self::find_cycles_from_node()],
Expand Down Expand Up @@ -417,15 +419,18 @@ impl<O: ForestObligation> ObligationForest<O> {
// nodes. Therefore we use a `while` loop.
let mut index = 0;
while let Some(node) = self.nodes.get_mut(index) {
if node.state.get() != NodeState::Pending
|| !processor.needs_process_obligation(&node.obligation)
{
index += 1;
continue;
}

// `processor.process_obligation` can modify the predicate within
// `node.obligation`, and that predicate is the key used for
// `self.active_cache`. This means that `self.active_cache` can get
// out of sync with `nodes`. It's not very common, but it does
// happen, and code in `compress` has to allow for it.
if node.state.get() != NodeState::Pending {
index += 1;
continue;
}

match processor.process_obligation(&mut node.obligation) {
ProcessResult::Unchanged => {
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_data_structures/src/obligation_forest/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ where
type Obligation = O;
type Error = E;

fn needs_process_obligation(&self, _obligation: &Self::Obligation) -> bool {
true
}

fn process_obligation(
&mut self,
obligation: &mut Self::Obligation,
Expand Down
71 changes: 30 additions & 41 deletions compiler/rustc_trait_selection/src/traits/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,22 +253,16 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
type Obligation = PendingPredicateObligation<'tcx>;
type Error = FulfillmentErrorCode<'tcx>;

/// Processes a predicate obligation and returns either:
/// - `Changed(v)` if the predicate is true, presuming that `v` are also true
/// - `Unchanged` if we don't have enough info to be sure
/// - `Error(e)` if the predicate does not hold
/// Identifies whether a predicate obligation needs processing.
///
/// This is always inlined, despite its size, because it has a single
/// callsite and it is called *very* frequently.
#[inline(always)]
fn process_obligation(
&mut self,
pending_obligation: &mut Self::Obligation,
) -> ProcessResult<Self::Obligation, Self::Error> {
fn needs_process_obligation(&self, pending_obligation: &Self::Obligation) -> bool {
// If we were stalled on some unresolved variables, first check whether
// any of them have been resolved; if not, don't bother doing more work
// yet.
let change = match pending_obligation.stalled_on.len() {
match pending_obligation.stalled_on.len() {
// Match arms are in order of frequency, which matters because this
// code is so hot. 1 and 0 dominate; 2+ is fairly rare.
1 => {
Expand All @@ -291,42 +285,18 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
false
})()
}
};

if !change {
debug!(
"process_predicate: pending obligation {:?} still stalled on {:?}",
self.selcx.infcx().resolve_vars_if_possible(pending_obligation.obligation.clone()),
pending_obligation.stalled_on
);
return ProcessResult::Unchanged;
}

self.process_changed_obligations(pending_obligation)
}

fn process_backedge<'c, I>(
&mut self,
cycle: I,
_marker: PhantomData<&'c PendingPredicateObligation<'tcx>>,
) where
I: Clone + Iterator<Item = &'c PendingPredicateObligation<'tcx>>,
{
if self.selcx.coinductive_match(cycle.clone().map(|s| s.obligation.predicate)) {
debug!("process_child_obligations: coinductive match");
} else {
let cycle: Vec<_> = cycle.map(|c| c.obligation.clone()).collect();
self.selcx.infcx().report_overflow_error_cycle(&cycle);
}
}
}

impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
// The code calling this method is extremely hot and only rarely
// actually uses this, so move this part of the code
// out of that loop.
/// Processes a predicate obligation and returns either:
/// - `Changed(v)` if the predicate is true, presuming that `v` are also true
/// - `Unchanged` if we don't have enough info to be sure
/// - `Error(e)` if the predicate does not hold
///
/// This is called much less often than `needs_process_obligation`, so we
/// never inline it.
#[inline(never)]
fn process_changed_obligations(
fn process_obligation(
&mut self,
pending_obligation: &mut PendingPredicateObligation<'tcx>,
) -> ProcessResult<PendingPredicateObligation<'tcx>, FulfillmentErrorCode<'tcx>> {
Expand All @@ -341,6 +311,8 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
self.selcx.infcx().resolve_vars_if_possible(obligation.predicate);
}

let obligation = &pending_obligation.obligation;

debug!(?obligation, ?obligation.cause, "process_obligation");

let infcx = self.selcx.infcx();
Expand Down Expand Up @@ -655,6 +627,23 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
}
}

fn process_backedge<'c, I>(
&mut self,
cycle: I,
_marker: PhantomData<&'c PendingPredicateObligation<'tcx>>,
) where
I: Clone + Iterator<Item = &'c PendingPredicateObligation<'tcx>>,
{
if self.selcx.coinductive_match(cycle.clone().map(|s| s.obligation.predicate)) {
debug!("process_child_obligations: coinductive match");
} else {
let cycle: Vec<_> = cycle.map(|c| c.obligation.clone()).collect();
self.selcx.infcx().report_overflow_error_cycle(&cycle);
}
}
}

impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
#[instrument(level = "debug", skip(self, obligation, stalled_on))]
fn process_trait_obligation(
&mut self,
Expand Down

0 comments on commit 32741d5

Please sign in to comment.