Skip to content

Commit 5486a86

Browse files
committed
propagate depth that was reached, not just whether there was a cycle
I tried to propagate this information with the return value, but I found a curiosity (actually, something that I'm not keen on in general) -- in particular, the candidate cache urrently invokes evaluation, which may detect a cycle, but the "depth" that results from that are is easily propagated back out. This probably means that the candidate caching mechanism *itself* is sort of problematic, but I'm choosing to ignore that in favor of a more ambitious rewrite later.
1 parent e64a7bf commit 5486a86

File tree

1 file changed

+43
-18
lines changed

1 file changed

+43
-18
lines changed

src/librustc/traits/select.rs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
154154
/// selection-context's freshener. Used to check for recursion.
155155
fresh_trait_ref: ty::PolyTraitRef<'tcx>,
156156

157-
/// Starts out as false -- if, during evaluation, we encounter a
158-
/// cycle, then we will set this flag to true for all participants
159-
/// in the cycle (apart from the "head" node). These participants
160-
/// will then forego caching their results. This is not the most
161-
/// efficient solution, but it addresses #60010. The problem we
162-
/// are trying to prevent:
157+
/// Starts out equal to `depth` -- if, during evaluation, we
158+
/// encounter a cycle, then we will set this flag to the minimum
159+
/// depth of that cycle for all participants in the cycle. These
160+
/// participants will then forego caching their results. This is
161+
/// not the most efficient solution, but it addresses #60010. The
162+
/// problem we are trying to prevent:
163163
///
164164
/// - If you have `A: AutoTrait` requires `B: AutoTrait` and `C: NonAutoTrait`
165165
/// - `B: AutoTrait` requires `A: AutoTrait` (coinductive cycle, ok)
@@ -182,7 +182,7 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
182182
/// evaluate each member of a cycle up to N times, where N is the
183183
/// length of the cycle. This means the performance impact is
184184
/// bounded and we shouldn't have any terrible worst-cases.
185-
in_cycle: Cell<bool>,
185+
reached_depth: Cell<usize>,
186186

187187
previous: TraitObligationStackList<'prev, 'tcx>,
188188

@@ -877,14 +877,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
877877
let (result, dep_node) = self.in_task(|this| this.evaluate_stack(&stack));
878878
let result = result?;
879879

880-
if !stack.in_cycle.get() {
880+
let reached_depth = stack.reached_depth.get();
881+
if reached_depth >= stack.depth {
881882
debug!("CACHE MISS: EVAL({:?})={:?}", fresh_trait_ref, result);
882883
self.insert_evaluation_cache(obligation.param_env, fresh_trait_ref, dep_node, result);
883884
} else {
884885
debug!(
885886
"evaluate_trait_predicate_recursively: skipping cache because {:?} \
886-
is a cycle participant",
887+
is a cycle participant (at depth {}, reached depth {})",
887888
fresh_trait_ref,
889+
stack.depth,
890+
reached_depth,
888891
);
889892
}
890893

@@ -986,10 +989,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
986989
// affect the inferencer state and (b) that if we see two
987990
// fresh regions with the same index, they refer to the same
988991
// unbound type variable.
989-
if let Some(rec_index) = stack.iter()
990-
.skip(1) // skip top-most frame
991-
.position(|prev| stack.obligation.param_env == prev.obligation.param_env &&
992-
stack.fresh_trait_ref == prev.fresh_trait_ref)
992+
if let Some(cycle_depth) = stack.iter()
993+
.skip(1) // skip top-most frame
994+
.find(|prev| stack.obligation.param_env == prev.obligation.param_env &&
995+
stack.fresh_trait_ref == prev.fresh_trait_ref)
996+
.map(|stack| stack.depth)
993997
{
994998
debug!("evaluate_stack({:?}) --> recursive", stack.fresh_trait_ref);
995999

@@ -999,17 +1003,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
9991003
// from the cycle head. We mark them as participating in a
10001004
// cycle. This suppresses caching for those nodes. See
10011005
// `in_cycle` field for more details.
1002-
for item in stack.iter().take(rec_index + 1) {
1006+
for item in stack.iter().take_while(|s| s.depth > cycle_depth) {
10031007
debug!("evaluate_stack: marking {:?} as cycle participant", item.fresh_trait_ref);
1004-
item.in_cycle.set(true);
1008+
item.update_reached_depth(cycle_depth);
10051009
}
10061010

10071011
// Subtle: when checking for a coinductive cycle, we do
10081012
// not compare using the "freshened trait refs" (which
10091013
// have erased regions) but rather the fully explicit
10101014
// trait refs. This is important because it's only a cycle
10111015
// if the regions match exactly.
1012-
let cycle = stack.iter().skip(1).take(rec_index + 1);
1016+
let cycle = stack.iter().skip(1).take_while(|s| s.depth >= cycle_depth);
10131017
let cycle = cycle.map(|stack| ty::Predicate::Trait(stack.obligation.predicate));
10141018
if self.coinductive_match(cycle) {
10151019
debug!(
@@ -1228,6 +1232,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
12281232
}
12291233

12301234
// If no match, compute result and insert into cache.
1235+
//
1236+
// FIXME(nikomatsakis) -- this cache is not taking into
1237+
// account cycles that may have occurred in forming the
1238+
// candidate. I don't know of any specific problems that
1239+
// result but it seems awfully suspicious.
12311240
let (candidate, dep_node) =
12321241
self.in_task(|this| this.candidate_from_obligation_no_cache(stack));
12331242

@@ -3743,12 +3752,13 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
37433752
.to_poly_trait_ref()
37443753
.fold_with(&mut self.freshener);
37453754

3755+
let depth = previous_stack.depth() + 1;
37463756
TraitObligationStack {
37473757
obligation,
37483758
fresh_trait_ref,
3749-
in_cycle: Cell::new(false),
3759+
reached_depth: Cell::new(depth),
37503760
previous: previous_stack,
3751-
depth: previous_stack.depth() + 1,
3761+
depth,
37523762
}
37533763
}
37543764

@@ -3944,6 +3954,21 @@ impl<'o, 'tcx> TraitObligationStack<'o, 'tcx> {
39443954
fn iter(&'o self) -> TraitObligationStackList<'o, 'tcx> {
39453955
self.list()
39463956
}
3957+
3958+
/// Indicates that attempting to evaluate this stack entry
3959+
/// required accessing something from the stack at depth `reached_depth`.
3960+
fn update_reached_depth(&self, reached_depth: usize) {
3961+
assert!(
3962+
self.depth > reached_depth,
3963+
"invoked `update_reached_depth` with something under this stack: \
3964+
self.depth={} reached_depth={}",
3965+
self.depth,
3966+
reached_depth,
3967+
);
3968+
debug!("update_reached_depth(reached_depth={})", reached_depth);
3969+
let v = self.reached_depth.get();
3970+
self.reached_depth.set(v.min(reached_depth));
3971+
}
39473972
}
39483973

39493974
#[derive(Copy, Clone)]

0 commit comments

Comments
 (0)