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 tree: Option<Tree> = None;
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 tree = tree.get_or_insert_with(|| Tree::of(func));
for piece in area.pieces() {
for run in line.reached(piece) {
let stretch = if run.block == block {
run.stretch_from(piece, first)
} else if tree.strictly(block, run.block) && !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),
}
}
struct Tree {
span: Vec<Option<(usize, usize)>>,
}
impl Tree {
fn of(func: &Func) -> Self {
let count = func.block_count();
let mut order: Vec<Block> = Vec::new();
if let Some(entry) = func.entry() {
let mut seen = vec![false; count];
seen[entry.index()] = true;
let mut stack: Vec<(Block, usize)> = vec![(entry, 0)];
while let Some(top) = stack.last_mut() {
let (block, next) = *top;
if let Some(call) = func[block].succs.get(next) {
top.1 += 1;
if !seen[call.block.index()] {
seen[call.block.index()] = true;
stack.push((call.block, 0));
}
} else {
order.push(block);
stack.pop();
}
}
}
order.reverse();
let mut rank = vec![usize::MAX; count];
for (at, &block) in order.iter().enumerate() {
rank[block.index()] = at;
}
let mut preds: Vec<Vec<usize>> = vec![Vec::new(); order.len()];
for (at, &block) in order.iter().enumerate() {
for call in &func[block].succs {
preds[rank[call.block.index()]].push(at);
}
}
let mut idom = vec![usize::MAX; order.len()];
if let Some(entry) = idom.first_mut() {
*entry = 0;
}
let mut changed = true;
while changed {
changed = false;
for at in 1..order.len() {
let mut new = usize::MAX;
for &pred in &preds[at] {
if idom[pred] == usize::MAX {
continue;
}
new = if new == usize::MAX { pred } else { meet(&idom, pred, new) };
}
if idom[at] != new {
idom[at] = new;
changed = true;
}
}
}
let mut children: Vec<Vec<usize>> = vec![Vec::new(); order.len()];
for (at, &parent) in idom.iter().enumerate().skip(1) {
children[parent].push(at);
}
let mut span = vec![None; count];
let mut enter = vec![0; order.len()];
let mut clock = 0;
let mut stack: Vec<(usize, usize)> =
if order.is_empty() { Vec::new() } else { vec![(0, 0)] };
while let Some(top) = stack.last_mut() {
let (node, next) = *top;
if next == 0 {
enter[node] = clock;
clock += 1;
}
if let Some(&child) = children[node].get(next) {
top.1 += 1;
stack.push((child, 0));
} else {
span[order[node].index()] = Some((enter[node], clock));
clock += 1;
stack.pop();
}
}
Self { span }
}
fn strictly(&self, above: Block, below: Block) -> bool {
match (self.span[above.index()], self.span[below.index()]) {
(Some((in_above, out_above)), Some((in_below, out_below))) => {
in_above < in_below && out_below < out_above
}
_ => false,
}
}
}
fn meet(idom: &[usize], mut one: usize, mut other: usize) -> usize {
while one != other {
while one > other {
one = idom[one];
}
while other > one {
other = idom[other];
}
}
one
}
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] }]);
}
}