use rucc_ir::{Func, Inst};
use crate::coalesce::{Write, crossed, plane, read};
use crate::discharge::{Fact, covers};
use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
const DEAD: &str = "plane write removed, a later write covers it and nothing in between reads it";
const NOT_COVERED: &str = "plane write kept, no later write to that plane covers all of its bytes";
const NO_FUEL: &str = "plane write kept, the pass ran out of fuel";
#[derive(Debug)]
pub struct DeadPlane;
impl Pass for DeadPlane {
fn name(&self) -> &'static str {
"dead-plane"
}
fn describe(&self) -> &'static str {
"a write to a plane that a later write covers, with nothing reading it in between, goes"
}
fn preserves(&self) -> Preserved {
Preserved::ALL.without(Analysis::Liveness)
}
fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
let mut stats = Stats::new();
let blocks: Vec<_> = func.blocks().collect();
for block in blocks {
let insts: Vec<Inst> = func.insts(block).collect();
let mut standing: Vec<Write> = Vec::new();
for inst in insts {
let opcode = func[inst].opcode;
let Some(kind) = plane(opcode) else {
standing.retain(|write| crossed(opcode, write.kind));
continue;
};
let Some(write) = read(func, inst, kind) else {
standing.retain(|standing| standing.kind != kind);
continue;
};
bury(func, &mut standing, &write, &mut stats, fuel);
standing.push(write);
}
}
stats
}
}
fn bury(
func: &mut Func,
standing: &mut Vec<Write>,
write: &Write,
stats: &mut Stats,
fuel: &mut Fuel,
) {
let over = Fact::range(write.base, write.at, write.size);
let mut gone = Vec::new();
for (index, earlier) in standing.iter().enumerate() {
if earlier.kind != write.kind {
continue;
}
if !covers(&over, &Fact::range(earlier.base, earlier.at, earlier.size)) {
stats.missed(NOT_COVERED);
continue;
}
if !fuel.take() {
stats.missed(NO_FUEL);
continue;
}
gone.push(index);
}
for &index in gone.iter().rev() {
func.remove_inst(standing.remove(index).inst);
stats.optimized(DEAD);
}
}
#[cfg(test)]
mod tests {
use rucc_base::{Idx, Interner};
use rucc_ir::{Block, Builder, Extra, Flags, Func, InstData, Opcode, Signature, Type, Value};
use super::DeadPlane;
use crate::stats::Kind;
use crate::{Fuel, Pass, Stats};
fn start(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)
}
fn walk(build: &mut Builder<'_>, base: Value, offset: i64) -> Value {
let step = build.iconst(Type::int(64), i128::from(offset));
build.binary(Opcode::PtrAdd, base, step, Flags::NONE)
}
fn plane(build: &mut Builder<'_>, kind: Opcode, address: Value, size: i64, node: u32) {
let width = build.iconst(Type::int(64), i128::from(size));
let extra = match kind {
Opcode::MetaType => Extra::Node(Idx::new(node)),
_ => Extra::None,
};
let args = build.func().push_values(&[address, width]);
build.inst(InstData { args, extra, ..InstData::new(kind) }, &[]);
}
fn writes_of(names: &mut Interner, kind: Opcode, at: &[(i64, i64)]) -> Func {
let (mut func, entry, base) = start(names);
let mut build = Builder::new(&mut func, entry);
for &(offset, size) in at {
let address = walk(&mut build, base, offset);
plane(&mut build, kind, address, size, 1);
}
build.ret(&[]);
func
}
fn left(func: &Func, kind: Opcode) -> Vec<(i128, i128)> {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter(|&inst| func[inst].opcode == kind)
.map(|inst| {
let [pointer, length] = func[func[inst].args] else { panic!("two operands") };
let (_, at) = crate::discharge::normal(func, pointer);
let (imm, ty) = crate::fold::constant(func, length).expect("a constant width");
(at, imm.signed(ty))
})
.collect()
}
fn run(func: &mut Func) -> Stats {
with(func, &mut Fuel::unlimited())
}
fn with(func: &mut Func, fuel: &mut Fuel) -> Stats {
DeadPlane.run(func, &mut crate::machine::fixtures::analyses(), fuel)
}
#[test]
fn a_field_written_twice_keeps_only_the_second_pair_of_plane_writes() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaInit, &[(0, 8), (0, 8)]);
let stats = run(&mut func);
assert!(stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8)]);
}
#[test]
fn a_wider_write_buries_every_narrower_one_it_covers() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaInit, &[(0, 8), (8, 8), (16, 8), (0, 24)]);
let stats = run(&mut func);
assert!(stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 24)]);
}
#[test]
fn a_write_that_covers_only_part_of_an_earlier_one_leaves_it_alone() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaInit, &[(0, 8), (0, 4)]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8), (0, 4)]);
assert_eq!(stats.count(Kind::Missed, super::NOT_COVERED), 1);
}
#[test]
fn two_writes_over_bytes_that_do_not_meet_are_both_kept() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaInit, &[(0, 8), (8, 8)]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8), (8, 8)]);
}
#[test]
fn the_node_an_earlier_write_names_does_not_save_it() {
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let mut build = Builder::new(&mut func, entry);
for node in [1, 2] {
let address = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaType, address, 8, node);
}
build.ret(&[]);
let stats = run(&mut func);
assert!(stats.changed());
assert_eq!(left(&func, Opcode::MetaType), [(0, 8)]);
}
#[test]
fn a_read_of_the_same_plane_between_them_is_what_the_earlier_write_was_for() {
for (between, kept) in
[(Opcode::CheckInit, vec![(0, 8), (0, 8)]), (Opcode::CheckBounds, vec![(0, 8)])]
{
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let mut build = Builder::new(&mut func, entry);
let first = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, first, 8, 1);
build.inst(InstData::new(between), &[]);
let second = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, second, 8, 1);
build.ret(&[]);
run(&mut func);
assert_eq!(left(&func, Opcode::MetaInit), kept, "across {}", between.name());
}
}
#[test]
fn a_call_between_them_keeps_the_earlier_write() {
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let mut build = Builder::new(&mut func, entry);
let first = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, first, 8, 1);
build.inst(InstData::new(Opcode::Call), &[]);
let second = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, second, 8, 1);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8), (0, 8)]);
}
#[test]
fn a_write_to_one_plane_does_not_bury_a_write_to_the_other() {
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let mut build = Builder::new(&mut func, entry);
let address = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaType, address, 8, 1);
plane(&mut build, Opcode::MetaInit, address, 8, 1);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaType), [(0, 8)]);
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8)]);
}
#[test]
fn the_epoch_plane_is_left_alone_however_its_writes_line_up() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaEpoch, &[(0, 8), (0, 8)]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaEpoch), [(0, 8), (0, 8)]);
}
#[test]
fn a_write_whose_width_the_pass_cannot_read_decides_nothing_about_the_ones_in_front_of_it() {
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let mut build = Builder::new(&mut func, entry);
let first = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, first, 8, 1);
let width = build.unary(Opcode::PtrToInt, base, Type::int(64));
let args = build.func().push_values(&[first, width]);
build.inst(InstData { args, ..InstData::new(Opcode::MetaInit) }, &[]);
let third = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, third, 8, 1);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
}
#[test]
fn a_write_in_another_block_does_not_reach_back_into_this_one() {
let mut names = Interner::new();
let (mut func, entry, base) = start(&mut names);
let next = func.create_block();
let mut build = Builder::new(&mut func, entry);
let address = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, address, 8, 1);
build.jump(next, &[]);
let mut build = Builder::new(&mut func, next);
let again = walk(&mut build, base, 0);
plane(&mut build, Opcode::MetaInit, again, 8, 1);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8), (0, 8)]);
}
#[test]
fn no_fuel_leaves_every_write_where_it_is() {
let mut names = Interner::new();
let mut func = writes_of(&mut names, Opcode::MetaInit, &[(0, 8), (0, 8)]);
let stats = with(&mut func, &mut Fuel::of(0));
assert!(!stats.changed());
assert_eq!(left(&func, Opcode::MetaInit), [(0, 8), (0, 8)]);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
}
#[test]
fn a_function_with_no_body_is_left_alone() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new());
let stats = run(&mut func);
assert!(!stats.changed());
}
}