Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
  • Loading branch information
rzvxa committed Jun 20, 2024
commit 3e78f9852fce9b5de7cef2d6c67fb8fb6b85f395
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
}