use alloc::{
boxed::Box,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::{
graph::{ControlFlowGraph, HasLabel, traversals},
utils::table::{HMap, ISet},
};
fn find_pre_sentinels<G, GraphContext>(ctx: &GraphContext, graph: &G) -> Vec<G::Node>
where
G: ControlFlowGraph<GraphContext>,
{
let sccs = traversals::region::sccs_in_topological_order(ctx, graph);
let scc_of: HMap<G::Node, usize> = sccs
.iter()
.enumerate()
.flat_map(|(i, scc)| scc.nodes.iter().map(move |node| (node.clone(), i)))
.collect();
let is_sink_scc: Vec<bool> = sccs
.iter()
.enumerate()
.map(|(i, scc)| {
scc.nodes.iter().all(|node| {
graph
.successors(ctx, node)
.iter()
.all(|succ| scc_of[succ] == i)
})
})
.collect();
let mut selected: Vec<Option<G::Node>> = vec![None; sccs.len()];
for node in graph.nodes(ctx) {
let scc = scc_of[&node];
if is_sink_scc[scc] {
selected[scc] = Some(node);
}
}
selected.into_iter().flatten().collect()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ReverseNode<N> {
Sentinel,
Real(N),
}
impl<N> ReverseNode<N> {
pub fn is_sentinel(&self) -> bool {
matches!(self, ReverseNode::Sentinel)
}
pub fn as_real(&self) -> Option<&N> {
match self {
ReverseNode::Sentinel => None,
ReverseNode::Real(node) => Some(node),
}
}
pub fn into_real(self) -> Option<N> {
match self {
ReverseNode::Sentinel => None,
ReverseNode::Real(node) => Some(node),
}
}
}
impl<N: HasLabel<GraphContext>, GraphContext> HasLabel<GraphContext> for ReverseNode<N> {
fn label(&self, ctx: &GraphContext) -> String {
match self {
ReverseNode::Sentinel => "sentinel".to_string(),
ReverseNode::Real(n) => n.label(ctx),
}
}
}
pub struct ReverseGraph<'a, G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
graph: &'a G,
pre_sentinels: ISet<G::Node>,
predecessors: HMap<G::Node, Vec<G::Node>>,
}
impl<'a, G, GraphContext> ReverseGraph<'a, G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
pub fn new(ctx: &GraphContext, graph: &'a G) -> Self {
let pre_sentinels = find_pre_sentinels(ctx, graph).into_iter().collect();
let predecessors = graph
.nodes(ctx)
.map(|node| {
let preds = graph.predecessors(ctx, &node);
(node, preds)
})
.collect();
ReverseGraph {
graph,
pre_sentinels,
predecessors,
}
}
pub fn original(&self) -> &'a G {
self.graph
}
pub fn pre_sentinels(&self) -> impl Iterator<Item = G::Node> + Clone + '_ {
self.pre_sentinels.iter().cloned()
}
}
impl<G, GraphContext> ControlFlowGraph<GraphContext> for ReverseGraph<'_, G, GraphContext>
where
G: ControlFlowGraph<GraphContext>,
{
type Node = ReverseNode<G::Node>;
fn num_successors(&self, _ctx: &GraphContext, node: &Self::Node) -> usize {
match node {
ReverseNode::Sentinel => self.pre_sentinels.len(),
ReverseNode::Real(n) => self.predecessors[n].len(),
}
}
fn get_successor(&self, _ctx: &GraphContext, node: &Self::Node, i: usize) -> Self::Node {
match node {
ReverseNode::Sentinel => ReverseNode::Real(
self.pre_sentinels
.get_index(i)
.expect("Pre-sentinel index out of bounds")
.clone(),
),
ReverseNode::Real(n) => ReverseNode::Real(self.predecessors[n][i].clone()),
}
}
fn num_predecessors(&self, ctx: &GraphContext, node: &Self::Node) -> usize {
match node {
ReverseNode::Sentinel => 0,
ReverseNode::Real(n) => {
self.graph.num_successors(ctx, n) + usize::from(self.pre_sentinels.contains(n))
}
}
}
fn get_predecessor(&self, ctx: &GraphContext, node: &Self::Node, i: usize) -> Self::Node {
let ReverseNode::Real(n) = node else {
panic!("The sentinel has no predecessors");
};
if i < self.graph.num_successors(ctx, n) {
ReverseNode::Real(self.graph.get_successor(ctx, n, i))
} else {
assert!(self.pre_sentinels.contains(n) && i == self.graph.num_successors(ctx, n));
ReverseNode::Sentinel
}
}
fn entry_node(&self, _ctx: &GraphContext) -> Option<Self::Node> {
Some(ReverseNode::Sentinel)
}
fn nodes<'a>(&'a self, ctx: &'a GraphContext) -> Box<dyn Iterator<Item = Self::Node> + 'a> {
Box::new(
core::iter::once(ReverseNode::Sentinel)
.chain(self.graph.nodes(ctx).map(ReverseNode::Real)),
)
}
}
#[cfg(test)]
mod tests {
use alloc::{
boxed::Box,
string::{String, ToString},
vec,
vec::Vec,
};
use expect_test::expect;
use super::*;
use crate::{graph::print_cfg, printable::State};
struct ArenaGraph;
impl HasLabel<Vec<Vec<usize>>> for usize {
fn label(&self, _ctx: &Vec<Vec<usize>>) -> String {
self.to_string()
}
}
impl ControlFlowGraph<Vec<Vec<usize>>> for ArenaGraph {
type Node = usize;
fn num_successors(&self, ctx: &Vec<Vec<usize>>, node: &Self::Node) -> usize {
ctx[*node].len()
}
fn get_successor(&self, ctx: &Vec<Vec<usize>>, node: &Self::Node, i: usize) -> Self::Node {
ctx[*node][i]
}
fn num_predecessors(&self, ctx: &Vec<Vec<usize>>, node: &Self::Node) -> usize {
ctx.iter().filter(|succs| succs.contains(node)).count()
}
fn get_predecessor(
&self,
ctx: &Vec<Vec<usize>>,
node: &Self::Node,
i: usize,
) -> Self::Node {
ctx.iter()
.enumerate()
.filter_map(|(idx, succs)| succs.contains(node).then_some(idx))
.nth(i)
.expect("Predecessor index out of bounds")
}
fn entry_node(&self, ctx: &Vec<Vec<usize>>) -> Option<Self::Node> {
if ctx.is_empty() { None } else { Some(0) }
}
fn nodes<'a>(
&'a self,
ctx: &'a Vec<Vec<usize>>,
) -> Box<dyn Iterator<Item = Self::Node> + 'a> {
Box::new(0..ctx.len())
}
}
fn print(ctx: &Vec<Vec<usize>>, graph: &impl ControlFlowGraph<Vec<Vec<usize>>>) -> String {
let mut output = String::new();
print_cfg(ctx, graph, &State::default(), &mut output).unwrap();
output
}
#[test]
fn reverse_graph_empty() {
let ctx: Vec<Vec<usize>> = vec![];
let reverse = ReverseGraph::new(&ctx, &ArenaGraph);
expect![[r#"
digraph cfg {
n0 [label="sentinel"];
}"#]]
.assert_eq(&print(&ctx, &reverse));
}
#[test]
fn reverse_graph_exit_and_infinite_loop() {
let ctx = vec![
vec![1, 2],
vec![3],
vec![4],
vec![],
vec![2],
vec![3],
];
expect![[r#"
digraph cfg {
n0 [label="0"];
n1 [label="1"];
n2 [label="2"];
n3 [label="3"];
n4 [label="4"];
n5 [label="5"];
n0 -> n1;
n0 -> n2;
n1 -> n3;
n2 -> n4;
n4 -> n2;
n5 -> n3;
}"#]]
.assert_eq(&print(&ctx, &ArenaGraph));
let reverse = ReverseGraph::new(&ctx, &ArenaGraph);
expect![[r#"
digraph cfg {
n0 [label="sentinel"];
n1 [label="0"];
n2 [label="1"];
n3 [label="2"];
n4 [label="3"];
n5 [label="4"];
n6 [label="5"];
n0 -> n5;
n0 -> n4;
n2 -> n1;
n3 -> n1;
n3 -> n5;
n4 -> n2;
n4 -> n6;
n5 -> n3;
}"#]]
.assert_eq(&print(&ctx, &reverse));
}
}