mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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.
This commit is contained in:
parent
6ba60e978a
commit
3e78f9852f
2 changed files with 106 additions and 4 deletions
|
|
@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||||
use oxc_syntax::node::AstNodeId;
|
use oxc_syntax::node::AstNodeId;
|
||||||
use petgraph::{
|
use petgraph::{
|
||||||
stable_graph::NodeIndex,
|
stable_graph::NodeIndex,
|
||||||
visit::{depth_first_search, Control, DfsEvent, EdgeRef},
|
visit::{Control, DfsEvent, EdgeRef},
|
||||||
Direction, Graph,
|
Direction, Graph,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub mod graph {
|
||||||
|
|
||||||
pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
|
pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
|
||||||
pub use dot::DisplayDot;
|
pub use dot::DisplayDot;
|
||||||
|
use visit::set_depth_first_search;
|
||||||
|
|
||||||
pub type BasicBlockId = NodeIndex;
|
pub type BasicBlockId = NodeIndex;
|
||||||
|
|
||||||
|
|
@ -155,7 +156,7 @@ impl ControlFlowGraph {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let graph = &self.graph;
|
let graph = &self.graph;
|
||||||
depth_first_search(&self.graph, Some(from), |event| match event {
|
set_depth_first_search(&self.graph, Some(from), |event| match event {
|
||||||
DfsEvent::TreeEdge(a, b) => {
|
DfsEvent::TreeEdge(a, b) => {
|
||||||
let filter_result = filter(a);
|
let filter_result = filter(a);
|
||||||
if !matches!(filter_result, Control::Continue) {
|
if !matches!(filter_result, Control::Continue) {
|
||||||
|
|
@ -246,7 +247,7 @@ impl ControlFlowGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_cyclic(&self, node: BasicBlockId) -> bool {
|
pub fn is_cyclic(&self, node: BasicBlockId) -> bool {
|
||||||
depth_first_search(&self.graph, Some(node), |event| match event {
|
set_depth_first_search(&self.graph, Some(node), |event| match event {
|
||||||
DfsEvent::BackEdge(_, id) if id == node => Err(()),
|
DfsEvent::BackEdge(_, id) if id == node => Err(()),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
use petgraph::{visit::EdgeRef, Direction, Graph};
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use petgraph::{
|
||||||
|
visit::{ControlFlow, DfsEvent, EdgeRef, IntoNeighbors, Time, VisitMap, Visitable},
|
||||||
|
Direction, Graph,
|
||||||
|
};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::BasicBlockId;
|
use crate::BasicBlockId;
|
||||||
|
|
@ -56,3 +61,99 @@ where
|
||||||
|
|
||||||
final_states
|
final_states
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copied from petgraph's `dfsvisit`.
|
||||||
|
/// Return if the expression is a break value, execute the provided statement
|
||||||
|
/// if it is a prune value.
|
||||||
|
macro_rules! try_control {
|
||||||
|
($e:expr, $p:stmt) => {
|
||||||
|
try_control!($e, $p, ());
|
||||||
|
};
|
||||||
|
($e:expr, $p:stmt, $q:stmt) => {
|
||||||
|
match $e {
|
||||||
|
x =>
|
||||||
|
{
|
||||||
|
#[allow(clippy::redundant_else)]
|
||||||
|
if x.should_break() {
|
||||||
|
return x;
|
||||||
|
} else if x.should_prune() {
|
||||||
|
$p
|
||||||
|
} else {
|
||||||
|
$q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to `depth_first_search` but uses a `HashSet` underneath. Ideal for small subgraphs.
|
||||||
|
pub fn set_depth_first_search<G, I, F, C, N>(graph: G, starts: I, mut visitor: F) -> C
|
||||||
|
where
|
||||||
|
N: Copy + PartialEq + Eq + Hash,
|
||||||
|
G: IntoNeighbors + Visitable<NodeId = N>,
|
||||||
|
I: IntoIterator<Item = G::NodeId>,
|
||||||
|
F: FnMut(DfsEvent<G::NodeId>) -> C,
|
||||||
|
C: ControlFlow,
|
||||||
|
{
|
||||||
|
let time = &mut Time(0);
|
||||||
|
let discovered = &mut FxHashSet::<G::NodeId>::default();
|
||||||
|
let finished = &mut FxHashSet::<G::NodeId>::default();
|
||||||
|
|
||||||
|
for start in starts {
|
||||||
|
try_control!(
|
||||||
|
dfs_visitor(graph, start, &mut visitor, discovered, finished, time),
|
||||||
|
unreachable!()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
C::continuing()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs_visitor<G, M, F, C>(
|
||||||
|
graph: G,
|
||||||
|
u: G::NodeId,
|
||||||
|
visitor: &mut F,
|
||||||
|
discovered: &mut M,
|
||||||
|
finished: &mut M,
|
||||||
|
time: &mut Time,
|
||||||
|
) -> C
|
||||||
|
where
|
||||||
|
G: IntoNeighbors + Visitable,
|
||||||
|
M: VisitMap<G::NodeId>,
|
||||||
|
F: FnMut(DfsEvent<G::NodeId>) -> C,
|
||||||
|
C: ControlFlow,
|
||||||
|
{
|
||||||
|
if !discovered.visit(u) {
|
||||||
|
return C::continuing();
|
||||||
|
}
|
||||||
|
|
||||||
|
try_control!(
|
||||||
|
visitor(DfsEvent::Discover(u, time_post_inc(time))),
|
||||||
|
{},
|
||||||
|
for v in graph.neighbors(u) {
|
||||||
|
if !discovered.is_visited(&v) {
|
||||||
|
try_control!(visitor(DfsEvent::TreeEdge(u, v)), continue);
|
||||||
|
try_control!(
|
||||||
|
dfs_visitor(graph, v, visitor, discovered, finished, time),
|
||||||
|
unreachable!()
|
||||||
|
);
|
||||||
|
} else if !finished.is_visited(&v) {
|
||||||
|
try_control!(visitor(DfsEvent::BackEdge(u, v)), continue);
|
||||||
|
} else {
|
||||||
|
try_control!(visitor(DfsEvent::CrossForwardEdge(u, v)), continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let first_finish = finished.visit(u);
|
||||||
|
debug_assert!(first_finish);
|
||||||
|
try_control!(
|
||||||
|
visitor(DfsEvent::Finish(u, time_post_inc(time))),
|
||||||
|
panic!("Pruning on the `DfsEvent::Finish` is not supported!")
|
||||||
|
);
|
||||||
|
C::continuing()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_post_inc(x: &mut Time) -> Time {
|
||||||
|
let v = *x;
|
||||||
|
x.0 += 1;
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue