#[path = "../src/testing.rs"]
#[allow(dead_code)]
mod testing;
use std::collections::BTreeSet;
use rucc_ir::Block;
use rucc_opt::{Cfg, Dominators, Loops};
use crate::testing::graph;
fn b(n: usize) -> Block {
Block::from_usize(n)
}
#[test]
fn every_loop_is_the_natural_loop_of_its_header_on_a_thousand_random_graphs() {
let mut random = Random::new(0x10ad_5eed_c0ff_ee01);
let mut with_loops = 0;
for _ in 0..1000 {
let edges = random.graph();
let lists: Vec<&[usize]> = edges.iter().map(Vec::as_slice).collect();
let func = graph(&lists);
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(), "in {edges:?}");
let found: BTreeSet<usize> = loops.all().map(|id| loops.header(id).index()).collect();
let expected = headers(&edges, &cfg, &doms);
assert!(found.is_subset(&expected), "headers in {edges:?}: {found:?} against {expected:?}");
if !found.is_empty() {
with_loops += 1;
}
if loops.irreducible().is_empty() {
assert_eq!(found, expected, "headers in {edges:?}");
} else {
for header in expected.difference(&found) {
assert!(
loops.is_irreducible(b(*header)),
"back edge head {header} is neither a loop nor irreducible in {edges:?}"
);
}
}
for id in loops.all() {
let header = loops.header(id).index();
let mut body: Vec<usize> = loops.blocks(id).iter().map(|block| block.index()).collect();
body.sort_unstable();
let body: BTreeSet<usize> = body.into_iter().collect();
assert_eq!(body, natural(&edges, &cfg, &doms, header), "loop at {header} in {edges:?}");
}
for block in cfg.postorder() {
if !in_a_cycle(&edges, block.index()) {
continue;
}
assert!(
loops.innermost(*block).is_some() || loops.is_irreducible(*block),
"block {} goes round and is neither in a loop nor irreducible in {edges:?}",
block.index()
);
}
}
assert!(with_loops > 100, "only {with_loops} of a thousand graphs had a loop in them");
}
#[test]
fn the_innermost_loop_of_a_block_is_the_deepest_one_holding_it() {
let mut random = Random::new(0xdeed_beef_1357_9bdf);
for _ in 0..1000 {
let edges = random.graph();
let lists: Vec<&[usize]> = edges.iter().map(Vec::as_slice).collect();
let func = graph(&lists);
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
for block in cfg.postorder().iter().copied() {
let holding: Vec<_> =
loops.all().filter(|&id| loops.blocks(id).contains(&block)).collect();
let deepest = holding.iter().copied().max_by_key(|&id| loops.depth(id));
assert_eq!(loops.innermost(block), deepest, "block {} in {edges:?}", block.index());
for id in loops.all() {
assert_eq!(
loops.contains(id, block),
holding.contains(&id),
"loop {} against block {} in {edges:?}",
id.index(),
block.index()
);
}
}
}
}
#[test]
fn a_preheader_is_the_only_way_in_and_leads_nowhere_else() {
let mut random = Random::new(0x9fe1_dead_1234_5678);
let mut found = 0;
for _ in 0..1000 {
let edges = random.graph();
let lists: Vec<&[usize]> = edges.iter().map(Vec::as_slice).collect();
let func = graph(&lists);
let cfg = Cfg::new(&func);
let doms = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &doms);
for id in loops.all() {
let header = loops.header(id);
let outside: Vec<Block> = cfg
.predecessors(header)
.iter()
.copied()
.filter(|&pred| !loops.contains(id, pred))
.collect();
match loops.preheader(&cfg, id) {
Some(preheader) => {
found += 1;
assert_eq!(outside, [preheader], "in {edges:?}");
assert_eq!(cfg.successors(preheader), [header], "in {edges:?}");
}
None => assert!(
outside.len() != 1 || cfg.successors(outside[0]).len() != 1,
"loop at {} in {edges:?} has a preheader and was told it does not",
header.index()
),
}
}
}
assert!(found > 50, "only {found} loops out of a thousand graphs had a preheader");
}
fn headers(edges: &[Vec<usize>], cfg: &Cfg, doms: &Dominators) -> BTreeSet<usize> {
let mut found = BTreeSet::new();
for (tail, targets) in edges.iter().enumerate() {
if !cfg.reaches(b(tail)) {
continue;
}
for &head in targets {
if doms.dominates(b(head), b(tail)) {
found.insert(head);
}
}
}
found
}
fn natural(edges: &[Vec<usize>], cfg: &Cfg, doms: &Dominators, header: usize) -> BTreeSet<usize> {
let mut body = BTreeSet::new();
body.insert(header);
let mut stack: Vec<usize> = (0..edges.len())
.filter(|&tail| cfg.reaches(b(tail)) && edges[tail].contains(&header))
.filter(|&tail| doms.dominates(b(header), b(tail)))
.collect();
let mut seen: Vec<bool> = vec![false; edges.len()];
for &tail in &stack {
seen[tail] = true;
body.insert(tail);
}
seen[header] = true;
stack.retain(|&tail| tail != header);
while let Some(block) = stack.pop() {
for (pred, targets) in edges.iter().enumerate() {
if pred == header || seen[pred] || !targets.contains(&block) || !cfg.reaches(b(pred)) {
continue;
}
seen[pred] = true;
body.insert(pred);
stack.push(pred);
}
}
body
}
fn in_a_cycle(edges: &[Vec<usize>], from: usize) -> bool {
let mut seen = vec![false; edges.len()];
let mut stack = edges[from].clone();
while let Some(block) = stack.pop() {
if block == from {
return true;
}
if seen[block] {
continue;
}
seen[block] = true;
stack.extend_from_slice(&edges[block]);
}
false
}
struct Random(u64);
impl Random {
fn new(seed: u64) -> Self {
Self(seed)
}
fn bits(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, bound: usize) -> usize {
(self.bits() % bound as u64) as usize
}
fn graph(&mut self) -> Vec<Vec<usize>> {
let blocks = 2 + self.below(6);
(0..blocks)
.map(|_| {
let count = self.below(4);
(0..count).map(|_| self.below(blocks)).collect()
})
.collect()
}
}