12
12
//! The global cache has to be completely unobservable, while the per-cycle cache may impact
13
13
//! behavior as long as the resulting behavior is still correct.
14
14
use std:: cmp:: Ordering ;
15
- use std:: collections:: BTreeMap ;
16
15
use std:: collections:: hash_map:: Entry ;
16
+ use std:: collections:: { BTreeMap , btree_map} ;
17
17
use std:: fmt:: Debug ;
18
18
use std:: hash:: Hash ;
19
+ use std:: iter;
19
20
use std:: marker:: PhantomData ;
20
21
21
22
use derive_where:: derive_where;
@@ -230,13 +231,19 @@ impl AvailableDepth {
230
231
}
231
232
}
232
233
234
+ #[ derive( Clone , Copy , Debug ) ]
235
+ struct CycleHead {
236
+ paths_to_head : PathsToNested ,
237
+ usage_kind : UsageKind ,
238
+ }
239
+
233
240
/// All cycle heads a given goal depends on, ordered by their stack depth.
234
241
///
235
242
/// We also track all paths from this goal to that head. This is necessary
236
243
/// when rebasing provisional cache results.
237
244
#[ derive( Clone , Debug , Default ) ]
238
245
struct CycleHeads {
239
- heads : BTreeMap < StackDepth , PathsToNested > ,
246
+ heads : BTreeMap < StackDepth , CycleHead > ,
240
247
}
241
248
242
249
impl CycleHeads {
@@ -256,32 +263,32 @@ impl CycleHeads {
256
263
self . heads . first_key_value ( ) . map ( |( k, _) | * k)
257
264
}
258
265
259
- fn remove_highest_cycle_head ( & mut self ) -> PathsToNested {
266
+ fn remove_highest_cycle_head ( & mut self ) -> CycleHead {
260
267
let last = self . heads . pop_last ( ) ;
261
268
last. unwrap ( ) . 1
262
269
}
263
270
264
- fn insert ( & mut self , head : StackDepth , path_from_entry : impl Into < PathsToNested > + Copy ) {
265
- * self . heads . entry ( head) . or_insert ( path_from_entry. into ( ) ) |= path_from_entry. into ( ) ;
271
+ fn insert (
272
+ & mut self ,
273
+ head_index : StackDepth ,
274
+ path_from_entry : impl Into < PathsToNested > + Copy ,
275
+ usage_kind : UsageKind ,
276
+ ) {
277
+ match self . heads . entry ( head_index) {
278
+ btree_map:: Entry :: Vacant ( entry) => {
279
+ entry. insert ( CycleHead { paths_to_head : path_from_entry. into ( ) , usage_kind } ) ;
280
+ }
281
+ btree_map:: Entry :: Occupied ( entry) => {
282
+ let head = entry. into_mut ( ) ;
283
+ head. paths_to_head |= path_from_entry. into ( ) ;
284
+ head. usage_kind = head. usage_kind . merge ( usage_kind) ;
285
+ }
286
+ }
266
287
}
267
288
268
- fn iter ( & self ) -> impl Iterator < Item = ( StackDepth , PathsToNested ) > + ' _ {
289
+ fn iter ( & self ) -> impl Iterator < Item = ( StackDepth , CycleHead ) > + ' _ {
269
290
self . heads . iter ( ) . map ( |( k, v) | ( * k, * v) )
270
291
}
271
-
272
- /// Update the cycle heads of a goal at depth `this` given the cycle heads
273
- /// of a nested goal. This merges the heads after filtering the parent goal
274
- /// itself.
275
- fn extend_from_child ( & mut self , this : StackDepth , step_kind : PathKind , child : & CycleHeads ) {
276
- for ( & head, & path_from_entry) in child. heads . iter ( ) {
277
- match head. cmp ( & this) {
278
- Ordering :: Less => { }
279
- Ordering :: Equal => continue ,
280
- Ordering :: Greater => unreachable ! ( ) ,
281
- }
282
- self . insert ( head, path_from_entry. extend_with ( step_kind) ) ;
283
- }
284
- }
285
292
}
286
293
287
294
bitflags:: bitflags! {
@@ -487,9 +494,6 @@ impl<X: Cx> EvaluationResult<X> {
487
494
488
495
pub struct SearchGraph < D : Delegate < Cx = X > , X : Cx = <D as Delegate >:: Cx > {
489
496
root_depth : AvailableDepth ,
490
- /// The stack of goals currently being computed.
491
- ///
492
- /// An element is *deeper* in the stack if its index is *lower*.
493
497
stack : Stack < X > ,
494
498
/// The provisional cache contains entries for already computed goals which
495
499
/// still depend on goals higher-up in the stack. We don't move them to the
@@ -511,6 +515,7 @@ pub struct SearchGraph<D: Delegate<Cx = X>, X: Cx = <D as Delegate>::Cx> {
511
515
/// cache entry.
512
516
enum UpdateParentGoalCtxt < ' a , X : Cx > {
513
517
Ordinary ( & ' a NestedGoals < X > ) ,
518
+ CycleOnStack ( X :: Input ) ,
514
519
ProvisionalCacheHit ,
515
520
}
516
521
@@ -532,21 +537,42 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
532
537
stack : & mut Stack < X > ,
533
538
step_kind_from_parent : PathKind ,
534
539
required_depth_for_nested : usize ,
535
- heads : & CycleHeads ,
540
+ heads : impl Iterator < Item = ( StackDepth , CycleHead ) > ,
536
541
encountered_overflow : bool ,
537
542
context : UpdateParentGoalCtxt < ' _ , X > ,
538
543
) {
539
- if let Some ( parent_index) = stack. last_index ( ) {
540
- let parent = & mut stack[ parent_index] ;
544
+ if let Some ( ( parent_index, parent) ) = stack. last_mut_with_index ( ) {
541
545
parent. required_depth = parent. required_depth . max ( required_depth_for_nested + 1 ) ;
542
546
parent. encountered_overflow |= encountered_overflow;
543
547
544
- parent. heads . extend_from_child ( parent_index, step_kind_from_parent, heads) ;
548
+ for ( head_index, head) in heads {
549
+ match head_index. cmp ( & parent_index) {
550
+ Ordering :: Less => parent. heads . insert (
551
+ head_index,
552
+ head. paths_to_head . extend_with ( step_kind_from_parent) ,
553
+ head. usage_kind ,
554
+ ) ,
555
+ Ordering :: Equal => {
556
+ let usage_kind = parent
557
+ . has_been_used
558
+ . map_or ( head. usage_kind , |prev| prev. merge ( head. usage_kind ) ) ;
559
+ parent. has_been_used = Some ( usage_kind) ;
560
+ }
561
+ Ordering :: Greater => unreachable ! ( ) ,
562
+ }
563
+ }
545
564
let parent_depends_on_cycle = match context {
546
565
UpdateParentGoalCtxt :: Ordinary ( nested_goals) => {
547
566
parent. nested_goals . extend_from_child ( step_kind_from_parent, nested_goals) ;
548
567
!nested_goals. is_empty ( )
549
568
}
569
+ UpdateParentGoalCtxt :: CycleOnStack ( head) => {
570
+ // We lookup provisional cache entries before detecting cycles.
571
+ // We therefore can't use a global cache entry if it contains a cycle
572
+ // whose head is in the provisional cache.
573
+ parent. nested_goals . insert ( head, step_kind_from_parent. into ( ) ) ;
574
+ true
575
+ }
550
576
UpdateParentGoalCtxt :: ProvisionalCacheHit => true ,
551
577
} ;
552
578
// Once we've got goals which encountered overflow or a cycle,
@@ -674,7 +700,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
674
700
& mut self . stack ,
675
701
step_kind_from_parent,
676
702
evaluation_result. required_depth ,
677
- & evaluation_result. heads ,
703
+ evaluation_result. heads . iter ( ) ,
678
704
evaluation_result. encountered_overflow ,
679
705
UpdateParentGoalCtxt :: Ordinary ( & evaluation_result. nested_goals ) ,
680
706
) ;
@@ -772,7 +798,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
772
798
stack_entry : & StackEntry < X > ,
773
799
mut mutate_result : impl FnMut ( X :: Input , X :: Result ) -> X :: Result ,
774
800
) {
775
- let popped_head = self . stack . next_index ( ) ;
801
+ let popped_head_index = self . stack . next_index ( ) ;
776
802
#[ allow( rustc:: potential_query_instability) ]
777
803
self . provisional_cache . retain ( |& input, entries| {
778
804
entries. retain_mut ( |entry| {
@@ -782,7 +808,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
782
808
path_from_head,
783
809
result,
784
810
} = entry;
785
- let ep = if heads. highest_cycle_head ( ) == popped_head {
811
+ let popped_head = if heads. highest_cycle_head ( ) == popped_head_index {
786
812
heads. remove_highest_cycle_head ( )
787
813
} else {
788
814
return true ;
@@ -795,9 +821,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
795
821
//
796
822
// After rebasing the cycles `hph` will go through `e`. We need to make
797
823
// sure that forall possible paths `hep`, `heph` is equal to `hph.`
798
- for ( h, ph) in stack_entry. heads . iter ( ) {
799
- let hp =
800
- Self :: cycle_path_kind ( & self . stack , stack_entry. step_kind_from_parent , h) ;
824
+ let ep = popped_head. paths_to_head ;
825
+ for ( head_index, head) in stack_entry. heads . iter ( ) {
826
+ let ph = head. paths_to_head ;
827
+ let hp = Self :: cycle_path_kind (
828
+ & self . stack ,
829
+ stack_entry. step_kind_from_parent ,
830
+ head_index,
831
+ ) ;
801
832
802
833
// We first validate that all cycles while computing `p` would stay
803
834
// the same if we were to recompute it as a nested goal of `e`.
@@ -817,7 +848,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
817
848
// the heads of `e` to make sure that rebasing `e` again also considers
818
849
// them.
819
850
let eph = ep. extend_with_paths ( ph) ;
820
- heads. insert ( h , eph) ;
851
+ heads. insert ( head_index , eph, head . usage_kind ) ;
821
852
}
822
853
823
854
let Some ( head) = heads. opt_highest_cycle_head ( ) else {
@@ -877,11 +908,10 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
877
908
& mut self . stack ,
878
909
step_kind_from_parent,
879
910
0 ,
880
- heads,
911
+ heads. iter ( ) ,
881
912
encountered_overflow,
882
913
UpdateParentGoalCtxt :: ProvisionalCacheHit ,
883
914
) ;
884
- debug_assert ! ( self . stack[ head] . has_been_used. is_some( ) ) ;
885
915
debug ! ( ?head, ?path_from_head, "provisional cache hit" ) ;
886
916
return Some ( result) ;
887
917
}
@@ -993,12 +1023,12 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
993
1023
994
1024
// We don't move cycle participants to the global cache, so the
995
1025
// cycle heads are always empty.
996
- let heads = Default :: default ( ) ;
1026
+ let heads = iter :: empty ( ) ;
997
1027
Self :: update_parent_goal (
998
1028
& mut self . stack ,
999
1029
step_kind_from_parent,
1000
1030
required_depth,
1001
- & heads,
1031
+ heads,
1002
1032
encountered_overflow,
1003
1033
UpdateParentGoalCtxt :: Ordinary ( nested_goals) ,
1004
1034
) ;
@@ -1014,34 +1044,31 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1014
1044
input : X :: Input ,
1015
1045
step_kind_from_parent : PathKind ,
1016
1046
) -> Option < X :: Result > {
1017
- let head = self . stack . find ( input) ?;
1047
+ let head_index = self . stack . find ( input) ?;
1018
1048
// We have a nested goal which directly relies on a goal deeper in the stack.
1019
1049
//
1020
1050
// We start by tagging all cycle participants, as that's necessary for caching.
1021
1051
//
1022
1052
// Finally we can return either the provisional response or the initial response
1023
1053
// in case we're in the first fixpoint iteration for this goal.
1024
- let path_kind = Self :: cycle_path_kind ( & self . stack , step_kind_from_parent, head) ;
1025
- debug ! ( ?path_kind, "encountered cycle with depth {head:?}" ) ;
1026
- let usage_kind = UsageKind :: Single ( path_kind) ;
1027
- self . stack [ head] . has_been_used =
1028
- Some ( self . stack [ head] . has_been_used . map_or ( usage_kind, |prev| prev. merge ( usage_kind) ) ) ;
1029
-
1030
- // Subtle: when encountering a cyclic goal, we still first checked for overflow,
1031
- // so we have to update the reached depth.
1032
- let last_index = self . stack . last_index ( ) . unwrap ( ) ;
1033
- let last = & mut self . stack [ last_index] ;
1034
- last. required_depth = last. required_depth . max ( 1 ) ;
1035
-
1036
- last. nested_goals . insert ( input, step_kind_from_parent. into ( ) ) ;
1037
- last. nested_goals . insert ( last. input , PathsToNested :: EMPTY ) ;
1038
- if last_index != head {
1039
- last. heads . insert ( head, step_kind_from_parent) ;
1040
- }
1054
+ let path_kind = Self :: cycle_path_kind ( & self . stack , step_kind_from_parent, head_index) ;
1055
+ debug ! ( ?path_kind, "encountered cycle with depth {head_index:?}" ) ;
1056
+ let head = CycleHead {
1057
+ paths_to_head : step_kind_from_parent. into ( ) ,
1058
+ usage_kind : UsageKind :: Single ( path_kind) ,
1059
+ } ;
1060
+ Self :: update_parent_goal (
1061
+ & mut self . stack ,
1062
+ step_kind_from_parent,
1063
+ 0 ,
1064
+ iter:: once ( ( head_index, head) ) ,
1065
+ false ,
1066
+ UpdateParentGoalCtxt :: CycleOnStack ( input) ,
1067
+ ) ;
1041
1068
1042
1069
// Return the provisional result or, if we're in the first iteration,
1043
1070
// start with no constraints.
1044
- if let Some ( result) = self . stack [ head ] . provisional_result {
1071
+ if let Some ( result) = self . stack [ head_index ] . provisional_result {
1045
1072
Some ( result)
1046
1073
} else {
1047
1074
Some ( D :: initial_provisional_result ( cx, path_kind, input) )
0 commit comments