use std::fmt::Write as _;
use rucc_regalloc::Allocation;
use rucc_regalloc::assign::Place;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Cost {
pub slots: usize,
pub stores: usize,
pub reloads: usize,
}
impl Cost {
#[must_use]
pub fn of(allocation: &Allocation) -> Self {
let mut cost = Self { slots: allocation.assignment.spilled(), ..Self::default() };
for edit in &allocation.edits {
if matches!(edit.mov.to, Place::Slot(_)) {
cost.stores += 1;
}
if matches!(edit.mov.from, Place::Slot(_)) {
cost.reloads += 1;
}
}
cost
}
fn add(&mut self, other: Self) {
self.slots += other.slots;
self.stores += other.stores;
self.reloads += other.reloads;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Row {
name: String,
cost: Cost,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Pressure {
rows: Vec<Row>,
}
impl Pressure {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, name: &str, cost: Cost) {
self.rows.push(Row { name: name.to_owned(), cost });
}
pub fn merge(&mut self, other: &Self) {
self.rows.extend(other.rows.iter().cloned());
}
#[must_use]
pub fn functions(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn total(&self) -> Cost {
let mut total = Cost::default();
for row in &self.rows {
total.add(row.cost);
}
total
}
#[must_use]
pub fn listing(&self) -> String {
let total = self.total();
let mut out = format!(
"# rucc register pressure: {} functions, {} slots, {} stores, {} reloads\n",
self.rows.len(),
total.slots,
total.stores,
total.reloads
);
for row in &self.rows {
let _ = writeln!(
out,
"{} {} {} {}",
row.cost.slots, row.cost.stores, row.cost.reloads, row.name
);
}
out
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{Func, Opcode};
use rucc_regalloc::assign::{Assignment, Env};
use rucc_regalloc::moves::Move;
use rucc_regalloc::rewrite::{At, Edit};
use rucc_target::x86_64::{GPR, SYSV};
use super::*;
fn allocated(moves: &[(Place, Place)]) -> Allocation {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let block = func.create_block();
let edits = moves
.iter()
.map(|&(to, from)| Edit {
at: At::StartOf(block),
mov: Move::new(to, from),
class: GPR,
})
.collect();
Allocation { assignment: Assignment::empty(0), edits }
}
#[test]
fn a_write_into_a_slot_is_a_store_and_a_read_out_of_one_is_a_reload() {
let reg = Place::Reg(SYSV.int_order[0]);
let cost = Cost::of(&allocated(&[
(Place::Slot(0), reg),
(reg, Place::Slot(0)),
(reg, Place::Slot(0)),
]));
assert_eq!(cost.stores, 1);
assert_eq!(cost.reloads, 2);
}
#[test]
fn a_move_from_one_slot_to_another_is_both() {
let cost = Cost::of(&allocated(&[(Place::Slot(1), Place::Slot(0))]));
assert_eq!(cost.stores, 1);
assert_eq!(cost.reloads, 1);
}
#[test]
fn the_listing_holds_every_function_and_the_totals_are_the_sum_of_them() {
let mut pressure = Pressure::new();
pressure.record("f", Cost { slots: 2, stores: 3, reloads: 4 });
pressure.record("g", Cost::default());
let mut second = Pressure::new();
second.record("h", Cost { slots: 1, stores: 1, reloads: 5 });
pressure.merge(&second);
assert_eq!(pressure.functions(), 3);
assert_eq!(pressure.total(), Cost { slots: 3, stores: 4, reloads: 9 });
let listing = pressure.listing();
let lines: Vec<&str> = listing.lines().collect();
assert_eq!(lines.len(), 4, "{listing}");
assert!(lines[0].contains("3 functions, 3 slots, 4 stores, 9 reloads"), "{}", lines[0]);
assert_eq!(lines[1], "2 3 4 f");
assert_eq!(lines[2], "0 0 0 g");
assert_eq!(lines[3], "1 1 5 h");
}
#[test]
fn a_function_that_runs_out_of_registers_is_recorded_as_having_spilled() {
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);
let third = func.new_vreg(GPR);
func.build(block, opcode).def(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).def(third, GPR).finish();
func.build(block, opcode).uses(first, GPR).uses(second, GPR).uses(third, GPR).finish();
let env = Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..5]);
let cost = Cost::of(&rucc_regalloc::run(&mut func, &env, "f"));
assert_eq!(cost.slots, 1);
assert_eq!(cost.stores, 1);
assert_eq!(cost.reloads, 1);
}
}