Skip to content

Commit

Permalink
feat(cfg): add depth first search with hash sets. (#3771)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rzvxa committed Jun 20, 2024
1 parent 6ba60e9 commit 3e78f98
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 4 deletions.
7 changes: 4 additions & 3 deletions crates/oxc_cfg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use itertools::Itertools;
use oxc_syntax::node::AstNodeId;
use petgraph::{
stable_graph::NodeIndex,
visit::{depth_first_search, Control, DfsEvent, EdgeRef},
visit::{Control, DfsEvent, EdgeRef},
Direction, Graph,
};

Expand All @@ -21,6 +21,7 @@ pub mod graph {

pub use builder::{ControlFlowGraphBuilder, CtxCursor, CtxFlags};
pub use dot::DisplayDot;
use visit::set_depth_first_search;

pub type BasicBlockId = NodeIndex;

Expand Down Expand Up @@ -155,7 +156,7 @@ impl ControlFlowGraph {
return true;
}
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) => {
let filter_result = filter(a);
if !matches!(filter_result, Control::Continue) {
Expand Down Expand Up @@ -246,7 +247,7 @@ impl ControlFlowGraph {
}

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(()),
_ => Ok(()),
})
Expand Down
103 changes: 102 additions & 1 deletion crates/oxc_cfg/src/visit.rs
Original file line number Diff line number Diff line change
@@ -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 crate::BasicBlockId;
Expand Down Expand Up @@ -56,3 +61,99 @@ where

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
}

0 comments on commit 3e78f98

Please sign in to comment.