@@ -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
@@ -436,6 +438,7 @@ impl<X: Cx> NestedGoals<X> {
436
438
/// goals still on the stack.
437
439
#[ derive_where( Debug ; X : Cx ) ]
438
440
struct ProvisionalCacheEntry < X : Cx > {
441
+ entry_node_id : tree:: NodeId ,
439
442
/// Whether evaluating the goal encountered overflow. This is used to
440
443
/// disable the cache entry except if the last goal on the stack is
441
444
/// already involved in this cycle.
@@ -459,6 +462,7 @@ struct ProvisionalCacheEntry<X: Cx> {
459
462
/// evaluation.
460
463
#[ derive_where( Debug ; X : Cx ) ]
461
464
struct EvaluationResult < X : Cx > {
465
+ node_id : tree:: NodeId ,
462
466
encountered_overflow : bool ,
463
467
required_depth : usize ,
464
468
heads : CycleHeads ,
@@ -479,7 +483,8 @@ impl<X: Cx> EvaluationResult<X> {
479
483
required_depth : final_entry. required_depth ,
480
484
heads : final_entry. heads ,
481
485
nested_goals : final_entry. nested_goals ,
482
- // We only care about the final result.
486
+ // We only care about the result and the `node_id` of the final iteration.
487
+ node_id : final_entry. node_id ,
483
488
result,
484
489
}
485
490
}
@@ -497,6 +502,8 @@ pub struct SearchGraph<D: Delegate<Cx = X>, X: Cx = <D as Delegate>::Cx> {
497
502
/// is only valid until the result of one of its cycle heads changes.
498
503
provisional_cache : HashMap < X :: Input , Vec < ProvisionalCacheEntry < X > > > ,
499
504
505
+ tree : SearchTree < X > ,
506
+
500
507
_marker : PhantomData < D > ,
501
508
}
502
509
@@ -520,6 +527,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
520
527
root_depth : AvailableDepth ( root_depth) ,
521
528
stack : Default :: default ( ) ,
522
529
provisional_cache : Default :: default ( ) ,
530
+ tree : Default :: default ( ) ,
523
531
_marker : PhantomData ,
524
532
}
525
533
}
@@ -605,6 +613,9 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
605
613
return self . handle_overflow ( cx, input, inspect) ;
606
614
} ;
607
615
616
+ let node_id =
617
+ self . tree . create_node ( & self . stack , input, step_kind_from_parent, available_depth) ;
618
+
608
619
// We check the provisional cache before checking the global cache. This simplifies
609
620
// the implementation as we can avoid worrying about cases where both the global and
610
621
// provisional cache may apply, e.g. consider the following example
@@ -613,7 +624,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
613
624
// - A
614
625
// - BA cycle
615
626
// - CB :x:
616
- if let Some ( result) = self . lookup_provisional_cache ( input, step_kind_from_parent) {
627
+ if let Some ( result) = self . lookup_provisional_cache ( node_id , input, step_kind_from_parent) {
617
628
return result;
618
629
}
619
630
@@ -630,7 +641,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
630
641
. inspect ( |expected| debug ! ( ?expected, "validate cache entry" ) )
631
642
. map ( |r| ( scope, r) )
632
643
} else if let Some ( result) =
633
- self . lookup_global_cache ( cx, input, step_kind_from_parent, available_depth)
644
+ self . lookup_global_cache ( cx, node_id , input, step_kind_from_parent, available_depth)
634
645
{
635
646
return result;
636
647
} else {
@@ -641,13 +652,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
641
652
// avoid iterating over the stack in case a goal has already been computed.
642
653
// This may not have an actual performance impact and we could reorder them
643
654
// as it may reduce the number of `nested_goals` we need to track.
644
- if let Some ( result) = self . check_cycle_on_stack ( cx, input, step_kind_from_parent) {
655
+ if let Some ( result) = self . check_cycle_on_stack ( cx, node_id , input, step_kind_from_parent) {
645
656
debug_assert ! ( validate_cache. is_none( ) , "global cache and cycle on stack: {input:?}" ) ;
646
657
return result;
647
658
}
648
659
649
660
// Unfortunate, it looks like we actually have to compute this goal.
650
661
self . stack . push ( StackEntry {
662
+ node_id,
651
663
input,
652
664
step_kind_from_parent,
653
665
available_depth,
@@ -694,6 +706,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
694
706
debug_assert ! ( validate_cache. is_none( ) , "unexpected non-root: {input:?}" ) ;
695
707
let entry = self . provisional_cache . entry ( input) . or_default ( ) ;
696
708
let EvaluationResult {
709
+ node_id,
697
710
encountered_overflow,
698
711
required_depth : _,
699
712
heads,
@@ -705,8 +718,13 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
705
718
step_kind_from_parent,
706
719
heads. highest_cycle_head ( ) ,
707
720
) ;
708
- let provisional_cache_entry =
709
- ProvisionalCacheEntry { encountered_overflow, heads, path_from_head, result } ;
721
+ let provisional_cache_entry = ProvisionalCacheEntry {
722
+ entry_node_id : node_id,
723
+ encountered_overflow,
724
+ heads,
725
+ path_from_head,
726
+ result,
727
+ } ;
710
728
debug ! ( ?provisional_cache_entry) ;
711
729
entry. push ( provisional_cache_entry) ;
712
730
} else {
@@ -780,6 +798,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
780
798
self . provisional_cache . retain ( |& input, entries| {
781
799
entries. retain_mut ( |entry| {
782
800
let ProvisionalCacheEntry {
801
+ entry_node_id : _,
783
802
encountered_overflow : _,
784
803
heads,
785
804
path_from_head,
@@ -831,6 +850,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
831
850
832
851
fn lookup_provisional_cache (
833
852
& mut self ,
853
+ node_id : tree:: NodeId ,
834
854
input : X :: Input ,
835
855
step_kind_from_parent : PathKind ,
836
856
) -> Option < X :: Result > {
@@ -839,8 +859,13 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
839
859
}
840
860
841
861
let entries = self . provisional_cache . get ( & input) ?;
842
- for & ProvisionalCacheEntry { encountered_overflow, ref heads, path_from_head, result } in
843
- entries
862
+ for & ProvisionalCacheEntry {
863
+ entry_node_id,
864
+ encountered_overflow,
865
+ ref heads,
866
+ path_from_head,
867
+ result,
868
+ } in entries
844
869
{
845
870
let head = heads. highest_cycle_head ( ) ;
846
871
if encountered_overflow {
@@ -872,6 +897,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
872
897
) ;
873
898
debug_assert ! ( self . stack[ head] . has_been_used. is_some( ) ) ;
874
899
debug ! ( ?head, ?path_from_head, "provisional cache hit" ) ;
900
+ self . tree . provisional_cache_hit ( node_id, entry_node_id) ;
875
901
return Some ( result) ;
876
902
}
877
903
}
@@ -912,6 +938,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
912
938
// A provisional cache entry is applicable if the path to
913
939
// its highest cycle head is equal to the expected path.
914
940
for & ProvisionalCacheEntry {
941
+ entry_node_id : _,
915
942
encountered_overflow,
916
943
ref heads,
917
944
path_from_head : head_to_provisional,
@@ -970,6 +997,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
970
997
fn lookup_global_cache (
971
998
& mut self ,
972
999
cx : X ,
1000
+ node_id : tree:: NodeId ,
973
1001
input : X :: Input ,
974
1002
step_kind_from_parent : PathKind ,
975
1003
available_depth : AvailableDepth ,
@@ -993,13 +1021,15 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
993
1021
) ;
994
1022
995
1023
debug ! ( ?required_depth, "global cache hit" ) ;
1024
+ self . tree . global_cache_hit ( node_id) ;
996
1025
Some ( result)
997
1026
} )
998
1027
}
999
1028
1000
1029
fn check_cycle_on_stack (
1001
1030
& mut self ,
1002
1031
cx : X ,
1032
+ node_id : tree:: NodeId ,
1003
1033
input : X :: Input ,
1004
1034
step_kind_from_parent : PathKind ,
1005
1035
) -> Option < X :: Result > {
@@ -1030,11 +1060,11 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1030
1060
1031
1061
// Return the provisional result or, if we're in the first iteration,
1032
1062
// start with no constraints.
1033
- if let Some ( result) = self . stack [ head] . provisional_result {
1034
- Some ( result )
1035
- } else {
1036
- Some ( D :: initial_provisional_result ( cx , path_kind , input ) )
1037
- }
1063
+ let result = self . stack [ head]
1064
+ . provisional_result
1065
+ . unwrap_or_else ( || D :: initial_provisional_result ( cx , path_kind , input ) ) ;
1066
+ self . tree . cycle_on_stack ( node_id , self . stack [ head ] . node_id , result ) ;
1067
+ Some ( result )
1038
1068
}
1039
1069
1040
1070
/// Whether we've reached a fixpoint when evaluating a cycle head.
@@ -1077,6 +1107,15 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1077
1107
let stack_entry = self . stack . pop ( ) ;
1078
1108
encountered_overflow |= stack_entry. encountered_overflow ;
1079
1109
debug_assert_eq ! ( stack_entry. input, input) ;
1110
+ // FIXME: Cloning the cycle heads here is quite ass. We should make cycle heads
1111
+ // CoW and use reference counting.
1112
+ self . tree . finish_evaluate (
1113
+ stack_entry. node_id ,
1114
+ stack_entry. provisional_result ,
1115
+ stack_entry. encountered_overflow ,
1116
+ stack_entry. heads . clone ( ) ,
1117
+ result,
1118
+ ) ;
1080
1119
1081
1120
// If the current goal is not the root of a cycle, we are done.
1082
1121
//
@@ -1137,7 +1176,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
1137
1176
self . clear_dependent_provisional_results ( ) ;
1138
1177
1139
1178
debug ! ( ?result, "fixpoint changed provisional results" ) ;
1179
+ let node_id = self . tree . create_node (
1180
+ & self . stack ,
1181
+ stack_entry. input ,
1182
+ stack_entry. step_kind_from_parent ,
1183
+ stack_entry. available_depth ,
1184
+ ) ;
1140
1185
self . stack . push ( StackEntry {
1186
+ node_id,
1141
1187
input,
1142
1188
step_kind_from_parent : stack_entry. step_kind_from_parent ,
1143
1189
available_depth : stack_entry. available_depth ,
0 commit comments