use rucc_mir::{Func, Inst, Kept, Where};
use rucc_regalloc::Allocation;
use rucc_regalloc::assign::Place;
use rucc_regalloc::order::Point;
use crate::frame::Frame;
#[must_use]
pub fn before(func: &Func) -> Vec<Inst> {
func.blocks().flat_map(|block| func.insts(block)).collect()
}
#[must_use]
pub fn of(func: &Func, before: &[Inst], allocation: &Allocation, frame: &Frame) -> Vec<Kept> {
if func.named.is_empty() {
return Vec::new();
}
let Some(line) = line(func, before, allocation) else { return Vec::new() };
let mut out = Vec::new();
for &(decl, reg) in &func.named {
let Some(class) = func.class_of(reg) else { continue };
let at = match allocation.assignment.place(reg) {
Some(Place::Reg(reg)) => Where::Reg { reg, class },
Some(Place::Slot(slot)) => match frame.slot_from_frame_base(slot) {
Some(at) => Where::Frame(at),
None => continue,
},
None => continue,
};
let Some(area) = allocation.live.area(reg) else { continue };
for piece in area.pieces() {
for run in &line {
let lo = run.partition_point(|&(point, _)| point <= piece.start);
let hi = run.partition_point(|&(point, _)| point <= piece.end);
if lo >= hi {
continue;
}
out.push(Kept { decl, at, from: run[lo].1, to: run[hi - 1].1 });
}
}
}
out
}
fn line(func: &Func, before: &[Inst], allocation: &Allocation) -> Option<Vec<Vec<(Point, Inst)>>> {
let mut known = vec![false; func.inst_count()];
for &inst in before {
known[inst.index()] = true;
}
let mut out = Vec::with_capacity(func.block_count());
for block in func.blocks() {
let mut run: Vec<(Point, Inst)> = Vec::new();
for inst in func.insts(block) {
if !known[inst.index()] {
continue;
}
let point = allocation.order.early(inst);
if run.last().is_some_and(|&(last, _)| last >= point) {
return None;
}
run.push((point, inst));
}
if !run.is_empty() {
out.push(run);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{BlockCall, Func, Opcode, Reg};
use rucc_regalloc::assign::Env;
use rucc_target::x86_64::{GPR, REGS, SYSV};
use super::*;
use crate::frame::Layout;
fn three(named: &[(u32, u32)]) -> (Func, Vec<Inst>) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
func.build(block, opcode).def(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).uses(first, GPR).finish();
func.named = named.iter().map(|&(decl, reg)| (decl, Reg::virtual_reg(reg))).collect();
let line = before(&func);
(func, line)
}
fn about(func: &mut Func, line: &[Inst]) -> Vec<Kept> {
let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
let allocation = rucc_regalloc::run(func, &env, "test", true);
let frame = Frame::of(func, &allocation, &Layout::new(&SYSV, REGS));
of(func, line, &allocation, &frame)
}
#[test]
fn a_register_holding_a_local_says_so_from_the_instruction_after_the_one_that_wrote_it() {
let (mut func, line) = three(&[(41, 0)]);
let kept = about(&mut func, &line);
assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
assert_eq!(kept[0].decl, 41);
assert_eq!(kept[0].from, line[1], "from the instruction after the one that wrote it");
assert_eq!(kept[0].to, line[2], "to the last one that reads it");
assert!(matches!(kept[0].at, Where::Reg { .. }), "in a register: {:?}", kept[0].at);
}
#[test]
fn a_value_nothing_reads_is_nowhere_worth_saying() {
let (mut func, line) = three(&[(41, 1)]);
let kept = about(&mut func, &line);
assert!(kept.is_empty(), "nothing to say: {kept:?}");
}
#[test]
fn a_declaration_two_registers_hold_gets_a_stretch_for_each_of_them() {
let (mut func, line) = three(&[(41, 0), (41, 1)]);
let kept = about(&mut func, &line);
assert_eq!(kept.iter().map(|kept| kept.decl).collect::<Vec<u32>>(), vec![41]);
}
#[test]
fn a_local_live_from_one_block_into_the_next_gets_a_stretch_in_each_of_them() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let head = func.create_block();
let tail = func.create_block();
let value = func.new_vreg(GPR);
func.build(head, opcode).def(value, GPR).finish();
let across = func.build(head, opcode).finish();
*func.succs_mut(head) = vec![BlockCall::to(tail)];
let read = func.build(tail, opcode).uses(value, GPR).finish();
func.named = vec![(41, value)];
let line = before(&func);
let kept = about(&mut func, &line);
assert_eq!(kept.len(), 2, "one stretch per block: {kept:?}");
assert_eq!((kept[0].from, kept[0].to), (across, across), "the rest of the first block");
assert_eq!((kept[1].from, kept[1].to), (read, read), "and into the second");
}
#[test]
fn a_function_the_front_end_named_nothing_in_says_nothing() {
let (mut func, line) = three(&[]);
let kept = about(&mut func, &line);
assert!(kept.is_empty(), "nothing to say: {kept:?}");
}
#[test]
fn a_function_whose_instructions_moved_inside_a_block_after_allocation_says_nothing() {
let (mut func, line) = three(&[(41, 0)]);
let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
assert!(!of(&func, &line, &allocation, &frame).is_empty(), "something to say first");
func.remove_inst(line[0]);
func.insert_after(line[1], line[0]);
assert!(of(&func, &line, &allocation, &frame).is_empty(), "no longer the order");
}
}