use rucc_ir::{Func, Inst, Opcode, Value};
use crate::loops::{LoopId, Loops};
use crate::scev::{Evolution, Invariant, Scev};
use crate::{Analyses, Fuel, Pass, Preserved, Stats};
const POPULATION: &str =
"loop nest two or more deep, perfectly nested, every address in it a straight line";
const NOT_AFFINE: &str =
"loop nest two or more deep, perfectly nested, an address in it is not a straight line";
const NOT_PERFECT: &str = "loop nest, but not perfectly nested, something sits between the loops";
const ALONE: &str = "loop with no loop inside it";
const REFERENCE: &str = "read or write in the innermost loop of a perfect nest";
#[derive(Debug)]
pub struct Nests;
impl Pass for Nests {
fn name(&self) -> &'static str {
"nests"
}
fn describe(&self) -> &'static str {
"counts the loop nests, and changes nothing"
}
fn preserves(&self) -> Preserved {
Preserved::ALL
}
fn run(&self, func: &mut Func, an: &mut Analyses, _fuel: &mut Fuel) -> Stats {
let mut stats = Stats::new();
if func.entry().is_none() {
return stats;
}
let cfg = an.cfg(func).clone();
let loops = an.loops(func).clone();
let mut scev = Scev::new(func, &cfg, &loops);
for id in loops.all() {
if loops.parent(id).is_some() {
continue;
}
match chain(func, &loops, id) {
Chain::Broken => stats.note(NOT_PERFECT),
Chain::Perfect(nest) => report(func, &loops, &mut scev, &nest, &mut stats),
}
}
stats
}
}
enum Chain {
Perfect(Vec<LoopId>),
Broken,
}
fn chain(func: &Func, loops: &Loops, outer: LoopId) -> Chain {
let mut nest = vec![outer];
let mut at = outer;
loop {
let inside = loops.children(at);
let [only] = inside else {
return match inside.is_empty() {
true => Chain::Perfect(nest),
false => Chain::Broken,
};
};
if between(func, loops, at, *only) {
return Chain::Broken;
}
nest.push(*only);
at = *only;
}
}
fn between(func: &Func, loops: &Loops, outer: LoopId, inner: LoopId) -> bool {
loops
.blocks(outer)
.iter()
.filter(|&&block| !loops.contains(inner, block))
.flat_map(|&block| func.insts(block))
.any(|inst| func[inst].opcode.touches_memory())
}
fn report(func: &Func, loops: &Loops, scev: &mut Scev<'_>, nest: &[LoopId], stats: &mut Stats) {
let Some(&innermost) = nest.last() else { return };
if nest.len() < 2 {
stats.note(ALONE);
return;
}
let mut affine = true;
let touching: Vec<Inst> = loops
.blocks(innermost)
.iter()
.flat_map(|&block| func.insts(block))
.filter(|&inst| func[inst].opcode.touches_memory())
.collect();
for inst in touching {
stats.note(REFERENCE);
affine &= match address(func, inst) {
None => false,
Some(addr) => straight(scev, nest, addr),
};
}
stats.note(if affine { POPULATION } else { NOT_AFFINE });
}
fn address(func: &Func, inst: Inst) -> Option<Value> {
let data = func[inst];
let args = &func[data.args];
match data.opcode {
Opcode::Load => args.first().copied(),
Opcode::Store => args.get(1).copied(),
_ => None,
}
}
fn straight(scev: &mut Scev<'_>, nest: &[LoopId], value: Value) -> bool {
let Some((&innermost, outer)) = nest.split_last() else { return true };
match scev.evolution(innermost, value) {
Evolution::Unknown => false,
Evolution::Invariant(inv) => part(scev, outer, inv),
Evolution::Affine(chrec) => part(scev, outer, chrec.base) && part(scev, outer, chrec.step),
}
}
fn part(scev: &mut Scev<'_>, outer: &[LoopId], inv: Invariant) -> bool {
match inv.value {
None => true,
Some(value) => straight(scev, outer, value),
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Block, Builder, Flags, Func, IntPred, MemInfo, MemOrder, Opcode, Restrict, Signature, Type,
Value,
};
use super::{ALONE, NOT_AFFINE, NOT_PERFECT, Nests, POPULATION, REFERENCE};
use crate::stats::Kind;
use crate::{Fuel, Pass, Stats};
fn survey(func: &mut Func) -> Stats {
Nests.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
}
fn plain() -> MemInfo {
MemInfo {
size: 0,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
}
}
struct Counted {
head: Block,
body: Block,
out: Block,
counter: Value,
}
fn counted(func: &mut Func, into: Block, limit: i128) -> Counted {
let head = func.create_block();
let body = func.create_block();
let out = func.create_block();
let i = func.append_param(head, Type::int(32));
let carried = func.append_param(body, Type::int(32));
let mut build = Builder::new(func, into);
let zero = build.iconst(Type::int(32), 0);
build.jump(head, &[zero]);
let mut build = Builder::new(func, head);
let stop = build.iconst(Type::int(32), limit);
let test = build.icmp(IntPred::Slt, i, stop);
build.br_if(test, body, &[i], out, &[]);
Counted { head, body, out, counter: carried }
}
fn close(func: &mut Func, it: &Counted, at: Block) {
let mut build = Builder::new(func, at);
let one = build.iconst(Type::int(32), 1);
let next = build.binary(Opcode::Add, it.counter, one, Flags::NSW);
build.jump(it.head, &[next]);
}
fn shell(names: &mut Interner) -> (Func, Block, Value) {
let signature = Signature::new().with_params(&[Type::PTR]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let base = func.append_param(entry, Type::PTR);
(func, entry, base)
}
#[test]
fn a_loop_with_nothing_inside_it_is_not_a_nest() {
let mut names = Interner::new();
let (mut func, entry, _) = shell(&mut names);
let it = counted(&mut func, entry, 8);
close(&mut func, &it, it.body);
Builder::new(&mut func, it.out).ret(&[]);
let stats = survey(&mut func);
assert_eq!(stats.count(Kind::Note, ALONE), 1);
assert_eq!(stats.count(Kind::Note, POPULATION), 0);
assert!(!stats.changed(), "the survey rewrites nothing");
}
#[test]
fn two_loops_walking_a_row_at_a_time_are_the_population() {
let mut names = Interner::new();
let (mut func, entry, base) = shell(&mut names);
let outer = counted(&mut func, entry, 4);
let mut build = Builder::new(&mut func, outer.body);
let wide = build.unary(Opcode::SExt, outer.counter, Type::int(64));
let stride = build.iconst(Type::int(64), 256);
let along = build.binary(Opcode::Mul, wide, stride, Flags::NSW);
let row = build.binary(Opcode::PtrAdd, base, along, Flags::NONE);
let inner = counted(&mut func, outer.body, 3);
let mut build = Builder::new(&mut func, inner.body);
let step = build.unary(Opcode::SExt, inner.counter, Type::int(64));
let four = build.iconst(Type::int(64), 4);
let by = build.binary(Opcode::Mul, step, four, Flags::NSW);
let addr = build.binary(Opcode::PtrAdd, row, by, Flags::NONE);
build.store(inner.counter, addr, plain(), Flags::NONE);
close(&mut func, &inner, inner.body);
close(&mut func, &outer, inner.out);
Builder::new(&mut func, outer.out).ret(&[]);
let stats = survey(&mut func);
assert_eq!(stats.count(Kind::Note, POPULATION), 1);
assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 0);
assert_eq!(stats.count(Kind::Note, REFERENCE), 1);
}
#[test]
fn an_address_added_from_both_counters_is_not_one_this_compiler_can_describe() {
let mut names = Interner::new();
let (mut func, entry, base) = shell(&mut names);
let outer = counted(&mut func, entry, 4);
let inner = counted(&mut func, outer.body, 3);
let mut build = Builder::new(&mut func, inner.body);
let sum = build.binary(Opcode::Add, outer.counter, inner.counter, Flags::NSW);
let wide = build.unary(Opcode::SExt, sum, Type::int(64));
let addr = build.binary(Opcode::PtrAdd, base, wide, Flags::NONE);
build.store(outer.counter, addr, plain(), Flags::NONE);
close(&mut func, &inner, inner.body);
close(&mut func, &outer, inner.out);
Builder::new(&mut func, outer.out).ret(&[]);
let stats = survey(&mut func);
assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
assert_eq!(stats.count(Kind::Note, POPULATION), 0);
}
#[test]
fn a_write_between_the_two_loops_stops_it_being_a_nest() {
let mut names = Interner::new();
let (mut func, entry, base) = shell(&mut names);
let outer = counted(&mut func, entry, 4);
let inner = counted(&mut func, outer.body, 3);
Builder::new(&mut func, inner.body).store(inner.counter, base, plain(), Flags::NONE);
close(&mut func, &inner, inner.body);
Builder::new(&mut func, inner.out).store(outer.counter, base, plain(), Flags::NONE);
close(&mut func, &outer, inner.out);
Builder::new(&mut func, outer.out).ret(&[]);
let stats = survey(&mut func);
assert_eq!(stats.count(Kind::Note, NOT_PERFECT), 1);
assert_eq!(stats.count(Kind::Note, POPULATION), 0);
}
#[test]
fn an_address_that_came_out_of_memory_is_not_a_straight_line() {
let mut names = Interner::new();
let (mut func, entry, base) = shell(&mut names);
let outer = counted(&mut func, entry, 4);
let inner = counted(&mut func, outer.body, 3);
let mut build = Builder::new(&mut func, inner.body);
let addr = build.load(Type::PTR, base, plain(), Flags::NONE);
build.store(inner.counter, addr, plain(), Flags::NONE);
close(&mut func, &inner, inner.body);
close(&mut func, &outer, inner.out);
Builder::new(&mut func, outer.out).ret(&[]);
let stats = survey(&mut func);
assert_eq!(stats.count(Kind::Note, NOT_AFFINE), 1);
assert_eq!(stats.count(Kind::Note, POPULATION), 0);
assert_eq!(stats.count(Kind::Note, REFERENCE), 2, "the load and the write both count");
}
}