use std::collections::{BTreeSet, VecDeque};
use std::fmt;
use crate::transformer::{InstrRef, LowInstr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CfgGraph {
pub cfg: Cfg,
pub children: Vec<CfgGraph>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Cfg {
pub blocks: Vec<BasicBlock>,
pub edges: Vec<CfgEdge>,
pub entry_block: BlockRef,
pub exit_block: BlockRef,
pub block_order: Vec<BlockRef>,
pub instr_to_block: Vec<BlockRef>,
pub preds: Vec<Vec<EdgeRef>>,
pub succs: Vec<Vec<EdgeRef>>,
pub reachable_blocks: BTreeSet<BlockRef>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct EdgeRef(pub usize);
impl EdgeRef {
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for EdgeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct BlockRef(pub usize);
impl BlockRef {
pub const fn index(self) -> usize {
self.0
}
}
impl fmt::Display for BlockRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct BasicBlock {
pub kind: BlockKind,
pub instrs: InstrRange,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub enum BlockKind {
#[default]
Normal,
SyntheticExit,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct InstrRange {
pub start: InstrRef,
pub len: usize,
}
impl InstrRange {
pub const fn new(start: InstrRef, len: usize) -> Self {
Self { start, len }
}
pub const fn end(self) -> usize {
self.start.index() + self.len
}
pub const fn is_empty(self) -> bool {
self.len == 0
}
pub const fn last(self) -> Option<InstrRef> {
if self.len == 0 {
None
} else {
Some(InstrRef(self.start.index() + self.len - 1))
}
}
}
impl Default for InstrRange {
fn default() -> Self {
Self::new(InstrRef(0), 0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct CfgEdge {
pub from: BlockRef,
pub to: BlockRef,
pub kind: EdgeKind,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum EdgeKind {
Fallthrough,
Jump,
BranchTrue,
BranchFalse,
LoopBody,
LoopExit,
Return,
TailCall,
}
impl Cfg {
pub fn terminator<'a>(&self, instrs: &'a [LowInstr], block: BlockRef) -> Option<&'a LowInstr> {
self.blocks
.get(block.index())
.and_then(|basic_block| basic_block.instrs.last())
.and_then(|instr| instrs.get(instr.index()))
}
pub fn branch_edges(&self, block: BlockRef) -> Option<(EdgeRef, EdgeRef)> {
let succs = &self.succs[block.index()];
if succs.len() != 2 {
return None;
}
let then_edge = succs
.iter()
.find(|edge_ref| matches!(self.edges[edge_ref.index()].kind, EdgeKind::BranchTrue))?;
let else_edge = succs
.iter()
.find(|edge_ref| matches!(self.edges[edge_ref.index()].kind, EdgeKind::BranchFalse))?;
Some((*then_edge, *else_edge))
}
pub fn reachable_successors(&self, block: BlockRef) -> Vec<BlockRef> {
let mut succs = self.succs[block.index()]
.iter()
.map(|edge_ref| self.edges[edge_ref.index()].to)
.filter(|succ| self.reachable_blocks.contains(succ))
.collect::<Vec<_>>();
succs.sort();
succs.dedup();
succs
}
pub fn reachable_predecessors(&self, block: BlockRef) -> Vec<BlockRef> {
let mut preds = self.preds[block.index()]
.iter()
.map(|edge_ref| self.edges[edge_ref.index()].from)
.filter(|pred| self.reachable_blocks.contains(pred))
.collect::<Vec<_>>();
preds.sort();
preds.dedup();
preds
}
pub fn unique_reachable_successor(&self, block: BlockRef) -> Option<BlockRef> {
let mut successors = self.succs[block.index()]
.iter()
.map(|edge_ref| self.edges[edge_ref.index()].to)
.filter(|succ| self.reachable_blocks.contains(succ));
let succ = successors.next()?;
if successors.next().is_none() {
Some(succ)
} else {
None
}
}
pub fn can_reach(&self, from: BlockRef, to: BlockRef) -> bool {
self.can_reach_within(from, to, &self.reachable_blocks)
}
pub fn can_reach_within(
&self,
from: BlockRef,
to: BlockRef,
allowed_blocks: &BTreeSet<BlockRef>,
) -> bool {
if from == to {
return true;
}
let mut visited = BTreeSet::new();
let mut worklist = VecDeque::from([from]);
while let Some(block) = worklist.pop_front() {
if !self.reachable_blocks.contains(&block)
|| !allowed_blocks.contains(&block)
|| !visited.insert(block)
{
continue;
}
for edge_ref in &self.succs[block.index()] {
let succ = self.edges[edge_ref.index()].to;
if succ == to {
return true;
}
if allowed_blocks.contains(&succ) {
worklist.push_back(succ);
}
}
}
false
}
}