Skip to content

Commit 6dc112d

Browse files
committed
Auto merge of #31349 - nikomatsakis:issue-31157-obligation-forest-cache, r=aturon
Have the `ObligationForest` keep some per-tree state (or type `T`) and have it give a mutable reference for use when processing obligations. In this case, it will be a hashmap. This obviously affects the work that @soltanmm has been doing on snapshotting. I partly want to toss this out there for discussion. Fixes #31157. (The test in question goes to approx. 30s instead of 5 minutes for me.) cc #30977. cc @aturon @arielb1 @soltanmm r? @aturon who reviewed original `ObligationForest`
2 parents 98422e8 + 35d6efb commit 6dc112d

File tree

5 files changed

+314
-152
lines changed

5 files changed

+314
-152
lines changed

src/librustc/middle/traits/fulfill.rs

+123-56
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub struct GlobalFulfilledPredicates<'tcx> {
3636
dep_graph: DepGraph,
3737
}
3838

39+
#[derive(Debug)]
3940
pub struct LocalFulfilledPredicates<'tcx> {
4041
set: FnvHashSet<ty::Predicate<'tcx>>
4142
}
@@ -66,7 +67,8 @@ pub struct FulfillmentContext<'tcx> {
6667

6768
// A list of all obligations that have been registered with this
6869
// fulfillment context.
69-
predicates: ObligationForest<PendingPredicateObligation<'tcx>>,
70+
predicates: ObligationForest<PendingPredicateObligation<'tcx>,
71+
LocalFulfilledPredicates<'tcx>>,
7072

7173
// A set of constraints that regionck must validate. Each
7274
// constraint has the form `T:'a`, meaning "some type `T` must
@@ -192,7 +194,7 @@ impl<'tcx> FulfillmentContext<'tcx> {
192194
obligation: obligation,
193195
stalled_on: vec![]
194196
};
195-
self.predicates.push_root(obligation);
197+
self.predicates.push_tree(obligation, LocalFulfilledPredicates::new());
196198
}
197199

198200
pub fn region_obligations(&self,
@@ -278,10 +280,11 @@ impl<'tcx> FulfillmentContext<'tcx> {
278280
let outcome = {
279281
let region_obligations = &mut self.region_obligations;
280282
self.predicates.process_obligations(
281-
|obligation, backtrace| process_predicate(selcx,
282-
obligation,
283-
backtrace,
284-
region_obligations))
283+
|obligation, tree, backtrace| process_predicate(selcx,
284+
tree,
285+
obligation,
286+
backtrace,
287+
region_obligations))
285288
};
286289

287290
debug!("select_where_possible: outcome={:?}", outcome);
@@ -315,61 +318,97 @@ impl<'tcx> FulfillmentContext<'tcx> {
315318

316319
/// Like `process_predicate1`, but wrap result into a pending predicate.
317320
fn process_predicate<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
321+
tree_cache: &mut LocalFulfilledPredicates<'tcx>,
318322
pending_obligation: &mut PendingPredicateObligation<'tcx>,
319-
backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
323+
mut backtrace: Backtrace<PendingPredicateObligation<'tcx>>,
320324
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>)
321325
-> Result<Option<Vec<PendingPredicateObligation<'tcx>>>,
322326
FulfillmentErrorCode<'tcx>>
323327
{
324-
match process_predicate1(selcx, pending_obligation, backtrace, region_obligations) {
328+
match process_predicate1(selcx, pending_obligation, backtrace.clone(), region_obligations) {
325329
Ok(Some(v)) => {
326-
// FIXME(#30977) the right thing to do here, I think, is to permit
327-
// DAGs. That is, we should detect whenever this predicate
328-
// has appeared somewhere in the current tree./ If it's a
329-
// parent, that's a cycle, and we should either error out
330-
// or consider it ok. But if it's NOT a parent, we can
331-
// ignore it, since it will be proven (or not) separately.
332-
// However, this is a touch tricky, so I'm doing something
333-
// a bit hackier for now so that the `huge-struct.rs` passes.
330+
// FIXME(#30977) The code below is designed to detect (and
331+
// permit) DAGs, while still ensuring that the reasoning
332+
// is acyclic. However, it does a few things
333+
// suboptimally. For example, it refreshes type variables
334+
// a lot, probably more than needed, but also less than
335+
// you might want.
336+
//
337+
// - more than needed: I want to be very sure we don't
338+
// accidentally treat a cycle as a DAG, so I am
339+
// refreshing type variables as we walk the ancestors;
340+
// but we are going to repeat this a lot, which is
341+
// sort of silly, and it would be nicer to refresh
342+
// them *in place* so that later predicate processing
343+
// can benefit from the same work;
344+
// - less than you might want: we only add items in the cache here,
345+
// but maybe we learn more about type variables and could add them into
346+
// the cache later on.
334347

335348
let tcx = selcx.tcx();
336349

337-
let retain_vec: Vec<_> = {
338-
let mut dedup = FnvHashSet();
339-
v.iter()
340-
.map(|o| {
350+
// Compute a little FnvHashSet for the ancestors. We only
351+
// do this the first time that we care.
352+
let mut cache = None;
353+
let mut is_ancestor = |predicate: &ty::Predicate<'tcx>| {
354+
if cache.is_none() {
355+
let mut c = FnvHashSet();
356+
for ancestor in backtrace.by_ref() {
357+
// Ugh. This just feels ridiculously
358+
// inefficient. But we need to compare
359+
// predicates without being concerned about
360+
// the vagaries of type inference, so for now
361+
// just ensure that they are always
362+
// up-to-date. (I suppose we could just use a
363+
// snapshot and check if they are unifiable?)
364+
let resolved_predicate =
365+
selcx.infcx().resolve_type_vars_if_possible(
366+
&ancestor.obligation.predicate);
367+
c.insert(resolved_predicate);
368+
}
369+
cache = Some(c);
370+
}
371+
372+
cache.as_ref().unwrap().contains(predicate)
373+
};
374+
375+
let pending_predicate_obligations: Vec<_> =
376+
v.into_iter()
377+
.filter_map(|obligation| {
378+
// Probably silly, but remove any inference
379+
// variables. This is actually crucial to the
380+
// ancestor check below, but it's not clear that
381+
// it makes sense to ALWAYS do it.
382+
let obligation = selcx.infcx().resolve_type_vars_if_possible(&obligation);
383+
341384
// Screen out obligations that we know globally
342385
// are true. This should really be the DAG check
343386
// mentioned above.
344-
if tcx.fulfilled_predicates.borrow().check_duplicate(&o.predicate) {
345-
return false;
387+
if tcx.fulfilled_predicates.borrow().check_duplicate(&obligation.predicate) {
388+
return None;
346389
}
347390

348-
// If we see two siblings that are exactly the
349-
// same, no need to add them twice.
350-
if !dedup.insert(&o.predicate) {
351-
return false;
391+
// Check whether this obligation appears somewhere else in the tree.
392+
if tree_cache.is_duplicate_or_add(&obligation.predicate) {
393+
// If the obligation appears as a parent,
394+
// allow it, because that is a cycle.
395+
// Otherwise though we can just ignore
396+
// it. Note that we have to be careful around
397+
// inference variables here -- for the
398+
// purposes of the ancestor check, we retain
399+
// the invariant that all type variables are
400+
// fully refreshed.
401+
if !(&mut is_ancestor)(&obligation.predicate) {
402+
return None;
403+
}
352404
}
353405

354-
true
406+
Some(PendingPredicateObligation {
407+
obligation: obligation,
408+
stalled_on: vec![]
409+
})
355410
})
356-
.collect()
357-
};
358-
359-
let pending_predicate_obligations =
360-
v.into_iter()
361-
.zip(retain_vec)
362-
.flat_map(|(o, retain)| {
363-
if retain {
364-
Some(PendingPredicateObligation {
365-
obligation: o,
366-
stalled_on: vec![]
367-
})
368-
} else {
369-
None
370-
}
371-
})
372-
.collect();
411+
.collect();
373412

374413
Ok(Some(pending_predicate_obligations))
375414
}
@@ -405,7 +444,7 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
405444
pending_obligation.stalled_on = vec![];
406445
}
407446

408-
let obligation = &pending_obligation.obligation;
447+
let obligation = &mut pending_obligation.obligation;
409448

410449
// If we exceed the recursion limit, take a moment to look for a
411450
// cycle so we can give a better error report from here, where we
@@ -417,18 +456,31 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
417456
}
418457
}
419458

459+
if obligation.predicate.has_infer_types() {
460+
obligation.predicate = selcx.infcx().resolve_type_vars_if_possible(&obligation.predicate);
461+
}
462+
420463
match obligation.predicate {
421464
ty::Predicate::Trait(ref data) => {
465+
if selcx.tcx().fulfilled_predicates.borrow().check_duplicate_trait(data) {
466+
return Ok(Some(vec![]));
467+
}
468+
422469
if coinductive_match(selcx, obligation, data, &backtrace) {
423470
return Ok(Some(vec![]));
424471
}
425472

426473
let trait_obligation = obligation.with(data.clone());
427474
match selcx.select(&trait_obligation) {
428475
Ok(Some(vtable)) => {
476+
info!("selecting trait `{:?}` at depth {} yielded Ok(Some)",
477+
data, obligation.recursion_depth);
429478
Ok(Some(vtable.nested_obligations()))
430479
}
431480
Ok(None) => {
481+
info!("selecting trait `{:?}` at depth {} yielded Ok(None)",
482+
data, obligation.recursion_depth);
483+
432484
// This is a bit subtle: for the most part, the
433485
// only reason we can fail to make progress on
434486
// trait selection is because we don't have enough
@@ -457,6 +509,8 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
457509
Ok(None)
458510
}
459511
Err(selection_err) => {
512+
info!("selecting trait `{:?}` at depth {} yielded Err",
513+
data, obligation.recursion_depth);
460514
Err(CodeSelectionError(selection_err))
461515
}
462516
}
@@ -642,18 +696,28 @@ impl<'tcx> GlobalFulfilledPredicates<'tcx> {
642696

643697
pub fn check_duplicate(&self, key: &ty::Predicate<'tcx>) -> bool {
644698
if let ty::Predicate::Trait(ref data) = *key {
645-
// For the global predicate registry, when we find a match, it
646-
// may have been computed by some other task, so we want to
647-
// add a read from the node corresponding to the predicate
648-
// processing to make sure we get the transitive dependencies.
649-
if self.set.contains(data) {
650-
debug_assert!(data.is_global());
651-
self.dep_graph.read(data.dep_node());
652-
return true;
653-
}
699+
self.check_duplicate_trait(data)
700+
} else {
701+
false
654702
}
703+
}
704+
705+
pub fn check_duplicate_trait(&self, data: &ty::PolyTraitPredicate<'tcx>) -> bool {
706+
// For the global predicate registry, when we find a match, it
707+
// may have been computed by some other task, so we want to
708+
// add a read from the node corresponding to the predicate
709+
// processing to make sure we get the transitive dependencies.
710+
if self.set.contains(data) {
711+
debug_assert!(data.is_global());
712+
self.dep_graph.read(data.dep_node());
713+
debug!("check_duplicate: global predicate `{:?}` already proved elsewhere", data);
714+
715+
info!("check_duplicate_trait hit: `{:?}`", data);
655716

656-
return false;
717+
true
718+
} else {
719+
false
720+
}
657721
}
658722

659723
fn add_if_global(&mut self, key: &ty::Predicate<'tcx>) {
@@ -663,7 +727,10 @@ impl<'tcx> GlobalFulfilledPredicates<'tcx> {
663727
// already has the required read edges, so we don't need
664728
// to add any more edges here.
665729
if data.is_global() {
666-
self.set.insert(data.clone());
730+
if self.set.insert(data.clone()) {
731+
debug!("add_if_global: global predicate `{:?}` added", data);
732+
info!("check_duplicate_trait entry: `{:?}`", data);
733+
}
667734
}
668735
}
669736
}

src/librustc_data_structures/obligation_forest/README.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ place).
99
`ObligationForest` supports two main public operations (there are a
1010
few others not discussed here):
1111

12-
1. Add a new root obligation (`push_root`).
12+
1. Add a new root obligations (`push_tree`).
1313
2. Process the pending obligations (`process_obligations`).
1414

1515
When a new obligation `N` is added, it becomes the root of an
16-
obligation tree. This tree is a singleton to start, so `N` is both the
17-
root and the only leaf. Each time the `process_obligations` method is
18-
called, it will invoke its callback with every pending obligation (so
19-
that will include `N`, the first time). The callback shoud process the
20-
obligation `O` that it is given and return one of three results:
16+
obligation tree. This tree can also carry some per-tree state `T`,
17+
which is given at the same time. This tree is a singleton to start, so
18+
`N` is both the root and the only leaf. Each time the
19+
`process_obligations` method is called, it will invoke its callback
20+
with every pending obligation (so that will include `N`, the first
21+
time). The callback also receives a (mutable) reference to the
22+
per-tree state `T`. The callback should process the obligation `O`
23+
that it is given and return one of three results:
2124

2225
- `Ok(None)` -> ambiguous result. Obligation was neither a success
2326
nor a failure. It is assumed that further attempts to process the

0 commit comments

Comments
 (0)