use llvm_ir::{Function, Name, Terminator};
use petgraph::prelude::{DiGraphMap, Direction};
use std::fmt;
pub struct ControlFlowGraph<'m> {
pub(crate) graph: DiGraphMap<CFGNode<'m>, ()>,
pub(crate) entry_node: CFGNode<'m>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum CFGNode<'m> {
Block(&'m Name),
Return,
}
impl<'m> fmt::Display for CFGNode<'m> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CFGNode::Block(block) => write!(f, "{}", block),
CFGNode::Return => write!(f, "Return"),
}
}
}
impl<'m> ControlFlowGraph<'m> {
pub(crate) fn new(function: &'m Function) -> Self {
let mut graph: DiGraphMap<CFGNode<'m>, ()> = DiGraphMap::with_capacity(
function.basic_blocks.len() + 1,
2 * function.basic_blocks.len(), );
for bb in &function.basic_blocks {
match &bb.term {
Terminator::Br(br) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&br.dest), ());
},
Terminator::CondBr(condbr) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&condbr.true_dest), ());
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&condbr.false_dest), ());
},
Terminator::IndirectBr(ibr) => {
for dest in &ibr.possible_dests {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(dest), ());
}
},
Terminator::Switch(switch) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&switch.default_dest), ());
for (_, dest) in &switch.dests {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(dest), ());
}
},
Terminator::Ret(_) | Terminator::Resume(_) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Return, ());
}
Terminator::Invoke(invoke) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&invoke.return_label), ());
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&invoke.exception_label), ());
},
Terminator::CleanupRet(cleanupret) => {
if let Some(dest) = &cleanupret.unwind_dest {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(dest), ());
} else {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Return, ());
}
},
Terminator::CatchRet(catchret) => {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(&catchret.successor), ());
},
Terminator::CatchSwitch(catchswitch) => {
if let Some(dest) = &catchswitch.default_unwind_dest {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(dest), ());
} else {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Return, ());
}
for handler in &catchswitch.catch_handlers {
graph.add_edge(CFGNode::Block(&bb.name), CFGNode::Block(handler), ());
}
},
#[cfg(not(feature = "llvm-8"))]
Terminator::CallBr(_) => unimplemented!("CallBr instruction"),
Terminator::Unreachable(_) => {
},
}
}
Self {
graph,
entry_node: CFGNode::Block(&function.basic_blocks[0].name),
}
}
pub fn preds<'s>(&'s self, block: &'m Name) -> impl Iterator<Item = &'m Name> + 's {
self.preds_of_cfgnode(CFGNode::Block(block))
}
pub fn preds_of_return<'s>(&'s self) -> impl Iterator<Item = &'m Name> + 's {
self.preds_of_cfgnode(CFGNode::Return)
}
pub(crate) fn preds_of_cfgnode<'s>(&'s self, node: CFGNode<'m>) -> impl Iterator<Item = &'m Name> + 's {
self.preds_as_nodes(node).map(|cfgnode| match cfgnode {
CFGNode::Block(block) => block,
CFGNode::Return => panic!("Shouldn't have CFGNode::Return as a predecessor"), })
}
pub(crate) fn preds_as_nodes<'s>(&'s self, node: CFGNode<'m>) -> impl Iterator<Item = CFGNode<'m>> + 's {
self.graph.neighbors_directed(node, Direction::Incoming)
}
pub fn succs<'s>(&'s self, block: &'m Name) -> impl Iterator<Item = CFGNode<'m>> + 's {
self.graph.neighbors_directed(CFGNode::Block(block), Direction::Outgoing)
}
pub fn entry(&self) -> &'m Name {
match self.entry_node {
CFGNode::Block(block) => block,
CFGNode::Return => panic!("Return node should not be entry"), }
}
pub(crate) fn reversed(&self) -> Self {
Self {
graph: DiGraphMap::from_edges(
self.graph.all_edges().map(|(a, b, _)| (b, a, ()))
),
entry_node: CFGNode::Return,
}
}
}