@@ -154,12 +154,12 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
154
154
/// selection-context's freshener. Used to check for recursion.
155
155
fresh_trait_ref : ty:: PolyTraitRef < ' tcx > ,
156
156
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:
163
163
///
164
164
/// - If you have `A: AutoTrait` requires `B: AutoTrait` and `C: NonAutoTrait`
165
165
/// - `B: AutoTrait` requires `A: AutoTrait` (coinductive cycle, ok)
@@ -182,7 +182,7 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
182
182
/// evaluate each member of a cycle up to N times, where N is the
183
183
/// length of the cycle. This means the performance impact is
184
184
/// bounded and we shouldn't have any terrible worst-cases.
185
- in_cycle : Cell < bool > ,
185
+ reached_depth : Cell < usize > ,
186
186
187
187
previous : TraitObligationStackList < ' prev , ' tcx > ,
188
188
@@ -877,14 +877,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
877
877
let ( result, dep_node) = self . in_task ( |this| this. evaluate_stack ( & stack) ) ;
878
878
let result = result?;
879
879
880
- if !stack. in_cycle . get ( ) {
880
+ let reached_depth = stack. reached_depth . get ( ) ;
881
+ if reached_depth >= stack. depth {
881
882
debug ! ( "CACHE MISS: EVAL({:?})={:?}" , fresh_trait_ref, result) ;
882
883
self . insert_evaluation_cache ( obligation. param_env , fresh_trait_ref, dep_node, result) ;
883
884
} else {
884
885
debug ! (
885
886
"evaluate_trait_predicate_recursively: skipping cache because {:?} \
886
- is a cycle participant",
887
+ is a cycle participant (at depth {}, reached depth {}) ",
887
888
fresh_trait_ref,
889
+ stack. depth,
890
+ reached_depth,
888
891
) ;
889
892
}
890
893
@@ -986,10 +989,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
986
989
// affect the inferencer state and (b) that if we see two
987
990
// fresh regions with the same index, they refer to the same
988
991
// 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 )
993
997
{
994
998
debug ! ( "evaluate_stack({:?}) --> recursive" , stack. fresh_trait_ref) ;
995
999
@@ -999,17 +1003,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
999
1003
// from the cycle head. We mark them as participating in a
1000
1004
// cycle. This suppresses caching for those nodes. See
1001
1005
// `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 ) {
1003
1007
debug ! ( "evaluate_stack: marking {:?} as cycle participant" , item. fresh_trait_ref) ;
1004
- item. in_cycle . set ( true ) ;
1008
+ item. update_reached_depth ( cycle_depth ) ;
1005
1009
}
1006
1010
1007
1011
// Subtle: when checking for a coinductive cycle, we do
1008
1012
// not compare using the "freshened trait refs" (which
1009
1013
// have erased regions) but rather the fully explicit
1010
1014
// trait refs. This is important because it's only a cycle
1011
1015
// 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 ) ;
1013
1017
let cycle = cycle. map ( |stack| ty:: Predicate :: Trait ( stack. obligation . predicate ) ) ;
1014
1018
if self . coinductive_match ( cycle) {
1015
1019
debug ! (
@@ -1228,6 +1232,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
1228
1232
}
1229
1233
1230
1234
// 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.
1231
1240
let ( candidate, dep_node) =
1232
1241
self . in_task ( |this| this. candidate_from_obligation_no_cache ( stack) ) ;
1233
1242
@@ -3743,12 +3752,13 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
3743
3752
. to_poly_trait_ref ( )
3744
3753
. fold_with ( & mut self . freshener ) ;
3745
3754
3755
+ let depth = previous_stack. depth ( ) + 1 ;
3746
3756
TraitObligationStack {
3747
3757
obligation,
3748
3758
fresh_trait_ref,
3749
- in_cycle : Cell :: new ( false ) ,
3759
+ reached_depth : Cell :: new ( depth ) ,
3750
3760
previous : previous_stack,
3751
- depth : previous_stack . depth ( ) + 1 ,
3761
+ depth,
3752
3762
}
3753
3763
}
3754
3764
@@ -3944,6 +3954,21 @@ impl<'o, 'tcx> TraitObligationStack<'o, 'tcx> {
3944
3954
fn iter ( & ' o self ) -> TraitObligationStackList < ' o , ' tcx > {
3945
3955
self . list ( )
3946
3956
}
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
+ }
3947
3972
}
3948
3973
3949
3974
#[ derive( Copy , Clone ) ]
0 commit comments