@@ -12,7 +12,6 @@ use rustc_middle::traits::solve::CacheData;
1212use rustc_middle:: traits:: solve:: { CanonicalInput , Certainty , EvaluationCache , QueryResult } ;
1313use rustc_middle:: ty:: TyCtxt ;
1414use rustc_session:: Limit ;
15- use std:: mem;
1615
1716rustc_index:: newtype_index! {
1817 #[ orderable]
@@ -98,13 +97,30 @@ impl<'tcx> ProvisionalCacheEntry<'tcx> {
9897 }
9998}
10099
101- pub ( super ) struct SearchGraph < ' tcx > {
102- mode : SolverMode ,
103- /// The stack of goals currently being computed.
104- ///
105- /// An element is *deeper* in the stack if its index is *lower*.
106- stack : IndexVec < StackDepth , StackEntry < ' tcx > > ,
107- provisional_cache : FxHashMap < CanonicalInput < ' tcx > , ProvisionalCacheEntry < ' tcx > > ,
100+ /// The provisional result for a given goal. It is only applicable if
101+ /// we access the goal with the same stack as during the last itereation.
102+ struct ProvisionalResult < ' tcx > {
103+ stack : IndexVec < StackDepth , CanonicalInput < ' tcx > > ,
104+ result : QueryResult < ' tcx > ,
105+ }
106+
107+ impl < ' tcx > ProvisionalResult < ' tcx > {
108+ fn is_applicable ( & self , stack : & IndexVec < StackDepth , StackEntry < ' tcx > > ) -> bool {
109+ self . stack . len ( ) == stack. len ( )
110+ && itertools:: zip_eq ( & self . stack , stack. iter ( ) . map ( |e| & e. input ) ) . all ( |( l, r) | l == r)
111+ }
112+ }
113+
114+ struct CycleData < ' tcx > {
115+ /// The lowest stack depth of all participants. The root is the only cycle
116+ /// participant which will get moved to the global cache.
117+ root : StackDepth ,
118+
119+ /// The provisional results for all nested cycle heads we've already computed.
120+ /// The next time we evaluate these cycle heads we use that result in the first
121+ /// iteration.
122+ provisional_results : FxHashMap < CanonicalInput < ' tcx > , Vec < ProvisionalResult < ' tcx > > > ,
123+
108124 /// We put only the root goal of a coinductive cycle into the global cache.
109125 ///
110126 /// If we were to use that result when later trying to prove another cycle
@@ -115,13 +131,100 @@ pub(super) struct SearchGraph<'tcx> {
115131 cycle_participants : FxHashSet < CanonicalInput < ' tcx > > ,
116132}
117133
134+ impl < ' tcx > CycleData < ' tcx > {
135+ fn new ( root : StackDepth ) -> CycleData < ' tcx > {
136+ CycleData {
137+ root,
138+ provisional_results : Default :: default ( ) ,
139+ cycle_participants : Default :: default ( ) ,
140+ }
141+ }
142+
143+ /// When encountering a solver cycle, the result of the current goal
144+ /// depends on goals lower on the stack.
145+ ///
146+ /// We have to therefore be careful when caching goals. Only the final result
147+ /// of the cycle root, i.e. the lowest goal on the stack involved in this cycle,
148+ /// is moved to the global cache while all others are stored in a provisional cache.
149+ ///
150+ /// We update both the head of this cycle to rerun its evaluation until
151+ /// we reach a fixpoint and all other cycle participants to make sure that
152+ /// their result does not get moved to the global cache.
153+ fn tag_cycle_participants (
154+ & mut self ,
155+ stack : & mut IndexVec < StackDepth , StackEntry < ' tcx > > ,
156+ usage_kind : HasBeenUsed ,
157+ head : StackDepth ,
158+ ) {
159+ stack[ head] . has_been_used |= usage_kind;
160+ debug_assert ! ( !stack[ head] . has_been_used. is_empty( ) ) ;
161+ self . root = self . root . min ( head) ;
162+ for entry in & mut stack. raw [ head. index ( ) + 1 ..] {
163+ entry. non_root_cycle_participant = entry. non_root_cycle_participant . max ( Some ( head) ) ;
164+ self . cycle_participants . insert ( entry. input ) ;
165+ }
166+
167+ debug ! ( ?self . root, ?stack) ;
168+ }
169+
170+ /// Add the provisional result for a given goal to the storage.
171+ fn add_provisional_result (
172+ & mut self ,
173+ stack : & IndexVec < StackDepth , StackEntry < ' tcx > > ,
174+ entry : & StackEntry < ' tcx > ,
175+ result : QueryResult < ' tcx > ,
176+ ) {
177+ // Add this provisional result for this goal, optionally overwriting its previous entry.
178+ let provisional_result =
179+ ProvisionalResult { stack : stack. iter ( ) . map ( |e| e. input ) . collect ( ) , result } ;
180+ let provisional_results = self . provisional_results . entry ( entry. input ) . or_default ( ) ;
181+ provisional_results. retain ( |result| !result. is_applicable ( stack) ) ;
182+ provisional_results. push ( provisional_result) ;
183+ }
184+
185+ fn get_provisional_result (
186+ & mut self ,
187+ stack : & IndexVec < StackDepth , StackEntry < ' tcx > > ,
188+ input : CanonicalInput < ' tcx > ,
189+ ) -> Option < QueryResult < ' tcx > > {
190+ self . provisional_results . get ( & input) . and_then ( |results| {
191+ for result in results {
192+ if result. is_applicable ( stack) {
193+ return Some ( result. result ) ;
194+ }
195+ }
196+
197+ None
198+ } )
199+ }
200+ }
201+
202+ pub ( super ) struct SearchGraph < ' tcx > {
203+ mode : SolverMode ,
204+ /// The stack of goals currently being computed.
205+ ///
206+ /// An element is *deeper* in the stack if its index is *lower*.
207+ stack : IndexVec < StackDepth , StackEntry < ' tcx > > ,
208+
209+ /// In case we're currently in a solver cycle, we have to track a
210+ /// lot of additional data.
211+ cycle_data : Option < CycleData < ' tcx > > ,
212+
213+ /// A cache for the result of nested goals which depend on goals currently on the
214+ /// stack. We remove cached results once we pop any goal used while computing it.
215+ ///
216+ /// This is not part of `cycle_data` as it contains all stack entries even while we're
217+ /// not yet in a cycle.
218+ provisional_cache : FxHashMap < CanonicalInput < ' tcx > , ProvisionalCacheEntry < ' tcx > > ,
219+ }
220+
118221impl < ' tcx > SearchGraph < ' tcx > {
119222 pub ( super ) fn new ( mode : SolverMode ) -> SearchGraph < ' tcx > {
120223 Self {
121224 mode,
122225 stack : Default :: default ( ) ,
226+ cycle_data : None ,
123227 provisional_cache : Default :: default ( ) ,
124- cycle_participants : Default :: default ( ) ,
125228 }
126229 }
127230
@@ -165,9 +268,10 @@ impl<'tcx> SearchGraph<'tcx> {
165268 }
166269
167270 pub ( super ) fn is_empty ( & self ) -> bool {
168- if self . stack . is_empty ( ) {
169- debug_assert ! ( self . provisional_cache. is_empty( ) ) ;
170- debug_assert ! ( self . cycle_participants. is_empty( ) ) ;
271+ let Self { mode : _, stack, cycle_data, provisional_cache } = self ;
272+ if stack. is_empty ( ) {
273+ debug_assert ! ( cycle_data. is_none( ) ) ;
274+ debug_assert ! ( provisional_cache. is_empty( ) ) ;
171275 true
172276 } else {
173277 false
@@ -209,30 +313,6 @@ impl<'tcx> SearchGraph<'tcx> {
209313 . all ( |entry| entry. input . value . goal . predicate . is_coinductive ( tcx) )
210314 }
211315
212- // When encountering a solver cycle, the result of the current goal
213- // depends on goals lower on the stack.
214- //
215- // We have to therefore be careful when caching goals. Only the final result
216- // of the cycle root, i.e. the lowest goal on the stack involved in this cycle,
217- // is moved to the global cache while all others are stored in a provisional cache.
218- //
219- // We update both the head of this cycle to rerun its evaluation until
220- // we reach a fixpoint and all other cycle participants to make sure that
221- // their result does not get moved to the global cache.
222- fn tag_cycle_participants (
223- stack : & mut IndexVec < StackDepth , StackEntry < ' tcx > > ,
224- cycle_participants : & mut FxHashSet < CanonicalInput < ' tcx > > ,
225- usage_kind : HasBeenUsed ,
226- head : StackDepth ,
227- ) {
228- stack[ head] . has_been_used |= usage_kind;
229- debug_assert ! ( !stack[ head] . has_been_used. is_empty( ) ) ;
230- for entry in & mut stack. raw [ head. index ( ) + 1 ..] {
231- entry. non_root_cycle_participant = entry. non_root_cycle_participant . max ( Some ( head) ) ;
232- cycle_participants. insert ( entry. input ) ;
233- }
234- }
235-
236316 fn clear_dependent_provisional_results (
237317 provisional_cache : & mut FxHashMap < CanonicalInput < ' tcx > , ProvisionalCacheEntry < ' tcx > > ,
238318 head : StackDepth ,
@@ -322,9 +402,9 @@ impl<'tcx> SearchGraph<'tcx> {
322402 // already set correctly while computing the cache entry.
323403 inspect
324404 . goal_evaluation_kind ( inspect:: WipCanonicalGoalEvaluationKind :: ProvisionalCacheHit ) ;
325- Self :: tag_cycle_participants (
405+
406+ self . cycle_data . as_mut ( ) . unwrap ( ) . tag_cycle_participants (
326407 & mut self . stack ,
327- & mut self . cycle_participants ,
328408 HasBeenUsed :: empty ( ) ,
329409 entry. head ,
330410 ) ;
@@ -344,12 +424,9 @@ impl<'tcx> SearchGraph<'tcx> {
344424 } else {
345425 HasBeenUsed :: INDUCTIVE_CYCLE
346426 } ;
347- Self :: tag_cycle_participants (
348- & mut self . stack ,
349- & mut self . cycle_participants ,
350- usage_kind,
351- stack_depth,
352- ) ;
427+
428+ let cycle_data = self . cycle_data . get_or_insert_with ( || CycleData :: new ( stack_depth) ) ;
429+ cycle_data. tag_cycle_participants ( & mut self . stack , usage_kind, stack_depth) ;
353430
354431 // Return the provisional result or, if we're in the first iteration,
355432 // start with no constraints.
@@ -363,14 +440,18 @@ impl<'tcx> SearchGraph<'tcx> {
363440 } else {
364441 // No entry, we push this goal on the stack and try to prove it.
365442 let depth = self . stack . next_index ( ) ;
443+ let provisional_result = self
444+ . cycle_data
445+ . as_mut ( )
446+ . and_then ( |cycle_data| cycle_data. get_provisional_result ( & self . stack , input) ) ;
366447 let entry = StackEntry {
367448 input,
368449 available_depth,
369450 reached_depth : depth,
370451 non_root_cycle_participant : None ,
371452 encountered_overflow : false ,
372453 has_been_used : HasBeenUsed :: empty ( ) ,
373- provisional_result : None ,
454+ provisional_result,
374455 } ;
375456 assert_eq ! ( self . stack. push( entry) , depth) ;
376457 cache_entry. stack_depth = Some ( depth) ;
@@ -386,14 +467,15 @@ impl<'tcx> SearchGraph<'tcx> {
386467 // of this we continuously recompute the cycle until the result
387468 // of the previous iteration is equal to the final result, at which
388469 // point we are done.
389- for _ in 0 ..FIXPOINT_STEP_LIMIT {
470+ for i in 0 ..FIXPOINT_STEP_LIMIT {
390471 let result = prove_goal ( self , inspect) ;
391- let stack_entry = self . pop_stack ( ) ;
392- debug_assert_eq ! ( stack_entry . input, input) ;
472+ let entry = self . pop_stack ( ) ;
473+ debug_assert_eq ! ( entry . input, input) ;
393474
394- // If the current goal is not the root of a cycle, we are done.
395- if stack_entry. has_been_used . is_empty ( ) {
396- return ( stack_entry, result) ;
475+ // For goals which are not the head of a cycle, this is
476+ // trivial.
477+ if entry. has_been_used . is_empty ( ) {
478+ return ( entry, result) ;
397479 }
398480
399481 // If it is a cycle head, we have to keep trying to prove it until
@@ -414,25 +496,27 @@ impl<'tcx> SearchGraph<'tcx> {
414496 // is equal to the provisional result of the previous iteration, or because
415497 // this was only the root of either coinductive or inductive cycles, and the
416498 // final result is equal to the initial response for that case.
417- let reached_fixpoint = if let Some ( r) = stack_entry . provisional_result {
499+ let reached_fixpoint = if let Some ( r) = entry . provisional_result {
418500 r == result
419- } else if stack_entry . has_been_used == HasBeenUsed :: COINDUCTIVE_CYCLE {
420- Self :: response_no_constraints ( tcx, input, Certainty :: Yes ) == result
421- } else if stack_entry . has_been_used == HasBeenUsed :: INDUCTIVE_CYCLE {
422- Self :: response_no_constraints ( tcx, input, Certainty :: overflow ( false ) )
501+ } else if entry . has_been_used == HasBeenUsed :: COINDUCTIVE_CYCLE {
502+ Self :: response_no_constraints ( tcx, entry . input , Certainty :: Yes ) == result
503+ } else if entry . has_been_used == HasBeenUsed :: INDUCTIVE_CYCLE {
504+ Self :: response_no_constraints ( tcx, entry . input , Certainty :: overflow ( false ) )
423505 == result
424506 } else {
425507 false
426508 } ;
427509
428510 // If we did not reach a fixpoint, update the provisional result and reevaluate.
429511 if reached_fixpoint {
430- return ( stack_entry, result) ;
512+ let cycle_data = self . cycle_data . as_mut ( ) . unwrap ( ) ;
513+ cycle_data. add_provisional_result ( & self . stack , & entry, result) ;
514+ return ( entry, result) ;
431515 } else {
432516 let depth = self . stack . push ( StackEntry {
433517 has_been_used : HasBeenUsed :: empty ( ) ,
434518 provisional_result : Some ( result) ,
435- ..stack_entry
519+ ..entry
436520 } ) ;
437521 debug_assert_eq ! ( self . provisional_cache[ & input] . stack_depth, Some ( depth) ) ;
438522 }
@@ -463,7 +547,6 @@ impl<'tcx> SearchGraph<'tcx> {
463547 } else {
464548 self . provisional_cache . remove ( & input) ;
465549 let reached_depth = final_entry. reached_depth . as_usize ( ) - self . stack . len ( ) ;
466- let cycle_participants = mem:: take ( & mut self . cycle_participants ) ;
467550 // When encountering a cycle, both inductive and coinductive, we only
468551 // move the root into the global cache. We also store all other cycle
469552 // participants involved.
@@ -472,6 +555,14 @@ impl<'tcx> SearchGraph<'tcx> {
472555 // participant is on the stack. This is necessary to prevent unstable
473556 // results. See the comment of `SearchGraph::cycle_participants` for
474557 // more details.
558+ let cycle_participants = if let Some ( cycle_data) =
559+ self . cycle_data . take_if ( |data| data. root == self . stack . next_index ( ) )
560+ {
561+ cycle_data. cycle_participants
562+ } else {
563+ Default :: default ( )
564+ } ;
565+
475566 self . global_cache ( tcx) . insert (
476567 tcx,
477568 input,
0 commit comments