use std::collections::{HashMap, HashSet};
use rucc_cost::heuristics;
use rucc_ir::{
Block, BlockCall, Builder, ExtraKind, Func, Inst, InstData, Opcode, Type, Value, ValueList,
};
use crate::cfg::Cfg;
use crate::dom::Dominators;
use crate::loops::{LoopId, Loops};
use crate::range::query::Ranges;
use crate::{Analyses, Fuel, Pass, Preserved, Stats, prune, simplify_cfg};
const COPIED: &str = "loop header copied in front of the loop so the test is at the bottom";
const ENTERED: &str = "entry test removed, the value ranges say the loop runs";
const SKIPPED: &str = "loop removed, the value ranges say the entry test never holds";
const UNDECIDED: &str = "entry test kept, the value ranges do not settle whether the loop runs";
const ALREADY: &str = "loop left as it was, it already tests at the bottom";
const TOO_BIG: &str = "loop header not copied, it is larger than this level allows";
const EFFECTS: &str = "loop header not copied, something in it may not be repeated";
const SHAPE: &str = "loop header not copied, its exit is not a two way branch";
const ESCAPES: &str = "loop header not copied, a value it defines is read outside the loop";
const NO_PREHEADER: &str = "loop header not copied, the loop has not been canonicalized";
const NO_FUEL: &str = "loop left as it was, the pass ran out of fuel";
#[derive(Debug)]
pub struct HeaderCopy {
name: &'static str,
budget: u32,
}
pub static SPEED: HeaderCopy =
HeaderCopy { name: "header-copy", budget: heuristics::LOOP_HEADER_INSNS_FOR_SPEED };
pub static SIZE: HeaderCopy =
HeaderCopy { name: "header-copy-small", budget: heuristics::LOOP_HEADER_INSNS_FOR_SIZE };
impl Pass for HeaderCopy {
fn name(&self) -> &'static str {
self.name
}
fn describe(&self) -> &'static str {
"copies a loop header in front of the loop, turning a while into a do-while"
}
fn preserves(&self) -> Preserved {
Preserved::NONE
}
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 mut done = HashSet::new();
let mut say = true;
let mut dry = false;
loop {
let jobs = self.plan(func, an, &done, &mut stats, say);
say = false;
if jobs.is_empty() {
break;
}
let mut copies = Vec::with_capacity(jobs.len());
for job in &jobs {
if !fuel.take() {
stats.missed(NO_FUEL);
dry = true;
break;
}
done.insert(job.header);
done.insert(job.body);
copies.push(apply(func, job));
stats.optimized(COPIED);
}
an.clear();
if settle(func, an, &copies, &mut stats) {
an.clear();
}
if dry {
break;
}
}
if stats.changed() {
simplify_cfg::sweep(func, an, &mut stats);
}
an.clear();
stats
}
}
#[derive(Debug)]
struct Job {
id: LoopId,
header: Block,
entry: Block,
body: Block,
carried: Vec<Value>,
}
#[derive(Debug)]
struct Candidate {
id: LoopId,
header: Block,
entry: Block,
body: Block,
defined: Vec<Value>,
}
impl HeaderCopy {
fn plan(
&self,
func: &Func,
an: &mut Analyses,
done: &HashSet<Block>,
stats: &mut Stats,
say: bool,
) -> Vec<Job> {
let (cfg, dom, loops) = (an.cfg(func), an.dominators(func), an.loops(func));
let mut wanted = Vec::new();
for id in loops.all() {
let header = loops.header(id);
if done.contains(&header) {
continue;
}
match self.consider(func, cfg, loops, id, header) {
Ok(candidate) => wanted.push(candidate),
Err(why) if say && why == ALREADY => stats.note(ALREADY),
Err(why) if say => stats.missed(why),
Err(_) => (),
}
}
let jobs = carried(func, dom, loops, wanted, stats, say);
independent(loops, jobs)
}
fn consider(
&self,
func: &Func,
cfg: &Cfg,
loops: &Loops,
id: LoopId,
header: Block,
) -> Result<Candidate, &'static str> {
let leaves = cfg.successors(header).iter().any(|&to| !loops.contains(id, to));
if !leaves {
return Err(ALREADY);
}
let entry = loops.preheader(cfg, id).ok_or(NO_PREHEADER)?;
let term = func.terminator(header).ok_or(SHAPE)?;
if func[term].opcode != Opcode::BrIf {
return Err(SHAPE);
}
let calls: Vec<BlockCall> = func.successors(term).collect();
let [then_call, else_call] = calls[..].try_into().map_err(|_| SHAPE)?;
let body = match (loops.contains(id, then_call.block), loops.contains(id, else_call.block))
{
(true, false) => then_call.block,
(false, true) => else_call.block,
_ => return Err(SHAPE),
};
if body == header {
return Err(SHAPE);
}
let insts: Vec<Inst> = func.insts(header).filter(|&inst| inst != term).collect();
if insts.len() > self.budget as usize {
return Err(TOO_BIG);
}
for &inst in &insts {
if !repeatable(func, inst) {
return Err(EFFECTS);
}
}
let mut defined: Vec<Value> = func[header].params.clone();
for &inst in &insts {
defined.extend(func[inst].results());
}
Ok(Candidate { id, header, entry, body, defined })
}
}
fn repeatable(func: &Func, inst: Inst) -> bool {
let data = func[inst];
if data.opcode.has_effects() || func.carries_mem(inst) {
return false;
}
matches!(
data.extra.kind(),
ExtraKind::None
| ExtraKind::Imm
| ExtraKind::Symbol
| ExtraKind::IntPred
| ExtraKind::FloatPred
)
}
fn carried(
func: &Func,
dom: &Dominators,
loops: &Loops,
wanted: Vec<Candidate>,
stats: &mut Stats,
say: bool,
) -> Vec<Job> {
let mut watched: HashMap<Value, usize> = HashMap::new();
for (which, candidate) in wanted.iter().enumerate() {
for &value in &candidate.defined {
watched.insert(value, which);
}
}
let mut read: Vec<HashSet<Value>> = vec![HashSet::new(); wanted.len()];
let mut escapes = vec![false; wanted.len()];
let mut names: Vec<usize> = Vec::new();
for block in func.blocks() {
names.clear();
for inst in func.insts(block) {
reads(func, inst, block, &wanted, &watched, &mut read, &mut names);
}
for &which in &names {
let candidate = &wanted[which];
if !loops.contains(candidate.id, block) || !dom.dominates(candidate.body, block) {
escapes[which] = true;
}
}
}
let mut jobs = Vec::new();
for (which, candidate) in wanted.into_iter().enumerate() {
if escapes[which] {
if say {
stats.missed(ESCAPES);
}
continue;
}
let taken = &read[which];
let carried = candidate.defined.into_iter().filter(|value| taken.contains(value)).collect();
jobs.push(Job {
id: candidate.id,
header: candidate.header,
entry: candidate.entry,
body: candidate.body,
carried,
});
}
jobs
}
fn reads(
func: &Func,
inst: Inst,
block: Block,
wanted: &[Candidate],
watched: &HashMap<Value, usize>,
read: &mut [HashSet<Value>],
names: &mut Vec<usize>,
) {
let mut note = |value: Value| {
let Some(&which) = watched.get(&value) else { return };
if block == wanted[which].header {
return;
}
read[which].insert(value);
if !names.contains(&which) {
names.push(which);
}
};
for &value in &func[func[inst].args] {
note(value);
}
for call in func.successors(inst) {
for &value in &func[call.args] {
note(value);
}
}
}
fn independent(loops: &Loops, jobs: Vec<Job>) -> Vec<Job> {
let mut blocked = vec![false; loops.count()];
let mut taken = vec![false; loops.count()];
let mut kept: Vec<Job> = Vec::new();
for job in jobs {
if blocked[job.id.index()] || inside(loops, &taken, job.entry) {
continue;
}
let mut up = Some(job.id);
while let Some(id) = up {
blocked[id.index()] = true;
up = loops.parent(id);
}
let mut down = vec![job.id];
while let Some(id) = down.pop() {
blocked[id.index()] = true;
down.extend(loops.children(id));
}
let mut around = loops.innermost(job.entry);
while let Some(id) = around {
blocked[id.index()] = true;
around = loops.parent(id);
}
taken[job.id.index()] = true;
kept.push(job);
}
kept
}
fn inside(loops: &Loops, taken: &[bool], block: Block) -> bool {
let mut walk = loops.innermost(block);
while let Some(id) = walk {
if taken[id.index()] {
return true;
}
walk = loops.parent(id);
}
false
}
fn apply(func: &mut Func, job: &Job) -> Block {
let term = func.terminator(job.header).expect("the plan read this terminator");
let entry_term = func.terminator(job.entry).expect("a preheader ends in a jump");
let incoming = edge_args(func, entry_term, job.header);
let mut map: HashMap<Value, Value> = HashMap::new();
for (¶m, &arg) in func[job.header].params.clone().iter().zip(&incoming) {
map.insert(param, arg);
}
let copy = func.create_block();
let insts: Vec<Inst> = func.insts(job.header).filter(|&inst| inst != term).collect();
for inst in insts {
clone_into(func, copy, inst, &mut map);
}
clone_branch(func, copy, term, &map);
for at in func.target_list(entry_term).iter() {
let call = func[at];
if call.block == job.header {
func.set_block_call(at, BlockCall { block: copy, args: ValueList::EMPTY, ..call });
}
}
for &value in &job.carried {
let arrived = map.get(&value).copied().unwrap_or(value);
merge(func, job, copy, value, arrived);
}
copy
}
fn edge_args(func: &Func, term: Inst, to: Block) -> Vec<Value> {
for call in func.successors(term) {
if call.block == to {
return func[call.args].to_vec();
}
}
Vec::new()
}
fn clone_into(func: &mut Func, into: Block, inst: Inst, map: &mut HashMap<Value, Value>) {
let data = func[inst];
let args: Vec<Value> =
func[data.args].iter().map(|value| map.get(value).copied().unwrap_or(*value)).collect();
let types: Vec<Type> = data.results().map(|result| func[result].ty).collect();
let span = func.span(inst);
let args = func.push_values(&args);
let fresh = func.create_inst(InstData { args, ..data }, &types, span);
func.append_inst(into, fresh);
for (old, new) in data.results().zip(func[fresh].results()) {
map.insert(old, new);
}
}
fn clone_branch(func: &mut Func, into: Block, term: Inst, map: &HashMap<Value, Value>) {
let at = |value: &Value| map.get(value).copied().unwrap_or(*value);
let cond = at(&func[func[term].args][0]);
let calls: Vec<BlockCall> = func.successors(term).collect();
let args: Vec<Vec<Value>> =
calls.iter().map(|call| func[call.args].iter().map(at).collect()).collect();
Builder::new(func, into).br_if(cond, calls[0].block, &args[0], calls[1].block, &args[1]);
}
fn merge(func: &mut Func, job: &Job, copy: Block, value: Value, arrived: Value) {
let param = func.append_param(job.body, func[value].ty);
for block in func.blocks().collect::<Vec<_>>() {
let Some(term) = func.terminator(block) else { continue };
let carry = if block == job.header {
value
} else if block == copy {
arrived
} else {
param
};
for at in func.target_list(term).iter() {
let call = func[at];
if call.block != job.body {
continue;
}
let args = func.append_arg(call.args, carry);
func.set_block_call(at, BlockCall { args, ..call });
}
}
for block in func.blocks().collect::<Vec<_>>() {
if block == job.header || block == copy {
continue;
}
for inst in func.insts(block).collect::<Vec<_>>() {
let swap = |had: Value| if had == value { param } else { had };
func.rewrite(func[inst].args, swap);
for at in func.target_list(inst).iter() {
func.rewrite(func[at].args, swap);
}
}
}
}
fn settle(func: &mut Func, an: &mut Analyses, copies: &[Block], stats: &mut Stats) -> bool {
let mut out: Vec<(Inst, BlockCall, bool)> = Vec::new();
{
let cfg = an.cfg(func);
let dom = an.dominators(func);
let mut ranges = Ranges::new(func, cfg, dom);
for © in copies {
let Some(term) = func.terminator(copy) else { continue };
let cond = func[func[term].args][0];
let Some(taken) = prune::settled(func, &mut ranges, copy, cond) else {
stats.missed(UNDECIDED);
continue;
};
let calls: Vec<BlockCall> = func.successors(term).collect();
out.push((term, if taken { calls[0] } else { calls[1] }, taken));
}
}
if out.is_empty() {
return false;
}
for (term, call, taken) in out {
simplify_cfg::jump_to(func, term, call);
stats.optimized(if taken { ENTERED } else { SKIPPED });
}
true
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Block, Builder, Def, Flags, Func, IntPred, MemInfo, MemOrder, Module, Opcode, Restrict,
Signature, Type, verify_func,
};
use rucc_target::{TargetInfo, Triple};
use super::{HeaderCopy, SIZE, SPEED};
use crate::canon::Canon;
use crate::cfg::Cfg;
use crate::dom::Dominators;
use crate::loops::Loops;
use crate::stats::Kind;
use crate::{Fuel, Pass, Stats};
fn copied(func: &mut Func, pass: &HeaderCopy) -> Stats {
let mut an = crate::machine::fixtures::analyses();
Canon.run(func, &mut an, &mut Fuel::unlimited());
pass.run(func, &mut an, &mut Fuel::unlimited())
}
fn forest(func: &Func) -> (Cfg, Dominators, Loops) {
let cfg = Cfg::new(func);
let dom = Dominators::new(&cfg);
let loops = Loops::new(&cfg, &dom);
(cfg, dom, loops)
}
fn sound(func: &Func, names: &mut Interner) {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let module = Module::new(names.intern("t.c"), &target);
if let Err(errors) = verify_func(&module, func, names) {
panic!("{errors:#?}");
}
}
fn counted(bound: Option<i128>) -> (Func, Interner, Vec<Block>) {
let mut names = Interner::new();
let params: &[Type] = if bound.is_some() { &[] } else { &[Type::int(32)] };
let signature = Signature::new().with_params(params).with_returns(&[Type::int(32)]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let head = func.create_block();
let body = func.create_block();
let done = func.create_block();
let limit = match bound {
Some(value) => Builder::new(&mut func, entry).iconst(Type::int(32), value),
None => func.append_param(entry, Type::int(32)),
};
let i = func.append_param(head, Type::int(32));
let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
Builder::new(&mut func, body).jump(head, &[next]);
Builder::new(&mut func, done).ret(&[i]);
(func, names, vec![entry, head, body, done])
}
fn tests_at_the_top(func: &Func) -> bool {
let (cfg, dom, loops) = forest(func);
let _ = dom;
let id = loops.all().next().expect("there is a loop");
let header = loops.header(id);
cfg.successors(header).iter().any(|&to| !loops.contains(id, to))
}
#[test]
fn a_loop_that_tests_at_the_top_ends_up_testing_at_the_bottom() {
let (mut func, mut names, _) = counted(None);
assert!(tests_at_the_top(&func), "the shape this pass is for");
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
assert!(!tests_at_the_top(&func), "the header no longer leaves the loop");
sound(&func, &mut names);
}
#[test]
fn the_value_the_header_defined_is_merged_where_the_two_ways_in_meet() {
let (mut func, mut names, blocks) = counted(None);
let body = blocks[2];
assert!(func[body].params.is_empty(), "the body carries nothing to start with");
copied(&mut func, &SPEED);
assert_eq!(func[body].params.len(), 1, "the counter arrives as a parameter now");
assert_eq!(
Cfg::new(&func).predecessors(body).len(),
2,
"one edge from the header and one from the copy"
);
sound(&func, &mut names);
}
#[test]
fn an_entry_test_the_ranges_settle_is_taken_out() {
let (mut func, mut names, _) = counted(Some(10));
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 1);
assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 0);
sound(&func, &mut names);
let (cfg, _dom, loops) = forest(&func);
let id = loops.all().next().expect("the loop is still there");
let entry = func.entry().expect("there is an entry");
assert!(cfg.reaches(loops.header(id)), "and it is still reached");
assert_eq!(cfg.successors(entry).len(), 1, "the guard in front of it has gone");
}
#[test]
fn a_loop_the_ranges_say_never_runs_is_removed() {
let (mut func, mut names, blocks) = counted(Some(0));
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::SKIPPED), 1);
sound(&func, &mut names);
let (_cfg, _dom, loops) = forest(&func);
assert_eq!(loops.count(), 0, "there is no loop left");
assert!(!func.blocks().any(|block| block == blocks[2]), "and the body has gone with it");
}
#[test]
fn a_test_the_ranges_cannot_settle_leaves_the_guard_where_it_is() {
let (mut func, _names, _) = counted(None);
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
assert_eq!(stats.count(Kind::Missed, super::UNDECIDED), 1);
assert_eq!(stats.count(Kind::Optimized, super::ENTERED), 0);
}
#[test]
fn a_second_run_changes_nothing() {
let (mut func, mut names, _) = counted(None);
copied(&mut func, &SPEED);
let again =
SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
assert_eq!(again.count(Kind::Optimized, super::COPIED), 0, "there is nothing left to do");
assert_eq!(again.count(Kind::Note, super::ALREADY), 1, "and it says why");
sound(&func, &mut names);
}
#[test]
fn a_header_that_writes_to_memory_is_left_alone() {
let mut names = Interner::new();
let signature = Signature::new().with_params(&[Type::int(32), Type::PTR]).with_returns(&[]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let head = func.create_block();
let body = func.create_block();
let done = func.create_block();
let limit = func.append_param(entry, Type::int(32));
let addr = func.append_param(entry, Type::PTR);
let i = func.append_param(head, Type::int(32));
let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let access = MemInfo {
size: 4,
align: 4,
order: MemOrder::NotAtomic,
tbaa: None,
owns: 0,
restrict: Restrict::NONE,
};
Builder::new(&mut func, head).store(i, addr, access, Flags::NONE);
let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
let one = Builder::new(&mut func, body).iconst(Type::int(32), 1);
let next = Builder::new(&mut func, body).binary(Opcode::Add, i, one, Flags::NONE);
Builder::new(&mut func, body).jump(head, &[next]);
Builder::new(&mut func, done).ret(&[]);
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
assert_eq!(stats.count(Kind::Missed, super::EFFECTS), 1);
assert!(tests_at_the_top(&func), "the loop is exactly as it was");
}
#[test]
fn a_header_larger_than_the_level_allows_is_left_alone() {
let stats = copied(&mut padded(6), &SIZE);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
assert_eq!(stats.count(Kind::Missed, super::TOO_BIG), 1);
let stats = copied(&mut padded(6), &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1, "the speed budget is wider");
}
fn padded(extra: usize) -> Func {
let (mut func, _names, blocks) = counted(None);
let head = blocks[1];
let term = func.terminator(head).expect("the header branches");
for _ in 0..extra {
let filler = Builder::new(&mut func, head).iconst(Type::int(32), 7);
let Def::Result { inst, .. } = func[filler].def else { unreachable!("an iconst") };
func.remove_inst(inst);
func.insert_before(inst, term);
}
func
}
#[test]
fn fuel_stops_the_copy_where_it_stands() {
let (mut func, _names, _) = counted(None);
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let stats = SPEED.run(&mut func, &mut an, &mut Fuel::of(0));
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
assert!(tests_at_the_top(&func), "and the loop is as it was");
}
#[test]
fn a_value_the_header_defines_and_the_code_after_the_loop_reads_is_declined() {
let (mut func, _names, _) = counted(None);
let stats =
SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 1);
assert!(tests_at_the_top(&func), "and the loop is as it was");
}
#[test]
fn a_loop_with_no_preheader_is_declined() {
let mut names = Interner::new();
let signature = Signature::new().with_params(&[Type::int(1), Type::int(32)]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let one = func.create_block();
let two = func.create_block();
let head = func.create_block();
let body = func.create_block();
let done = func.create_block();
let c = func.append_param(entry, Type::int(1));
let limit = func.append_param(entry, Type::int(32));
let i = func.append_param(head, Type::int(32));
Builder::new(&mut func, entry).br_if(c, one, &[], two, &[]);
let zero = Builder::new(&mut func, one).iconst(Type::int(32), 0);
Builder::new(&mut func, one).jump(head, &[zero]);
let start = Builder::new(&mut func, two).iconst(Type::int(32), 1);
Builder::new(&mut func, two).jump(head, &[start]);
let test = Builder::new(&mut func, head).icmp(IntPred::Slt, i, limit);
Builder::new(&mut func, head).br_if(test, body, &[], done, &[]);
Builder::new(&mut func, body).jump(head, &[i]);
Builder::new(&mut func, done).ret(&[]);
let stats =
SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 0);
assert_eq!(stats.count(Kind::Missed, super::NO_PREHEADER), 1);
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
sound(&func, &mut names);
}
fn side_by_side() -> (Func, Interner, Vec<Block>) {
let mut names = Interner::new();
let signature = Signature::new().with_params(&[Type::int(32)]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let one = func.create_block();
let up = func.create_block();
let mid = func.create_block();
let two = func.create_block();
let down = func.create_block();
let done = func.create_block();
let n = func.append_param(entry, Type::int(32));
let i = func.append_param(one, Type::int(32));
let j = func.append_param(two, Type::int(32));
let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
Builder::new(&mut func, entry).jump(one, &[zero]);
let t = Builder::new(&mut func, one).icmp(IntPred::Slt, i, n);
Builder::new(&mut func, one).br_if(t, up, &[], mid, &[]);
let step = Builder::new(&mut func, up).iconst(Type::int(32), 1);
let next = Builder::new(&mut func, up).binary(Opcode::Add, i, step, Flags::NONE);
Builder::new(&mut func, up).jump(one, &[next]);
let start = Builder::new(&mut func, mid).iconst(Type::int(32), 0);
Builder::new(&mut func, mid).jump(two, &[start]);
let u = Builder::new(&mut func, two).icmp(IntPred::Slt, j, n);
Builder::new(&mut func, two).br_if(u, down, &[], done, &[]);
let stride = Builder::new(&mut func, down).iconst(Type::int(32), 1);
let after = Builder::new(&mut func, down).binary(Opcode::Add, j, stride, Flags::NONE);
Builder::new(&mut func, down).jump(two, &[after]);
Builder::new(&mut func, done).ret(&[]);
(func, names, vec![up, down])
}
#[test]
fn two_loops_that_do_not_meet_are_both_copied() {
let (mut func, mut names, bodies) = side_by_side();
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
sound(&func, &mut names);
let (_cfg, _dom, loops) = forest(&func);
assert_eq!(loops.count(), 2, "both loops are still loops");
for id in loops.all() {
let header = loops.header(id);
assert!(
!Cfg::new(&func).successors(header).iter().any(|&to| !loops.contains(id, to)),
"and neither of them tests at the top any more"
);
}
for body in bodies {
assert_eq!(func[body].params.len(), 1, "each body carries its own counter");
}
}
fn nested() -> (Func, Interner) {
let mut names = Interner::new();
let signature = Signature::new().with_params(&[Type::int(32)]);
let mut func = Func::new(names.intern("f"), signature);
let entry = func.create_block();
let outer = func.create_block();
let ahead = func.create_block();
let inner = func.create_block();
let under = func.create_block();
let latch = func.create_block();
let done = func.create_block();
let n = func.append_param(entry, Type::int(32));
let i = func.append_param(outer, Type::int(32));
let j = func.append_param(inner, Type::int(32));
let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
Builder::new(&mut func, entry).jump(outer, &[zero]);
let t = Builder::new(&mut func, outer).icmp(IntPred::Slt, i, n);
Builder::new(&mut func, outer).br_if(t, ahead, &[], done, &[]);
let start = Builder::new(&mut func, ahead).iconst(Type::int(32), 0);
Builder::new(&mut func, ahead).jump(inner, &[start]);
let u = Builder::new(&mut func, inner).icmp(IntPred::Slt, j, n);
Builder::new(&mut func, inner).br_if(u, under, &[], latch, &[]);
let stride = Builder::new(&mut func, under).iconst(Type::int(32), 1);
let after = Builder::new(&mut func, under).binary(Opcode::Add, j, stride, Flags::NONE);
Builder::new(&mut func, under).jump(inner, &[after]);
let step = Builder::new(&mut func, latch).iconst(Type::int(32), 1);
let next = Builder::new(&mut func, latch).binary(Opcode::Add, i, step, Flags::NONE);
Builder::new(&mut func, latch).jump(outer, &[next]);
Builder::new(&mut func, done).ret(&[]);
(func, names)
}
#[test]
fn a_loop_and_the_loop_inside_it_are_copied_one_round_apart() {
let (mut func, mut names) = nested();
let stats = copied(&mut func, &SPEED);
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 2);
sound(&func, &mut names);
let (cfg, _dom, loops) = forest(&func);
assert_eq!(loops.count(), 2, "both loops survived the copy");
for id in loops.all() {
let header = loops.header(id);
assert!(
!cfg.successors(header).iter().any(|&to| !loops.contains(id, to)),
"and both test at the bottom now"
);
}
}
#[test]
fn a_round_stops_where_the_fuel_does() {
let (mut func, mut names) = {
let (mut func, names, _) = side_by_side();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
(func, names)
};
let stats =
SPEED.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::of(1));
assert_eq!(stats.count(Kind::Optimized, super::COPIED), 1);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
sound(&func, &mut names);
}
}