use std::collections::HashMap;
use rucc_mir::{Block, Func, Inst, Kept, Reg, Where};
use rucc_regalloc::Allocation;
use rucc_regalloc::assign::Place;
use rucc_regalloc::live::Range;
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,
framed: &[(u32, i32, &[Range])],
) -> Vec<Kept> {
if func.named.is_empty() && func.starts.is_empty() && framed.is_empty() {
return Vec::new();
}
let line = line(func, before, allocation);
let mut out = Vec::new();
for &(decl, reg) in &func.named {
let Some(at) = place(func, allocation, frame, reg) else { continue };
let Some(area) = allocation.live.area(reg) else { continue };
let held = |run: &Run, piece: Range| !other(func, decl, reg, run, piece);
over(decl, at, area.pieces(), &line, held, &mut out);
}
for &(decl, at, area) in framed {
over(decl, Where::Frame(at), area.iter().copied(), &line, |_, _| true, &mut out);
}
let mut under: HashMap<Block, Vec<bool>> = HashMap::new();
for &(decl, reg, first) in &func.starts {
let Some(block) = func.block_of(first) else { continue };
let Some(at) = place(func, allocation, frame, reg) else { continue };
let Some(area) = allocation.live.area(reg) else { continue };
let dominated = under.entry(block).or_insert_with(|| dominated(func, block));
for piece in area.pieces() {
for run in line.reached(piece) {
let stretch = if run.block == block {
run.stretch_from(piece, first)
} else if dominated[run.block.index()] && !other(func, decl, reg, run, piece) {
run.stretch(piece)
} else {
None
};
if let Some((from, to)) = stretch {
out.push(Kept { decl, at, from, to });
}
}
}
}
out
}
fn place(func: &Func, allocation: &Allocation, frame: &Frame, reg: Reg) -> Option<Where> {
let class = func.class_of(reg)?;
match allocation.assignment.place(reg)? {
Place::Reg(reg) => Some(Where::Reg { reg, class }),
Place::Slot(slot) => frame.slot_from_frame_base(slot).map(Where::Frame),
}
}
fn dominated(func: &Func, from: Block) -> Vec<bool> {
let reach = |skip: Option<Block>| {
let mut seen = vec![false; func.block_count()];
let mut stack: Vec<Block> =
func.entry().filter(|&entry| Some(entry) != skip).into_iter().collect();
for &block in &stack {
seen[block.index()] = true;
}
while let Some(block) = stack.pop() {
for call in &func[block].succs {
if Some(call.block) != skip && !seen[call.block.index()] {
seen[call.block.index()] = true;
stack.push(call.block);
}
}
}
seen
};
let all = reach(None);
let around = reach(Some(from));
all.iter()
.zip(&around)
.enumerate()
.map(|(index, (&all, &around))| all && !around && index != from.index())
.collect()
}
fn other(func: &Func, decl: u32, reg: Reg, run: &Run, piece: Range) -> bool {
let Some((start, _)) = run.bounds else { return false };
if piece.start > start {
return false;
}
let at = func.entries.partition_point(|&(have, block, _)| (have, block) < (decl, run.block));
func.entries
.get(at)
.is_some_and(|&(have, block, held)| have == decl && block == run.block && held != reg)
}
fn over(
decl: u32,
at: Where,
pieces: impl Iterator<Item = Range>,
line: &Line,
held: impl Fn(&Run, Range) -> bool,
out: &mut Vec<Kept>,
) {
for piece in pieces {
for run in line.reached(piece) {
if !held(run, piece) {
continue;
}
if let Some((from, to)) = run.stretch(piece) {
out.push(Kept { decl, at, from, to });
}
}
}
}
struct Line {
runs: Vec<Run>,
by_start: Vec<(Point, Point, usize)>,
reach: Vec<Point>,
}
impl Line {
fn reached(&self, piece: Range) -> impl Iterator<Item = &Run> {
let first = self.reach.partition_point(|&last| last < piece.start);
let mut found: Vec<usize> = self.by_start[first..]
.iter()
.take_while(|&&(start, _, _)| start <= piece.end)
.map(|&(_, _, index)| index)
.collect();
found.sort_unstable();
found.into_iter().map(|index| &self.runs[index])
}
}
struct Run {
block: Block,
insts: Vec<(Point, Point, Inst)>,
sorted: bool,
bounds: Option<(Point, Point)>,
}
impl Run {
fn extent(&self) -> Option<(Point, Point)> {
match (self.bounds, self.sorted) {
(Some(bounds), _) => Some(bounds),
(None, true) => Some((self.insts.first()?.0, self.insts.last()?.0)),
(None, false) => None,
}
}
fn stretch(&self, piece: Range) -> Option<(Inst, Inst)> {
let (lo, hi) = self.span(piece)?;
Some((self.insts[lo].2, self.insts[hi].2))
}
fn stretch_from(&self, piece: Range, first: Inst) -> Option<(Inst, Inst)> {
let (lo, hi) = self.span(piece)?;
let lo = lo.max(self.insts.iter().position(|&(_, _, inst)| inst == first)?);
(lo <= hi).then(|| (self.insts[lo].2, self.insts[hi].2))
}
fn span(&self, piece: Range) -> Option<(usize, usize)> {
if self.sorted {
let lo = self.insts.partition_point(|&(early, _, _)| early <= piece.start);
let hi = self.insts.partition_point(|&(early, _, _)| early <= piece.end);
return (lo < hi).then(|| (lo, hi - 1));
}
let (start, end) = self.bounds?;
if piece.end < start || piece.start > end {
return None;
}
let at = |point: Point| {
self.insts.iter().position(|&(early, late, _)| early == point || late == point)
};
let lo = if piece.start <= start { 0 } else { at(piece.start)? + 1 };
let hi = if piece.end >= end { self.insts.len().checked_sub(1)? } else { at(piece.end)? };
(lo <= hi).then_some((lo, hi))
}
}
fn line(func: &Func, before: &[Inst], allocation: &Allocation) -> Line {
let order = &allocation.order;
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 insts: Vec<(Point, Point, Inst)> = func
.insts(block)
.filter(|inst| known[inst.index()])
.map(|inst| (order.early(inst), order.late(inst), inst))
.collect();
if insts.is_empty() {
continue;
}
let sorted = insts.windows(2).all(|pair| pair[0].0 < pair[1].0);
out.push(Run { block, insts, sorted, bounds: order.bounds(block) });
}
let mut by_start: Vec<(Point, Point, usize)> = out
.iter()
.enumerate()
.filter_map(|(index, run)| run.extent().map(|(start, end)| (start, end, index)))
.collect();
by_start.sort_unstable();
let reach = by_start
.iter()
.scan(0, |furthest, &(_, end, _)| {
*furthest = end.max(*furthest);
Some(*furthest)
})
.collect();
Line { runs: out, by_start, reach }
}
#[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_block_two_values_of_one_local_come_into_gets_the_one_it_holds_there() {
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 old = func.new_vreg(GPR);
let new = func.new_vreg(GPR);
func.build(head, opcode).def(old, GPR).finish();
func.build(head, opcode).def(new, GPR).finish();
func.build(head, opcode).finish();
*func.succs_mut(head) = vec![BlockCall::to(tail)];
let first = func.build(tail, opcode).uses(old, GPR).finish();
let second = func.build(tail, opcode).uses(new, GPR).finish();
func.named = vec![(41, old), (41, new)];
func.entries = vec![(41, tail, new)];
let line = before(&func);
let kept = about(&mut func, &line);
let into: Vec<(Inst, Inst)> = kept
.iter()
.filter(|kept| func.block_of(kept.from) == Some(tail))
.map(|kept| (kept.from, kept.to))
.collect();
assert_eq!(into, [(first, second)], "{kept:?}");
let before_it = kept.iter().filter(|kept| func.block_of(kept.from) == Some(head)).count();
assert_eq!(before_it, 2, "{kept:?}");
}
#[test]
fn a_local_that_shares_its_frame_bytes_is_there_over_its_area_and_nowhere_else() {
let (mut func, line) = three(&[]);
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));
let order = &allocation.order;
let area = [Range { start: order.early(line[0]), end: order.late(line[1]) }];
let kept = of(&func, &line, &allocation, &frame, &[(41, -24, &area)]);
assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-24), from: line[1], to: line[1] }]);
}
#[test]
fn a_declaration_that_took_a_value_part_of_the_way_through_holds_it_from_there() {
let (mut func, line) = three(&[(41, 0)]);
func.starts = vec![(42, Reg::virtual_reg(0), line[2])];
let kept = about(&mut func, &line);
let said: Vec<(u32, Inst, Inst)> =
kept.iter().map(|kept| (kept.decl, kept.from, kept.to)).collect();
assert_eq!(said, [(41, line[1], line[2]), (42, line[2], line[2])]);
}
#[test]
fn a_declaration_that_took_a_value_holds_it_in_the_blocks_its_own_dominates_only() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let [head, left, below, right, tail] = std::array::from_fn(|_| func.create_block());
let value = func.new_vreg(GPR);
func.build(head, opcode).def(value, GPR).finish();
let first = func.build(left, opcode).uses(value, GPR).finish();
let under = func.build(below, opcode).uses(value, GPR).finish();
func.build(right, opcode).uses(value, GPR).finish();
func.build(tail, opcode).uses(value, GPR).finish();
*func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
*func.succs_mut(left) = vec![BlockCall::to(below)];
*func.succs_mut(below) = vec![BlockCall::to(tail)];
*func.succs_mut(right) = vec![BlockCall::to(tail)];
func.starts = vec![(42, value, first)];
let line = before(&func);
let kept = about(&mut func, &line);
let said: Vec<(Inst, Inst)> = kept.iter().map(|kept| (kept.from, kept.to)).collect();
assert_eq!(said, [(first, first), (under, under)]);
}
#[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_block_the_scheduler_reordered_is_read_by_where_the_two_ends_went() {
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));
func.remove_inst(line[0]);
func.insert_after(line[1], line[0]);
let kept = of(&func, &line, &allocation, &frame, &[]);
assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
assert_eq!((kept[0].from, kept[0].to), (line[2], line[2]));
}
#[test]
fn a_local_live_across_the_whole_of_a_reordered_block_covers_all_of_it() {
let (mut func, line) = three(&[]);
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));
func.remove_inst(line[0]);
func.insert_after(line[1], line[0]);
let block = func.blocks().next().expect("one block");
let (start, end) = allocation.order.bounds(block).expect("laid out");
let area = [Range { start, end }];
let kept = of(&func, &line, &allocation, &frame, &[(41, -8, &area)]);
assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-8), from: line[1], to: line[2] }]);
}
}