use rucc_ir::{Block, Def, Func, Value};
use crate::cfg::Cfg;
use crate::dom::Dominators;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LoopId(u32);
impl LoopId {
#[must_use]
pub fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Exit {
pub from: Block,
pub to: Block,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct LoopData {
header: Block,
blocks: Vec<Block>,
latches: Vec<Block>,
exits: Vec<Exit>,
parent: Option<LoopId>,
children: Vec<LoopId>,
depth: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Loops {
loops: Vec<LoopData>,
innermost: Vec<Option<LoopId>>,
roots: Vec<LoopId>,
irreducible: Vec<Block>,
}
impl Loops {
#[must_use]
pub fn new(cfg: &Cfg, doms: &Dominators) -> Self {
let mut build = Build::new(cfg, doms);
let region: Vec<Block> = cfg.postorder().to_vec();
build.region(®ion, None);
build.finish()
}
#[must_use]
pub fn count(&self) -> usize {
self.loops.len()
}
pub fn all(&self) -> impl Iterator<Item = LoopId> + use<> {
(0..self.loops.len()).map(|index| LoopId(index as u32))
}
#[must_use]
pub fn roots(&self) -> &[LoopId] {
&self.roots
}
#[must_use]
pub fn header(&self, id: LoopId) -> Block {
self.loops[id.index()].header
}
#[must_use]
pub fn blocks(&self, id: LoopId) -> &[Block] {
&self.loops[id.index()].blocks
}
#[must_use]
pub fn latches(&self, id: LoopId) -> &[Block] {
&self.loops[id.index()].latches
}
#[must_use]
pub fn exits(&self, id: LoopId) -> &[Exit] {
&self.loops[id.index()].exits
}
#[must_use]
pub fn parent(&self, id: LoopId) -> Option<LoopId> {
self.loops[id.index()].parent
}
#[must_use]
pub fn children(&self, id: LoopId) -> &[LoopId] {
&self.loops[id.index()].children
}
#[must_use]
pub fn depth(&self, id: LoopId) -> u32 {
self.loops[id.index()].depth
}
#[must_use]
pub fn innermost(&self, block: Block) -> Option<LoopId> {
*self.innermost.get(block.index()).unwrap_or(&None)
}
#[must_use]
pub fn contains(&self, id: LoopId, block: Block) -> bool {
let mut walk = self.innermost(block);
while let Some(inner) = walk {
if inner == id {
return true;
}
walk = self.parent(inner);
}
false
}
#[must_use]
pub fn preheader(&self, cfg: &Cfg, id: LoopId) -> Option<Block> {
let header = self.header(id);
let mut outside = cfg.predecessors(header).iter().filter(|&&pred| !self.contains(id, pred));
let candidate = *outside.next()?;
if outside.next().is_some() || cfg.successors(candidate).len() != 1 {
return None;
}
Some(candidate)
}
#[must_use]
pub fn irreducible(&self) -> &[Block] {
&self.irreducible
}
#[must_use]
pub fn is_irreducible(&self, block: Block) -> bool {
self.irreducible.binary_search_by_key(&block.index(), |b| b.index()).is_ok()
}
#[must_use]
pub fn is_invariant(&self, func: &Func, id: LoopId, value: Value) -> bool {
let block = match func[value].def {
Def::Result { inst, .. } => func.block_of(inst),
Def::Param { block, .. } => Some(block),
};
block.is_some_and(|block| !self.contains(id, block))
}
#[must_use]
pub fn problems(&self, cfg: &Cfg, doms: &Dominators) -> Vec<String> {
let mut found = Vec::new();
for id in self.all() {
let header = self.header(id);
for &block in self.blocks(id) {
if !doms.dominates(header, block) {
found.push(format!("loop {} holds a block its header does not dominate", id.0));
break;
}
}
if self.latches(id).is_empty() {
found.push(format!("loop {} has no way back to its header", id.0));
}
for &latch in self.latches(id) {
if !cfg.successors(latch).contains(&header) {
found.push(format!("loop {} has a latch that does not reach its header", id.0));
break;
}
}
for exit in self.exits(id) {
if !self.contains(id, exit.from) || self.contains(id, exit.to) {
found.push(format!("loop {} has an exit that does not leave it", id.0));
break;
}
}
if let Some(parent) = self.parent(id) {
if !self.blocks(id).iter().all(|&block| self.contains(parent, block)) {
found.push(format!("loop {} is not inside the loop it says it is in", id.0));
}
if self.depth(id) != self.depth(parent) + 1 {
found.push(format!("loop {} is not one deeper than its parent", id.0));
}
} else if self.depth(id) != 0 {
found.push(format!("loop {} has a depth and nothing to be deep inside", id.0));
}
}
found
}
}
struct Build<'a> {
cfg: &'a Cfg,
doms: &'a Dominators,
loops: Vec<LoopData>,
innermost: Vec<Option<LoopId>>,
roots: Vec<LoopId>,
irreducible: Vec<Block>,
inside: Vec<bool>,
index: Vec<u32>,
low: Vec<u32>,
stacked: Vec<bool>,
}
const UNVISITED: u32 = u32::MAX;
impl<'a> Build<'a> {
fn new(cfg: &'a Cfg, doms: &'a Dominators) -> Self {
let blocks = cfg.capacity();
Self {
cfg,
doms,
loops: Vec::new(),
innermost: vec![None; blocks],
roots: Vec::new(),
irreducible: Vec::new(),
inside: vec![false; blocks],
index: vec![UNVISITED; blocks],
low: vec![0; blocks],
stacked: vec![false; blocks],
}
}
fn region(&mut self, region: &[Block], parent: Option<LoopId>) {
for &block in region {
self.inside[block.index()] = true;
self.index[block.index()] = UNVISITED;
self.stacked[block.index()] = false;
}
let components = self.components(region);
for &block in region {
self.inside[block.index()] = false;
}
for component in components {
let Some(header) = component
.iter()
.copied()
.try_fold(component[0], |a, b| self.doms.nearest_common_dominator(a, b))
else {
continue;
};
if !component.contains(&header) {
self.irreducible.extend_from_slice(&component);
continue;
}
let id = self.record(header, component, parent);
let inner: Vec<Block> =
self.loops[id.index()].blocks.iter().copied().filter(|&b| b != header).collect();
if !inner.is_empty() {
self.region(&inner, Some(id));
}
}
}
fn record(&mut self, header: Block, blocks: Vec<Block>, parent: Option<LoopId>) -> LoopId {
let id = LoopId(self.loops.len() as u32);
let mut latches = Vec::new();
let mut exits = Vec::new();
for &block in &blocks {
self.innermost[block.index()] = Some(id);
if self.cfg.successors(block).contains(&header) {
latches.push(block);
}
for &next in self.cfg.successors(block) {
if !blocks.contains(&next) {
exits.push(Exit { from: block, to: next });
}
}
}
let depth = parent.map_or(0, |parent| self.loops[parent.index()].depth + 1);
self.loops.push(LoopData {
header,
blocks,
latches,
exits,
parent,
children: Vec::new(),
depth,
});
match parent {
Some(parent) => self.loops[parent.index()].children.push(id),
None => self.roots.push(id),
}
id
}
fn components(&mut self, region: &[Block]) -> Vec<Vec<Block>> {
let mut found = Vec::new();
let mut next = 0;
let mut component: Vec<Block> = Vec::new();
let mut walk: Vec<(Block, usize)> = Vec::new();
for &start in region {
if self.index[start.index()] != UNVISITED {
continue;
}
self.enter(start, &mut next, &mut component);
walk.push((start, 0));
while let Some((block, step)) = walk.pop() {
let successors = self.cfg.successors(block);
if step < successors.len() {
let next_block = successors[step];
walk.push((block, step + 1));
if !self.inside[next_block.index()] {
continue;
}
if self.index[next_block.index()] == UNVISITED {
self.enter(next_block, &mut next, &mut component);
walk.push((next_block, 0));
} else if self.stacked[next_block.index()] {
let seen = self.index[next_block.index()];
self.low[block.index()] = self.low[block.index()].min(seen);
}
continue;
}
if self.low[block.index()] == self.index[block.index()] {
let start = component.iter().rposition(|&b| b == block).expect("on the stack");
let members: Vec<Block> = component.split_off(start);
for &member in &members {
self.stacked[member.index()] = false;
}
if members.len() > 1 || self.cfg.successors(block).contains(&block) {
found.push(members);
}
}
if let Some(&(above, _)) = walk.last() {
let reached = self.low[block.index()];
self.low[above.index()] = self.low[above.index()].min(reached);
}
}
}
found
}
fn enter(&mut self, block: Block, next: &mut u32, component: &mut Vec<Block>) {
self.index[block.index()] = *next;
self.low[block.index()] = *next;
*next += 1;
component.push(block);
self.stacked[block.index()] = true;
}
fn finish(mut self) -> Loops {
self.irreducible.sort_unstable_by_key(|block| block.index());
self.irreducible.dedup();
Loops {
loops: self.loops,
innermost: self.innermost,
roots: self.roots,
irreducible: self.irreducible,
}
}
}
#[cfg(test)]
mod tests {
use rucc_ir::Block;
use crate::cfg::Cfg;
use crate::dom::Dominators;
use crate::loops::Loops;
use crate::testing::graph;
fn b(n: usize) -> Block {
Block::from_usize(n)
}
fn forest(edges: &[&[usize]]) -> (Cfg, Loops) {
let func = graph(edges);
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
assert_eq!(loops.problems(&cfg, &doms), Vec::<String>::new());
(cfg, loops)
}
fn blocks(loops: &Loops, id: crate::loops::LoopId) -> Vec<usize> {
let mut list: Vec<usize> = loops.blocks(id).iter().map(|b| b.index()).collect();
list.sort_unstable();
list
}
#[test]
fn a_function_with_no_cycle_has_no_loops() {
let (_, loops) = forest(&[&[1, 2], &[3], &[3], &[]]);
assert_eq!(loops.count(), 0);
assert!(loops.innermost(b(1)).is_none());
assert!(loops.irreducible().is_empty());
}
#[test]
fn a_block_that_branches_to_itself_is_a_loop() {
let (_, loops) = forest(&[&[1], &[1, 2], &[]]);
assert_eq!(loops.count(), 1);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(1));
assert_eq!(blocks(&loops, id), [1]);
assert_eq!(loops.latches(id), [b(1)]);
assert_eq!(loops.exits(id), [crate::loops::Exit { from: b(1), to: b(2) }]);
}
#[test]
fn a_loop_holds_its_body_and_names_its_latch() {
let (cfg, loops) = forest(&[&[1], &[2, 3], &[1], &[]]);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(1));
assert_eq!(blocks(&loops, id), [1, 2]);
assert_eq!(loops.latches(id), [b(2)]);
assert_eq!(loops.depth(id), 0);
assert_eq!(loops.preheader(&cfg, id), Some(b(0)));
}
#[test]
fn a_loop_inside_a_loop_is_a_child_of_it() {
let (_, loops) = forest(&[&[1], &[2, 4], &[2, 3], &[1], &[]]);
assert_eq!(loops.count(), 2);
let outer = loops.roots()[0];
assert_eq!(loops.header(outer), b(1));
assert_eq!(blocks(&loops, outer), [1, 2, 3]);
assert_eq!(loops.children(outer).len(), 1);
let inner = loops.children(outer)[0];
assert_eq!(loops.header(inner), b(2));
assert_eq!(blocks(&loops, inner), [2]);
assert_eq!(loops.depth(inner), 1);
assert_eq!(loops.parent(inner), Some(outer));
assert_eq!(loops.innermost(b(2)), Some(inner));
assert!(loops.contains(outer, b(2)));
assert!(!loops.contains(inner, b(3)));
}
#[test]
fn two_back_edges_to_one_header_are_two_latches_of_one_loop() {
let (_, loops) = forest(&[&[1], &[2, 3], &[1], &[1, 4], &[]]);
assert_eq!(loops.count(), 1);
let id = loops.roots()[0];
let mut latches: Vec<usize> = loops.latches(id).iter().map(|b| b.index()).collect();
latches.sort_unstable();
assert_eq!(latches, [2, 3]);
}
#[test]
fn a_two_entry_loop_is_irreducible_and_is_not_a_loop() {
let (_, loops) = forest(&[&[1, 2], &[2], &[1, 3], &[]]);
assert_eq!(loops.count(), 0);
assert_eq!(loops.irreducible(), [b(1), b(2)]);
assert!(loops.is_irreducible(b(1)));
assert!(!loops.is_irreducible(b(0)));
}
#[test]
fn an_irreducible_region_inside_a_loop_is_found_and_the_loop_is_still_a_loop() {
let (_, loops) = forest(&[&[1], &[2, 3], &[3, 4], &[2, 4], &[1, 5], &[]]);
assert_eq!(loops.count(), 1);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(1));
assert_eq!(blocks(&loops, id), [1, 2, 3, 4]);
assert_eq!(loops.irreducible(), [b(2), b(3)]);
}
#[test]
fn a_self_loop_inside_an_irreducible_region_is_declined_along_with_it() {
let (_, loops) = forest(&[&[1, 2], &[0, 2], &[1, 2]]);
assert_eq!(loops.count(), 1);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(0));
assert_eq!(blocks(&loops, id), [0, 1, 2]);
assert_eq!(loops.irreducible(), [b(1), b(2)]);
}
#[test]
fn a_loop_with_more_than_one_way_in_has_no_preheader() {
let (cfg, loops) = forest(&[&[1, 2], &[2], &[2, 3], &[]]);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(2));
assert!(loops.preheader(&cfg, id).is_none());
}
#[test]
fn a_predecessor_that_goes_two_ways_is_not_a_preheader() {
let (cfg, loops) = forest(&[&[1, 3], &[1, 2], &[], &[]]);
let id = loops.roots()[0];
assert_eq!(loops.header(id), b(1));
assert!(loops.preheader(&cfg, id).is_none());
}
#[test]
fn an_unreachable_cycle_is_not_a_loop() {
let (_, loops) = forest(&[&[1], &[], &[3], &[2]]);
assert_eq!(loops.count(), 0);
assert!(loops.irreducible().is_empty());
}
#[test]
fn a_value_defined_outside_the_loop_is_invariant_in_it() {
use rucc_base::Interner;
use rucc_ir::{Builder, Func, Signature, Type};
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new());
let entry = func.create_block();
let header = func.create_block();
let after = func.create_block();
let mut build = Builder::new(&mut func, entry);
let outside = build.iconst(Type::int(32), 7);
build.jump(header, &[]);
let mut build = Builder::new(&mut func, header);
let inside = build.iconst(Type::int(32), 9);
let cond = build.iconst(Type::int(1), 1);
build.br_if(cond, header, &[], after, &[]);
let mut build = Builder::new(&mut func, after);
build.ret(&[]);
let cfg = Cfg::new(&func);
let loops = Loops::new(&cfg, &Dominators::new(&cfg));
let id = loops.roots()[0];
assert!(loops.is_invariant(&func, id, outside));
assert!(!loops.is_invariant(&func, id, inside));
}
}