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

Commit 71bf812

Browse files
author
Hendrik van Antwerpen
authored
Merge pull request #193 from github/new-cycle-detection
Per-path cycle detection
2 parents 2a75ad4 + d594cd2 commit 71bf812

File tree

11 files changed

+945
-211
lines changed

11 files changed

+945
-211
lines changed

stack-graphs/include/stack-graphs.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,9 +1072,9 @@ struct sg_forward_partial_path_stitcher *sg_forward_partial_path_stitcher_from_n
10721072
// must ensure that `db` contains all possible extensions of any of those initial partial paths.
10731073
// You can retrieve a list of those extensions via the `previous_phase_partial_paths` and
10741074
// `previous_phase_partial_paths_length` fields.
1075-
struct sg_forward_partial_path_stitcher *sg_forward_partial_path_stitcher_from_partial_paths(const struct sg_stack_graph *_graph,
1075+
struct sg_forward_partial_path_stitcher *sg_forward_partial_path_stitcher_from_partial_paths(const struct sg_stack_graph *graph,
10761076
struct sg_partial_path_arena *partials,
1077-
struct sg_partial_path_database *_db,
1077+
struct sg_partial_path_database *db,
10781078
size_t count,
10791079
const struct sg_partial_path *initial_partial_paths);
10801080

stack-graphs/src/c.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,16 +1989,23 @@ pub extern "C" fn sg_forward_partial_path_stitcher_from_nodes(
19891989
/// `previous_phase_partial_paths_length` fields.
19901990
#[no_mangle]
19911991
pub extern "C" fn sg_forward_partial_path_stitcher_from_partial_paths(
1992-
_graph: *const sg_stack_graph,
1992+
graph: *const sg_stack_graph,
19931993
partials: *mut sg_partial_path_arena,
1994-
_db: *mut sg_partial_path_database,
1994+
db: *mut sg_partial_path_database,
19951995
count: usize,
19961996
initial_partial_paths: *const sg_partial_path,
19971997
) -> *mut sg_forward_partial_path_stitcher {
1998+
let graph = unsafe { &(*graph).inner };
19981999
let partials = unsafe { &mut (*partials).inner };
2000+
let db = unsafe { &mut (*db).inner };
19992001
let initial_partial_paths =
20002002
unsafe { std::slice::from_raw_parts(initial_partial_paths as *const PartialPath, count) };
2001-
let stitcher = ForwardPartialPathStitcher::from_partial_paths(initial_partial_paths.to_vec());
2003+
let stitcher = ForwardPartialPathStitcher::from_partial_paths(
2004+
graph,
2005+
partials,
2006+
db,
2007+
initial_partial_paths.to_vec(),
2008+
);
20022009
Box::into_raw(Box::new(InternalForwardPartialPathStitcher::new(
20032010
stitcher, partials,
20042011
))) as *mut _

stack-graphs/src/cycles.rs

Lines changed: 154 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,20 @@ use std::collections::HashMap;
3434
use smallvec::SmallVec;
3535

3636
use crate::arena::Handle;
37+
use crate::arena::List;
38+
use crate::arena::ListArena;
39+
use crate::graph::Edge;
3740
use crate::graph::Node;
41+
use crate::graph::StackGraph;
3842
use crate::partial::PartialPath;
43+
use crate::partial::PartialPaths;
3944
use crate::paths::Path;
45+
use crate::paths::PathResolutionError;
46+
use crate::stitching::Database;
47+
use crate::stitching::OwnedOrDatabasePath;
4048

41-
/// Helps detect cycles in the path-finding algorithm.
42-
pub struct CycleDetector<P> {
49+
/// Helps detect similar paths in the path-finding algorithm.
50+
pub struct SimilarPathDetector<P> {
4351
paths: HashMap<PathKey, SmallVec<[P; 8]>>,
4452
}
4553

@@ -86,13 +94,13 @@ impl HasPathKey for PartialPath {
8694

8795
const MAX_SIMILAR_PATH_COUNT: usize = 7;
8896

89-
impl<P> CycleDetector<P>
97+
impl<P> SimilarPathDetector<P>
9098
where
9199
P: HasPathKey,
92100
{
93101
/// Creates a new, empty cycle detector.
94-
pub fn new() -> CycleDetector<P> {
95-
CycleDetector {
102+
pub fn new() -> SimilarPathDetector<P> {
103+
SimilarPathDetector {
96104
paths: HashMap::new(),
97105
}
98106
}
@@ -127,3 +135,144 @@ where
127135
true
128136
}
129137
}
138+
139+
// ----------------------------------------------------------------------------
140+
// Cycle detector
141+
142+
pub trait Appendable {
143+
type Ctx;
144+
145+
fn append_to(
146+
&self,
147+
graph: &StackGraph,
148+
partials: &mut PartialPaths,
149+
ctx: &mut Self::Ctx,
150+
path: &mut PartialPath,
151+
) -> Result<(), PathResolutionError>;
152+
fn start_node(&self, ctx: &mut Self::Ctx) -> Handle<Node>;
153+
fn end_node(&self, ctx: &mut Self::Ctx) -> Handle<Node>;
154+
}
155+
156+
#[derive(Clone)]
157+
pub struct AppendingCycleDetector<A> {
158+
appendages: List<A>,
159+
}
160+
161+
pub type Appendables<A> = ListArena<A>;
162+
163+
impl<A: Appendable + Clone> AppendingCycleDetector<A> {
164+
pub fn new() -> Self {
165+
Self {
166+
appendages: List::empty(),
167+
}
168+
}
169+
170+
pub fn from(appendables: &mut Appendables<A>, appendage: A) -> Self {
171+
let mut result = Self::new();
172+
result.appendages.push_front(appendables, appendage);
173+
result
174+
}
175+
176+
pub fn append(
177+
&mut self,
178+
graph: &StackGraph,
179+
partials: &mut PartialPaths,
180+
ctx: &mut A::Ctx,
181+
appendables: &mut Appendables<A>,
182+
appendage: A,
183+
) -> Result<(), PathResolutionError> {
184+
let end_node = appendage.end_node(ctx);
185+
self.appendages.push_front(appendables, appendage);
186+
187+
let mut maybe_cyclic_path = None;
188+
let mut appendages = self.appendages;
189+
loop {
190+
// get prefix elements
191+
let mut prefix_appendages = List::empty();
192+
loop {
193+
let appendable = appendages.pop_front(appendables).cloned();
194+
match appendable {
195+
Some(appendage) => {
196+
let is_cycle = appendage.start_node(ctx) == end_node;
197+
prefix_appendages.push_front(appendables, appendage);
198+
if is_cycle {
199+
break;
200+
}
201+
}
202+
None => return Ok(()),
203+
}
204+
}
205+
206+
// build prefix path -- prefix starts at end_node, because this is a cycle
207+
let mut prefix_path = PartialPath::from_node(graph, partials, end_node);
208+
while let Some(appendage) = prefix_appendages.pop_front(appendables) {
209+
prefix_path
210+
.resolve_to(graph, partials, appendage.start_node(ctx))
211+
.expect("resolving cycle prefix path failed");
212+
appendage
213+
.append_to(graph, partials, ctx, &mut prefix_path)
214+
.expect("appending cycle prefix path failed");
215+
}
216+
217+
// build cyclic path
218+
let cyclic_path = maybe_cyclic_path
219+
.unwrap_or_else(|| PartialPath::from_node(graph, partials, end_node));
220+
prefix_path
221+
.resolve_to(graph, partials, cyclic_path.start_node)
222+
.expect("resolving cyclic path failed");
223+
prefix_path.ensure_no_overlapping_variables(partials, &cyclic_path);
224+
prefix_path
225+
.concatenate(graph, partials, &cyclic_path)
226+
.expect("concatenating cyclic path failed ");
227+
if !prefix_path.is_productive(graph, partials) {
228+
return Err(PathResolutionError::DisallowedCycle);
229+
}
230+
maybe_cyclic_path = Some(prefix_path);
231+
}
232+
}
233+
}
234+
235+
impl Appendable for Edge {
236+
type Ctx = ();
237+
238+
fn append_to(
239+
&self,
240+
graph: &StackGraph,
241+
partials: &mut PartialPaths,
242+
_: &mut (),
243+
path: &mut PartialPath,
244+
) -> Result<(), PathResolutionError> {
245+
path.append(graph, partials, *self)
246+
}
247+
248+
fn start_node(&self, _: &mut ()) -> Handle<Node> {
249+
self.source
250+
}
251+
252+
fn end_node(&self, _: &mut ()) -> Handle<Node> {
253+
self.sink
254+
}
255+
}
256+
257+
impl Appendable for OwnedOrDatabasePath {
258+
type Ctx = Database;
259+
260+
fn append_to(
261+
&self,
262+
graph: &StackGraph,
263+
partials: &mut PartialPaths,
264+
db: &mut Database,
265+
path: &mut PartialPath,
266+
) -> Result<(), PathResolutionError> {
267+
path.ensure_no_overlapping_variables(partials, self.get(db));
268+
path.concatenate(graph, partials, self.get(db))
269+
}
270+
271+
fn start_node(&self, db: &mut Database) -> Handle<Node> {
272+
self.get(db).start_node
273+
}
274+
275+
fn end_node(&self, db: &mut Database) -> Handle<Node> {
276+
self.get(db).end_node
277+
}
278+
}

stack-graphs/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
//! importantly, each “chunk” of the overall graph only depends on “local” information from the
5656
//! original source file. (a.k.a., it’s incremental!)
5757
58+
use std::time::{Duration, Instant};
59+
5860
use thiserror::Error;
5961

6062
pub mod arena;
@@ -85,6 +87,29 @@ impl CancellationFlag for NoCancellation {
8587
}
8688
}
8789

90+
pub struct CancelAfterDuration {
91+
limit: Duration,
92+
start: Instant,
93+
}
94+
95+
impl CancelAfterDuration {
96+
pub fn new(limit: Duration) -> Self {
97+
Self {
98+
limit,
99+
start: Instant::now(),
100+
}
101+
}
102+
}
103+
104+
impl CancellationFlag for CancelAfterDuration {
105+
fn check(&self, at: &'static str) -> Result<(), CancellationError> {
106+
if self.start.elapsed() > self.limit {
107+
return Err(CancellationError(at));
108+
}
109+
Ok(())
110+
}
111+
}
112+
88113
#[derive(Clone, Debug, Error)]
89114
#[error("Cancelled at \"{0}\"")]
90115
pub struct CancellationError(pub &'static str);

0 commit comments

Comments
 (0)