Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit af5a406

Browse files
author
Hendrik van Antwerpen
committed
Implement similar path detection based on equals
1 parent 7f3d7f6 commit af5a406

File tree

5 files changed

+179
-3
lines changed

5 files changed

+179
-3
lines changed

stack-graphs/src/cycles.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
//! any time.
3131
3232
use enumset::EnumSet;
33+
use smallvec::SmallVec;
34+
use std::collections::HashMap;
3335

3436
use crate::arena::Handle;
3537
use crate::arena::List;
@@ -40,10 +42,110 @@ use crate::graph::StackGraph;
4042
use crate::partial::Cyclicity;
4143
use crate::partial::PartialPath;
4244
use crate::partial::PartialPaths;
45+
use crate::paths::Path;
4346
use crate::paths::PathResolutionError;
47+
use crate::paths::Paths;
4448
use crate::stitching::Database;
4549
use crate::stitching::OwnedOrDatabasePath;
4650

51+
/// Helps detect similar paths in the path-finding algorithm.
52+
pub struct SimilarPathDetector<P> {
53+
paths: HashMap<PathKey, SmallVec<[P; 8]>>,
54+
}
55+
56+
#[doc(hidden)]
57+
#[derive(Clone, Eq, Hash, PartialEq)]
58+
pub struct PathKey {
59+
start_node: Handle<Node>,
60+
end_node: Handle<Node>,
61+
symbol_stack_precondition_len: usize,
62+
scope_stack_precondition_len: usize,
63+
symbol_stack_postcondition_len: usize,
64+
scope_stack_postcondition_len: usize,
65+
}
66+
67+
#[doc(hidden)]
68+
pub trait HasPathKey: Clone {
69+
type Arena;
70+
fn key(&self) -> PathKey;
71+
}
72+
73+
impl HasPathKey for Path {
74+
type Arena = Paths;
75+
76+
fn key(&self) -> PathKey {
77+
PathKey {
78+
start_node: self.start_node,
79+
end_node: self.end_node,
80+
symbol_stack_precondition_len: 0,
81+
scope_stack_precondition_len: 0,
82+
symbol_stack_postcondition_len: self.symbol_stack.len(),
83+
scope_stack_postcondition_len: self.scope_stack.len(),
84+
}
85+
}
86+
}
87+
88+
impl HasPathKey for PartialPath {
89+
type Arena = PartialPaths;
90+
91+
fn key(&self) -> PathKey {
92+
PathKey {
93+
start_node: self.start_node,
94+
end_node: self.end_node,
95+
symbol_stack_precondition_len: self.symbol_stack_precondition.len(),
96+
scope_stack_precondition_len: self.scope_stack_precondition.len(),
97+
symbol_stack_postcondition_len: self.symbol_stack_postcondition.len(),
98+
scope_stack_postcondition_len: self.scope_stack_postcondition.len(),
99+
}
100+
}
101+
}
102+
103+
impl<P> SimilarPathDetector<P>
104+
where
105+
P: HasPathKey,
106+
{
107+
/// Creates a new, empty cycle detector.
108+
pub fn new() -> SimilarPathDetector<P> {
109+
SimilarPathDetector {
110+
paths: HashMap::new(),
111+
}
112+
}
113+
114+
/// Determines whether we should process this path during the path-finding algorithm. If we have seen
115+
/// a path with the same start and end node, and the same pre- and postcondition, then we return false.
116+
/// Otherwise, we return true.
117+
pub fn has_similar_path<Eq>(
118+
&mut self,
119+
graph: &StackGraph,
120+
arena: &mut P::Arena,
121+
path: &P,
122+
eq: Eq,
123+
) -> bool
124+
where
125+
Eq: Fn(&mut P::Arena, &P, &P) -> bool,
126+
{
127+
let key = path.key();
128+
129+
if graph.incoming_edge_count(key.end_node) <= 1 {
130+
// return false;
131+
}
132+
133+
let possibly_similar_paths = self.paths.entry(key).or_default();
134+
for other_path in possibly_similar_paths.iter() {
135+
if eq(arena, path, other_path) {
136+
return true;
137+
}
138+
}
139+
140+
possibly_similar_paths.push(path.clone());
141+
false
142+
}
143+
144+
pub fn max_bucket_size(&self) -> usize {
145+
self.paths.iter().map(|b| b.1.len()).max().unwrap_or(0)
146+
}
147+
}
148+
47149
// ----------------------------------------------------------------------------
48150
// Cycle detector
49151

stack-graphs/src/graph.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,7 @@ impl StackGraph {
13011301
let edges = &mut self.outgoing_edges[source];
13021302
if let Err(index) = edges.binary_search_by_key(&sink, |o| o.sink) {
13031303
edges.insert(index, OutgoingEdge { sink, precedence });
1304+
self.incoming_edges[sink] += 1;
13041305
}
13051306
}
13061307

@@ -1323,6 +1324,14 @@ impl StackGraph {
13231324
None => Either::Left(std::iter::empty()),
13241325
}
13251326
}
1327+
1328+
/// Returns an iterator of all of the edges that begin at a particular source node.
1329+
pub fn incoming_edge_count(&self, sink: Handle<Node>) -> usize {
1330+
match self.incoming_edges.get(sink) {
1331+
Some(count) => *count,
1332+
None => 0,
1333+
}
1334+
}
13261335
}
13271336

13281337
//-------------------------------------------------------------------------------------------------
@@ -1410,6 +1419,7 @@ pub struct StackGraph {
14101419
pub(crate) nodes: Arena<Node>,
14111420
pub(crate) source_info: SupplementalArena<Node, SourceInfo>,
14121421
node_id_handles: NodeIDHandles,
1422+
incoming_edges: SupplementalArena<Node, usize>,
14131423
outgoing_edges: SupplementalArena<Node, SmallVec<[OutgoingEdge; 8]>>,
14141424
pub(crate) debug_info: SupplementalArena<Node, DebugInfo>,
14151425
}
@@ -1588,6 +1598,7 @@ impl Default for StackGraph {
15881598
nodes,
15891599
source_info: SupplementalArena::new(),
15901600
node_id_handles: NodeIDHandles::new(),
1601+
incoming_edges: SupplementalArena::new(),
15911602
outgoing_edges: SupplementalArena::new(),
15921603
debug_info: SupplementalArena::new(),
15931604
}

stack-graphs/src/partial.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use crate::arena::DequeArena;
4848
use crate::arena::Handle;
4949
use crate::cycles::Appendables;
5050
use crate::cycles::AppendingCycleDetector;
51+
use crate::cycles::SimilarPathDetector;
5152
use crate::graph::Edge;
5253
use crate::graph::File;
5354
use crate::graph::Node;
@@ -2464,6 +2465,7 @@ impl PartialPath {
24642465
file: Handle<File>,
24652466
edges: &mut Appendables<Edge>,
24662467
path_cycle_detector: AppendingCycleDetector<Edge>,
2468+
similar_path_detector: &mut SimilarPathDetector<PartialPath>,
24672469
result: &mut R,
24682470
) {
24692471
let extensions = graph.outgoing_edges(self.end_node);
@@ -2479,13 +2481,21 @@ impl PartialPath {
24792481
continue;
24802482
}
24812483
let mut new_path = self.clone();
2482-
let mut new_cycle_detector = path_cycle_detector.clone();
24832484
// If there are errors adding this edge to the partial path, or resolving the resulting
24842485
// partial path, just skip the edge — it's not a fatal error.
24852486
if new_path.append(graph, partials, extension).is_err() {
24862487
copious_debugging!(" * invalid extension");
24872488
continue;
24882489
}
2490+
if similar_path_detector.has_similar_path(
2491+
graph,
2492+
partials,
2493+
&new_path,
2494+
|ps, left, right| left.equals(ps, right),
2495+
) {
2496+
copious_debugging!(" * too many similar");
2497+
}
2498+
let mut new_cycle_detector = path_cycle_detector.clone();
24892499
new_cycle_detector.append(edges, extension);
24902500
result.push((new_path, new_cycle_detector));
24912501
}
@@ -2792,6 +2802,7 @@ impl PartialPaths {
27922802
}
27932803

27942804
copious_debugging!("Find all partial paths in {}", graph[file]);
2805+
let mut similar_path_detector = SimilarPathDetector::new();
27952806
let mut queue = VecDeque::new();
27962807
let mut edges = Appendables::new();
27972808
queue.extend(
@@ -2827,10 +2838,15 @@ impl PartialPaths {
28272838
file,
28282839
&mut edges,
28292840
path_cycle_detector,
2841+
&mut similar_path_detector,
28302842
&mut queue,
28312843
);
28322844
}
28332845
}
2846+
copious_debugging!(
2847+
" Max similar path bucket size: {}",
2848+
similar_path_detector.max_bucket_size()
2849+
);
28342850
Ok(())
28352851
}
28362852
}

stack-graphs/src/paths.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::arena::List;
2828
use crate::arena::ListArena;
2929
use crate::cycles::Appendables;
3030
use crate::cycles::AppendingCycleDetector;
31+
use crate::cycles::SimilarPathDetector;
3132
use crate::graph::Edge;
3233
use crate::graph::Node;
3334
use crate::graph::NodeID;
@@ -878,18 +879,24 @@ impl Path {
878879
paths: &mut Paths,
879880
edges: &mut Appendables<Edge>,
880881
path_cycle_detector: AppendingCycleDetector<Edge>,
882+
similar_path_detector: &mut SimilarPathDetector<Path>,
881883
result: &mut R,
882884
) {
883885
let extensions = graph.outgoing_edges(self.end_node);
884886
result.reserve(extensions.size_hint().0);
885887
for extension in extensions {
886888
let mut new_path = self.clone();
887-
let mut new_cycle_detector = path_cycle_detector.clone();
888889
// If there are errors adding this edge to the path, or resolving the resulting path,
889890
// just skip the edge — it's not a fatal error.
890891
if new_path.append(graph, paths, extension).is_err() {
891892
continue;
892893
}
894+
if similar_path_detector.has_similar_path(graph, paths, &new_path, |ps, left, right| {
895+
left.equals(ps, right)
896+
}) {
897+
continue;
898+
}
899+
let mut new_cycle_detector = path_cycle_detector.clone();
893900
new_cycle_detector.append(edges, extension);
894901
result.push((new_path, new_cycle_detector));
895902
}
@@ -917,6 +924,7 @@ impl Paths {
917924
I: IntoIterator<Item = Handle<Node>>,
918925
F: FnMut(&StackGraph, &mut Paths, Path),
919926
{
927+
let mut similar_path_detector = SimilarPathDetector::new();
920928
let mut queue = starting_nodes
921929
.into_iter()
922930
.filter_map(|node| {
@@ -935,7 +943,14 @@ impl Paths {
935943
.all(|c| c == Cyclicity::StrengthensPrecondition)
936944
{
937945
} else {
938-
path.extend(graph, self, &mut edges, path_cycle_detector, &mut queue);
946+
path.extend(
947+
graph,
948+
self,
949+
&mut edges,
950+
path_cycle_detector,
951+
&mut similar_path_detector,
952+
&mut queue,
953+
);
939954
}
940955
}
941956
Ok(())

stack-graphs/src/stitching.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use crate::arena::ListCell;
5050
use crate::arena::SupplementalArena;
5151
use crate::cycles::Appendables;
5252
use crate::cycles::AppendingCycleDetector;
53+
use crate::cycles::SimilarPathDetector;
5354
use crate::graph::Node;
5455
use crate::graph::StackGraph;
5556
use crate::graph::Symbol;
@@ -484,6 +485,7 @@ pub struct PathStitcher {
484485
VecDeque<AppendingCycleDetector<OwnedOrDatabasePath>>,
485486
),
486487
appended_paths: Appendables<OwnedOrDatabasePath>,
488+
similar_path_detector: SimilarPathDetector<Path>,
487489
max_work_per_phase: usize,
488490
#[cfg(feature = "copious-debugging")]
489491
phase_number: usize,
@@ -535,6 +537,7 @@ impl PathStitcher {
535537
queue: VecDeque::new(),
536538
next_iteration,
537539
appended_paths,
540+
similar_path_detector: SimilarPathDetector::new(),
538541
// By default, there's no artificial bound on the amount of work done per phase
539542
max_work_per_phase: usize::MAX,
540543
#[cfg(feature = "copious-debugging")]
@@ -631,6 +634,15 @@ impl PathStitcher {
631634
copious_debugging!(" is invalid: cyclic");
632635
continue;
633636
}
637+
if self.similar_path_detector.has_similar_path(
638+
graph,
639+
paths,
640+
&path,
641+
|ps, left, right| left.equals(ps, right),
642+
) {
643+
copious_debugging!(" is invalid: too many similar");
644+
continue;
645+
}
634646
copious_debugging!(" is {}", new_path.display(graph, paths));
635647
self.next_iteration.0.push_back(new_path);
636648
self.next_iteration.1.push_back(new_cycle_detector);
@@ -677,6 +689,10 @@ impl PathStitcher {
677689

678690
#[cfg(feature = "copious-debugging")]
679691
{
692+
copious_debugging!(
693+
" Max similar path bucket size: {}",
694+
self.similar_path_detector.max_bucket_size()
695+
);
680696
copious_debugging!("==> End phase {}", self.phase_number);
681697
self.phase_number += 1;
682698
}
@@ -756,6 +772,7 @@ pub struct ForwardPartialPathStitcher {
756772
VecDeque<AppendingCycleDetector<OwnedOrDatabasePath>>,
757773
),
758774
appended_paths: Appendables<OwnedOrDatabasePath>,
775+
similar_path_detector: SimilarPathDetector<PartialPath>,
759776
max_work_per_phase: usize,
760777
#[cfg(feature = "copious-debugging")]
761778
phase_number: usize,
@@ -820,6 +837,7 @@ impl ForwardPartialPathStitcher {
820837
queue: VecDeque::new(),
821838
next_iteration,
822839
appended_paths,
840+
similar_path_detector: SimilarPathDetector::new(),
823841
// By default, there's no artificial bound on the amount of work done per phase
824842
max_work_per_phase: usize::MAX,
825843
#[cfg(feature = "copious-debugging")]
@@ -856,6 +874,7 @@ impl ForwardPartialPathStitcher {
856874
queue: VecDeque::new(),
857875
next_iteration,
858876
appended_paths,
877+
similar_path_detector: SimilarPathDetector::new(),
859878
// By default, there's no artificial bound on the amount of work done per phase
860879
max_work_per_phase: usize::MAX,
861880
#[cfg(feature = "copious-debugging")]
@@ -958,6 +977,15 @@ impl ForwardPartialPathStitcher {
958977
copious_debugging!(" is invalid: cyclic");
959978
continue;
960979
}
980+
if self.similar_path_detector.has_similar_path(
981+
graph,
982+
partials,
983+
&partial_path,
984+
|ps, left, right| left.equals(ps, right),
985+
) {
986+
copious_debugging!(" is invalid: too many similar");
987+
continue;
988+
}
961989
}
962990
copious_debugging!(" is {}", new_partial_path.display(graph, partials));
963991
self.next_iteration.0.push_back(new_partial_path);
@@ -1009,6 +1037,10 @@ impl ForwardPartialPathStitcher {
10091037

10101038
#[cfg(feature = "copious-debugging")]
10111039
{
1040+
copious_debugging!(
1041+
" Max similar path bucket size: {}",
1042+
self.similar_path_detector.max_bucket_size()
1043+
);
10121044
copious_debugging!("==> End phase {}", self.phase_number);
10131045
self.phase_number += 1;
10141046
}

0 commit comments

Comments
 (0)