Skip to content

Commit a49cd4c

Browse files
committed
feat(cfg): add depth fist search with hash sets.
1 parent a658ca8 commit a49cd4c

File tree

2 files changed

+155
-4
lines changed

2 files changed

+155
-4
lines changed

crates/oxc_cfg/src/lib.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use itertools::Itertools;
66
use oxc_syntax::node::AstNodeId;
77
use petgraph::{
88
stable_graph::NodeIndex,
9-
visit::{depth_first_search, Control, DfsEvent, EdgeRef},
9+
visit::{Control, DfsEvent, EdgeRef},
1010
Direction, Graph,
1111
};
1212

@@ -21,6 +21,7 @@ pub mod graph {
2121

2222
pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
2323
pub use dot::DisplayDot;
24+
use visit::set_depth_first_search;
2425

2526
pub type BasicBlockId = NodeIndex;
2627

@@ -155,7 +156,7 @@ impl ControlFlowGraph {
155156
return true;
156157
}
157158
let graph = &self.graph;
158-
depth_first_search(&self.graph, Some(from), |event| match event {
159+
set_depth_first_search(&self.graph, Some(from), |event| match event {
159160
DfsEvent::TreeEdge(a, b) => {
160161
let filter_result = filter(a);
161162
if !matches!(filter_result, Control::Continue) {
@@ -246,7 +247,7 @@ impl ControlFlowGraph {
246247
}
247248

248249
pub fn is_cyclic(&self, node: BasicBlockId) -> bool {
249-
depth_first_search(&self.graph, Some(node), |event| match event {
250+
set_depth_first_search(&self.graph, Some(node), |event| match event {
250251
DfsEvent::BackEdge(_, id) if id == node => Err(()),
251252
_ => Ok(()),
252253
})

crates/oxc_cfg/src/visit.rs

+151-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use petgraph::{visit::EdgeRef, Direction, Graph};
1+
use std::hash::Hash;
2+
3+
use petgraph::{
4+
visit::{ControlFlow, DfsEvent, EdgeRef, IntoNeighbors, Time, VisitMap, Visitable},
5+
Direction, Graph,
6+
};
27
use rustc_hash::FxHashSet;
38

49
use crate::BasicBlockId;
@@ -56,3 +61,148 @@ where
5661

5762
final_states
5863
}
64+
65+
/// Copied from petgraph's `dfsvisit`.
66+
/// Return if the expression is a break value, execute the provided statement
67+
/// if it is a prune value.
68+
macro_rules! try_control {
69+
($e:expr, $p:stmt) => {
70+
try_control!($e, $p, ());
71+
};
72+
($e:expr, $p:stmt, $q:stmt) => {
73+
match $e {
74+
x =>
75+
{
76+
#[allow(clippy::redundant_else)]
77+
if x.should_break() {
78+
return x;
79+
} else if x.should_prune() {
80+
$p
81+
} else {
82+
$q
83+
}
84+
}
85+
}
86+
};
87+
}
88+
89+
pub enum SDfsEvent<N> {
90+
Discover(N),
91+
TreeEdge(N, N),
92+
BackEdge(N, N),
93+
Finish(N),
94+
}
95+
96+
/// Similar to `depth_first_search` but uses a `HashSet` underneath. Ideal for small subgraphs.
97+
pub fn set_depth_first_search<G, I, F, C, N>(graph: G, starts: I, mut visitor: F) -> C
98+
where
99+
N: Copy + PartialEq + Eq + Hash,
100+
G: IntoNeighbors + Visitable<NodeId = N>,
101+
I: IntoIterator<Item = G::NodeId>,
102+
F: FnMut(DfsEvent<G::NodeId>) -> C,
103+
C: ControlFlow,
104+
{
105+
// let discovered = &mut vec![];
106+
// let finished = &mut FxHashSet::default();
107+
let time = &mut Time(0);
108+
let discovered = &mut FxHashSet::<G::NodeId>::default();
109+
let finished = &mut FxHashSet::<G::NodeId>::default();
110+
111+
for start in starts {
112+
try_control!(
113+
sdfs_visitor(graph, start, &mut visitor, discovered, finished, time),
114+
unreachable!()
115+
);
116+
}
117+
C::continuing()
118+
}
119+
120+
fn sdfs_visitor<G, M, F, C>(
121+
graph: G,
122+
u: G::NodeId,
123+
visitor: &mut F,
124+
discovered: &mut M,
125+
finished: &mut M,
126+
time: &mut Time,
127+
) -> C
128+
where
129+
G: IntoNeighbors + Visitable,
130+
M: VisitMap<G::NodeId>,
131+
F: FnMut(DfsEvent<G::NodeId>) -> C,
132+
C: ControlFlow,
133+
{
134+
if !discovered.visit(u) {
135+
return C::continuing();
136+
}
137+
138+
try_control!(
139+
visitor(DfsEvent::Discover(u, time_post_inc(time))),
140+
{},
141+
for v in graph.neighbors(u) {
142+
if !discovered.is_visited(&v) {
143+
try_control!(visitor(DfsEvent::TreeEdge(u, v)), continue);
144+
try_control!(
145+
sdfs_visitor(graph, v, visitor, discovered, finished, time),
146+
unreachable!()
147+
);
148+
} else if !finished.is_visited(&v) {
149+
try_control!(visitor(DfsEvent::BackEdge(u, v)), continue);
150+
} else {
151+
try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)), continue);
152+
}
153+
}
154+
);
155+
let first_finish = finished.visit(u);
156+
debug_assert!(first_finish);
157+
try_control!(
158+
visitor(DfsEvent::Finish(u, time_post_inc(time))),
159+
panic!("Pruning on the `DfsEvent::Finish` is not supported!")
160+
);
161+
C::continuing()
162+
}
163+
164+
fn time_post_inc(x: &mut Time) -> Time {
165+
let v = *x;
166+
x.0 += 1;
167+
v
168+
}
169+
170+
// fn sdfs_visitor<G, F, C>(
171+
// graph: G,
172+
// u: G::NodeId,
173+
// visitor: &mut F,
174+
// discovered: &mut Vec<G::NodeId>,
175+
// finished: &mut FxHashSet<G::NodeId>,
176+
// ) -> C
177+
// where
178+
// G: IntoNeighbors + Visitable,
179+
// F: FnMut(SDfsEvent<G::NodeId>) -> C,
180+
// C: ControlFlow,
181+
// {
182+
// if !discovered.visit(u) {
183+
// return C::continuing();
184+
// }
185+
//
186+
// try_control!(
187+
// visitor(SDfsEvent::Discover(u)),
188+
// {},
189+
// for v in graph.neighbors(u) {
190+
// if !discovered.is_visited(&v) {
191+
// try_control!(visitor(SDfsEvent::TreeEdge(u, v)), continue);
192+
// try_control!(sdfs_visitor(graph, v, visitor, discovered, finished), unreachable!());
193+
// } else if !finished.contains(&v) {
194+
// try_control!(visitor(SDfsEvent::BackEdge(u, v)), continue);
195+
// }
196+
// // } else {
197+
// // try_control!(visitor(SDfsEvent::CrossForwardEdge(u, v)), continue);
198+
// // }
199+
// }
200+
// );
201+
// let first_finish = finished.visit(u);
202+
// debug_assert!(first_finish);
203+
// try_control!(
204+
// visitor(DfsEvent::Finish(u, time_post_inc(time))),
205+
// panic!("Pruning on the `DfsEvent::Finish` is not supported!")
206+
// );
207+
// C::continuing()
208+
// }

0 commit comments

Comments
 (0)