Skip to content

Commit 3e78f98

Browse files
committed
feat(cfg): add depth first search with hash sets. (#3771)
petgraph allocates a chunk of memory for all of the graph for its dfs which is really costly for small subgraph sreachs. This PR adds a new `set_depth_first_search` function which uses a `FxHashSet` instead.
1 parent 6ba60e9 commit 3e78f98

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-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

+102-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,99 @@ 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+
/// Similar to `depth_first_search` but uses a `HashSet` underneath. Ideal for small subgraphs.
90+
pub fn set_depth_first_search<G, I, F, C, N>(graph: G, starts: I, mut visitor: F) -> C
91+
where
92+
N: Copy + PartialEq + Eq + Hash,
93+
G: IntoNeighbors + Visitable<NodeId = N>,
94+
I: IntoIterator<Item = G::NodeId>,
95+
F: FnMut(DfsEvent<G::NodeId>) -> C,
96+
C: ControlFlow,
97+
{
98+
let time = &mut Time(0);
99+
let discovered = &mut FxHashSet::<G::NodeId>::default();
100+
let finished = &mut FxHashSet::<G::NodeId>::default();
101+
102+
for start in starts {
103+
try_control!(
104+
dfs_visitor(graph, start, &mut visitor, discovered, finished, time),
105+
unreachable!()
106+
);
107+
}
108+
C::continuing()
109+
}
110+
111+
fn dfs_visitor<G, M, F, C>(
112+
graph: G,
113+
u: G::NodeId,
114+
visitor: &mut F,
115+
discovered: &mut M,
116+
finished: &mut M,
117+
time: &mut Time,
118+
) -> C
119+
where
120+
G: IntoNeighbors + Visitable,
121+
M: VisitMap<G::NodeId>,
122+
F: FnMut(DfsEvent<G::NodeId>) -> C,
123+
C: ControlFlow,
124+
{
125+
if !discovered.visit(u) {
126+
return C::continuing();
127+
}
128+
129+
try_control!(
130+
visitor(DfsEvent::Discover(u, time_post_inc(time))),
131+
{},
132+
for v in graph.neighbors(u) {
133+
if !discovered.is_visited(&v) {
134+
try_control!(visitor(DfsEvent::TreeEdge(u, v)), continue);
135+
try_control!(
136+
dfs_visitor(graph, v, visitor, discovered, finished, time),
137+
unreachable!()
138+
);
139+
} else if !finished.is_visited(&v) {
140+
try_control!(visitor(DfsEvent::BackEdge(u, v)), continue);
141+
} else {
142+
try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)), continue);
143+
}
144+
}
145+
);
146+
let first_finish = finished.visit(u);
147+
debug_assert!(first_finish);
148+
try_control!(
149+
visitor(DfsEvent::Finish(u, time_post_inc(time))),
150+
panic!("Pruning on the `DfsEvent::Finish` is not supported!")
151+
);
152+
C::continuing()
153+
}
154+
155+
fn time_post_inc(x: &mut Time) -> Time {
156+
let v = *x;
157+
x.0 += 1;
158+
v
159+
}

0 commit comments

Comments
 (0)