Skip to content

Commit 65bcac4

Browse files
committed
Auto merge of #160479 - nnethercote:opt-try_evaluate_obligations, r=jdonszelmann
Optimize `try_evaluate_obligations` This function is very sub-optimal, perf-wise: it takes `self.obligations.pending` (with `mem::take`) and iterates over the elements, checking each one. But most of the time no progress is made and all the obligations get pushed back onto `self.obligations.pending`. This drain + reconstruct approach is very expensive, mostly because the new `pending` vec is built by pushing one element at a time, which requires repeated reallocations. And this vec can have thousands of elements in it, in extreme cases. Also, `obligation` and `stalled_on` get passed by value to `evaluate_root_goal` (`obligation` as `goal`), which then usually passes the values back in the `GoalEvaluation` which is immediately deconstructed. This is a lot of wasted value moves. This commit optimizes things in two ways. - It prioritizes the hot path. This involves checking in advance if there is an inspector (usually not) and adding `goal_remains_stalled` which takes `stalled_on` by reference. This hot path avoids all the value moves and `GoalEvaluation` construction/deconstruction and gets to the very common "nothing needed to be done" outcome as quickly as possible. - It uses `retain_mut` to update `self.obligations.pending`. This requires some adjustments (e.g. handling recursion via the `overflowed` flag with some cleanup code after the `retain_mut` call, and cloning obligations in the error cases). r? @lcnr cc @jdonszelmann @WaffleLapkin
2 parents ae45457 + 7089725 commit 65bcac4

2 files changed

Lines changed: 59 additions & 13 deletions

File tree

  • compiler
    • rustc_next_trait_solver/src/solve/eval_ctxt
    • rustc_trait_selection/src/solve

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ pub trait SolverDelegateEvalExt: SolverDelegate {
179179
stalled_on: Option<GoalStalledOn<Self::Interner>>,
180180
) -> Result<GoalEvaluation<Self::Interner>, NoSolution>;
181181

182+
/// Checks whether a stalled goal would remain stalled if re-evaluated, without consuming
183+
/// `stalled_on`.
184+
fn goal_remains_stalled(&self, stalled_on: &GoalStalledOn<Self::Interner>)
185+
-> Option<Certainty>;
186+
182187
/// Checks whether evaluating `goal` may hold while treating not-yet-defined
183188
/// opaque types as being kind of rigid.
184189
///
@@ -260,6 +265,16 @@ where
260265
}
261266
}
262267

268+
fn goal_remains_stalled(
269+
&self,
270+
stalled_on: &GoalStalledOn<Self::Interner>,
271+
) -> Option<Certainty> {
272+
match rerunning_stalled_goal_may_make_progress(self, Some(stalled_on)) {
273+
RerunStalled::WontMakeProgress(certainty) => Some(certainty),
274+
RerunStalled::MayMakeProgress => None,
275+
}
276+
}
277+
263278
#[instrument(level = "debug", skip(self), ret)]
264279
fn root_goal_may_hold_opaque_types_jank(
265280
&self,

compiler/rustc_trait_selection/src/solve/fulfill.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> {
138138
}
139139

140140
fn inspect_evaluated_obligation(
141-
&self,
142141
infcx: &InferCtxt<'tcx>,
143142
obligation: &PredicateObligation<'tcx>,
144143
result: &Result<GoalEvaluation<TyCtxt<'tcx>>, NoSolution>,
@@ -196,22 +195,39 @@ where
196195
fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors<E> {
197196
assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
198197
let mut errors = TraitErrors::NoErrors;
198+
let delegate = <&SolverDelegate<'tcx>>::from(infcx);
199199
loop {
200200
let mut any_changed = false;
201-
for (mut obligation, stalled_on) in mem::take(&mut self.obligations.pending) {
202-
let goal = obligation.as_goal();
203-
let delegate = <&SolverDelegate<'tcx>>::from(infcx);
201+
let mut overflowed = false;
202+
203+
self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| {
204+
if overflowed {
205+
return false;
206+
}
204207

205-
let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on);
206-
self.inspect_evaluated_obligation(infcx, &obligation, &result);
208+
// Common case: still stalled; keep the obligation. This path is extremely hot in
209+
// some cases; there can be thousands of pending obligations.
210+
if let Some(stalled_on) = opt_stalled_on
211+
&& let Some(certainty) = delegate.goal_remains_stalled(stalled_on)
212+
&& matches!(certainty, Certainty::Maybe(_))
213+
{
214+
return true;
215+
}
216+
217+
let result = delegate.evaluate_root_goal(
218+
obligation.as_goal(),
219+
obligation.cause.span,
220+
opt_stalled_on.take(),
221+
);
222+
Self::inspect_evaluated_obligation(infcx, &obligation, &result);
207223
let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result {
208224
Ok(result) => result,
209225
Err(NoSolution) => {
210226
errors.push(E::from_solver_error(
211227
infcx,
212-
NextSolverError::TrueError(obligation),
228+
NextSolverError::TrueError(obligation.clone()),
213229
));
214-
continue;
230+
return false;
215231
}
216232
};
217233

@@ -229,9 +245,11 @@ where
229245
obligation.recursion_depth += 1;
230246

231247
if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) {
232-
self.obligations.on_fulfillment_overflow(infcx);
233-
// Only return true errors that we have accumulated while processing.
234-
return errors;
248+
// At this point we want to stop evaluating goals. We can't break out of
249+
// `retain_mut`, so instead we set this flag which causes all other
250+
// elements to be skipped.
251+
overflowed = true;
252+
return false;
235253
} else {
236254
any_changed = true;
237255
}
@@ -253,11 +271,24 @@ where
253271
if infcx.in_hir_typeck
254272
&& (obligation.has_non_region_infer() || obligation.has_free_regions())
255273
{
256-
infcx.push_hir_typeck_potentially_region_dependent_goal(obligation);
274+
infcx.push_hir_typeck_potentially_region_dependent_goal(
275+
obligation.clone(),
276+
);
257277
}
278+
false
279+
}
280+
Certainty::Maybe(_) => {
281+
// Update `opt_stalled_on` goal, for the next retain_mut, because we are
282+
// running until a fixpoint.
283+
*opt_stalled_on = stalled_on;
284+
true
258285
}
259-
Certainty::Maybe(_) => self.obligations.register(obligation, stalled_on),
260286
}
287+
});
288+
if overflowed {
289+
self.obligations.on_fulfillment_overflow(infcx);
290+
// Only return true errors that we have accumulated while processing.
291+
return errors;
261292
}
262293

263294
if !any_changed {

0 commit comments

Comments
 (0)