@@ -28,8 +28,10 @@ use crate::data_structures::HashMap;
28
28
mod stack;
29
29
use stack:: { Stack , StackDepth , StackEntry } ;
30
30
mod global_cache;
31
+ mod tree;
31
32
use global_cache:: CacheData ;
32
33
pub use global_cache:: GlobalCache ;
34
+ use tree:: SearchTree ;
33
35
34
36
/// The search graph does not simply use `Interner` directly
35
37
/// to enable its fuzzing without having to stub the rest of
@@ -443,6 +445,7 @@ impl<X: Cx> NestedGoals<X> {
443
445
/// goals still on the stack.
444
446
#[ derive_where( Debug ; X : Cx ) ]
445
447
struct ProvisionalCacheEntry < X : Cx > {
448
+ entry_node_id : tree:: NodeId ,
446
449
/// Whether evaluating the goal encountered overflow. This is used to
447
450
/// disable the cache entry except if the last goal on the stack is
448
451
/// already involved in this cycle.
@@ -466,6 +469,7 @@ struct ProvisionalCacheEntry<X: Cx> {
466
469
/// evaluation.
467
470
#[ derive_where( Debug ; X : Cx ) ]
468
471
struct EvaluationResult < X : Cx > {
472
+ node_id : tree:: NodeId ,
469
473
encountered_overflow : bool ,
470
474
required_depth : usize ,
471
475
heads : CycleHeads ,
@@ -486,7 +490,8 @@ impl<X: Cx> EvaluationResult<X> {
486
490
required_depth : final_entry. required_depth ,
487
491
heads : final_entry. heads ,
488
492
nested_goals : final_entry. nested_goals ,
489
- // We only care about the final result.
493
+ // We only care about the result and the `node_id` of the final iteration.
494
+ node_id : final_entry. node_id ,
490
495
result,
491
496
}
492
497
}
@@ -504,6 +509,8 @@ pub struct SearchGraph<D: Delegate<Cx = X>, X: Cx = <D as Delegate>::Cx> {
504
509
/// is only valid until the result of one of its cycle heads changes.
505
510
provisional_cache : HashMap < X :: Input , Vec < ProvisionalCacheEntry < X > > > ,
506
511
512
+ tree : SearchTree < X > ,
513
+
507
514
_marker : PhantomData < D > ,
508
515
}
509
516
@@ -527,6 +534,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
527
534
root_depth : AvailableDepth ( root_depth) ,
528
535
stack : Default :: default ( ) ,
529
536
provisional_cache : Default :: default ( ) ,
537
+ tree : Default :: default ( ) ,
530
538
_marker : PhantomData ,
531
539
}
532
540
}
@@ -612,6 +620,9 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
612
620
return self . handle_overflow ( cx, input, inspect) ;
613
621
} ;
614
622
623
+ let node_id =
624
+ self . tree . create_node ( & self . stack , input, step_kind_from_parent, available_depth) ;
625
+
615
626
// We check the provisional cache before checking the global cache. This simplifies
616
627
// the implementation as we can avoid worrying about cases where both the global and
617
628
// provisional cache may apply, e.g. consider the following example
@@ -620,7 +631,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
620
631
// - A
621
632
// - BA cycle
622
633
// - CB :x:
623
- if let Some ( result) = self . lookup_provisional_cache ( input, step_kind_from_parent) {
634
+ if let Some ( result) = self . lookup_provisional_cache ( node_id , input, step_kind_from_parent) {
624
635
return result;
625
636
}
626
637
@@ -637,7 +648,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
637
648
. inspect ( |expected| debug ! ( ?expected, "validate cache entry" ) )
638
649
. map ( |r| ( scope, r) )
639
650
} else if let Some ( result) =
640
- self . lookup_global_cache ( cx, input, step_kind_from_parent, available_depth)
651
+ self . lookup_global_cache ( cx, node_id , input, step_kind_from_parent, available_depth)
641
652
{
642
653
return result;
643
654
} else {
@@ -648,13 +659,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
648
659
// avoid iterating over the stack in case a goal has already been computed.
649
660
// This may not have an actual performance impact and we could reorder them
650
661
// as it may reduce the number of `nested_goals` we need to track.
651
- if let Some ( result) = self . check_cycle_on_stack ( cx, input, step_kind_from_parent) {
662
+ if let Some ( result) = self . check_cycle_on_stack ( cx, node_id , input, step_kind_from_parent) {
652
663
debug_assert ! ( validate_cache. is_none( ) , "global cache and cycle on stack: {input:?}" ) ;
653
664
return result;
654
665
}
655
666
656
667
// Unfortunate, it looks like we actually have to compute this goal.
657
668
self . stack . push ( StackEntry {
669
+ node_id,
658
670
input,
659
671
step_kind_from_parent,
660
672
available_depth,
@@ -701,6 +713,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
701
713
debug_assert ! ( validate_cache. is_none( ) , "unexpected non-root: {input:?}" ) ;
702
714
let entry = self . provisional_cache . entry ( input) . or_default ( ) ;
703
715
let EvaluationResult {
716
+ node_id,
704
717
encountered_overflow,
705
718
required_depth : _,
706
719
heads,
@@ -712,8 +725,13 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
712
725
step_kind_from_parent,
713
726
heads. highest_cycle_head ( ) ,
714
727
) ;
715
- let provisional_cache_entry =
716
- ProvisionalCacheEntry { encountered_overflow, heads, path_from_head, result } ;
728
+ let provisional_cache_entry = ProvisionalCacheEntry {
729
+ entry_node_id : node_id,
730
+ encountered_overflow,
731
+ heads,
732
+ path_from_head,
733
+ result,
734
+ } ;
717
735
debug ! ( ?provisional_cache_entry) ;
718
736
entry. push ( provisional_cache_entry) ;
719
737
} else {
@@ -787,6 +805,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
787
805
self . provisional_cache . retain ( |& input, entries| {
788
806
entries. retain_mut ( |entry| {
789
807
let ProvisionalCacheEntry {
808
+ entry_node_id : _,
790
809
encountered_overflow : _,
791
810
heads,
792
811
path_from_head,
@@ -838,6 +857,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
838
857
839
858
fn lookup_provisional_cache (
840
859
& mut self ,
860
+ node_id : tree:: NodeId ,
841
861
input : X :: Input ,
842
862
step_kind_from_parent : PathKind ,
843
863
) -> Option < X :: Result > {
@@ -846,8 +866,13 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
846
866
}
847
867
848
868
let entries = self . provisional_cache . get ( & input) ?;
849
- for & ProvisionalCacheEntry { encountered_overflow, ref heads, path_from_head, result } in
850
- entries
869
+ for & ProvisionalCacheEntry {
870
+ entry_node_id,
871
+ encountered_overflow,
872
+ ref heads,
873
+ path_from_head,
874
+ result,
875
+ } in entries
851
876
{
852
877
let head = heads. highest_cycle_head ( ) ;
853
878
if encountered_overflow {
@@ -879,6 +904,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
879
904
) ;
880
905
debug_assert ! ( self . stack[ head] . has_been_used. is_some( ) ) ;
881
906
debug ! ( ?head, ?path_from_head, "provisional cache hit" ) ;
907
+ self . tree . provisional_cache_hit ( node_id, entry_node_id) ;
882
908
return Some ( result) ;
883
909
}
884
910
}
@@ -919,6 +945,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
919
945
// A provisional cache entry is applicable if the path to
920
946
// its highest cycle head is equal to the expected path.
921
947
for & ProvisionalCacheEntry {
948
+ entry_node_id : _,
922
949
encountered_overflow,
923
950
ref heads,
924
951
path_from_head : head_to_provisional,
@@ -977,6 +1004,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
977
1004
fn lookup_global_cache (
978
1005
& mut self ,
979
1006
cx : X ,
1007
+ node_id : tree:: NodeId ,
980
1008
input : X :: Input ,
981
1009
step_kind_from_parent : PathKind ,
982
1010
available_depth : AvailableDepth ,
@@ -1000,13 +1028,15 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1000
1028
) ;
1001
1029
1002
1030
debug ! ( ?required_depth, "global cache hit" ) ;
1031
+ self . tree . global_cache_hit ( node_id) ;
1003
1032
Some ( result)
1004
1033
} )
1005
1034
}
1006
1035
1007
1036
fn check_cycle_on_stack (
1008
1037
& mut self ,
1009
1038
cx : X ,
1039
+ node_id : tree:: NodeId ,
1010
1040
input : X :: Input ,
1011
1041
step_kind_from_parent : PathKind ,
1012
1042
) -> Option < X :: Result > {
@@ -1037,11 +1067,11 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1037
1067
1038
1068
// Return the provisional result or, if we're in the first iteration,
1039
1069
// start with no constraints.
1040
- if let Some ( result) = self . stack [ head] . provisional_result {
1041
- Some ( result )
1042
- } else {
1043
- Some ( D :: initial_provisional_result ( cx , path_kind , input ) )
1044
- }
1070
+ let result = self . stack [ head]
1071
+ . provisional_result
1072
+ . unwrap_or_else ( || D :: initial_provisional_result ( cx , path_kind , input ) ) ;
1073
+ self . tree . cycle_on_stack ( node_id , self . stack [ head ] . node_id , result ) ;
1074
+ Some ( result )
1045
1075
}
1046
1076
1047
1077
/// Whether we've reached a fixpoint when evaluating a cycle head.
@@ -1083,6 +1113,15 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1083
1113
let stack_entry = self . stack . pop ( ) ;
1084
1114
encountered_overflow |= stack_entry. encountered_overflow ;
1085
1115
debug_assert_eq ! ( stack_entry. input, input) ;
1116
+ // FIXME: Cloning the cycle heads here is quite ass. We should make cycle heads
1117
+ // CoW and use reference counting.
1118
+ self . tree . finish_evaluate (
1119
+ stack_entry. node_id ,
1120
+ stack_entry. provisional_result ,
1121
+ stack_entry. encountered_overflow ,
1122
+ stack_entry. heads . clone ( ) ,
1123
+ result,
1124
+ ) ;
1086
1125
1087
1126
// If the current goal is not the root of a cycle, we are done.
1088
1127
//
@@ -1143,7 +1182,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1143
1182
self . clear_dependent_provisional_results ( ) ;
1144
1183
1145
1184
debug ! ( ?result, "fixpoint changed provisional results" ) ;
1185
+ let node_id = self . tree . create_node (
1186
+ & self . stack ,
1187
+ stack_entry. input ,
1188
+ stack_entry. step_kind_from_parent ,
1189
+ stack_entry. available_depth ,
1190
+ ) ;
1146
1191
self . stack . push ( StackEntry {
1192
+ node_id,
1147
1193
input,
1148
1194
step_kind_from_parent : stack_entry. step_kind_from_parent ,
1149
1195
available_depth : stack_entry. available_depth ,
0 commit comments