use std::fmt;
use rucc_mir::{Constraint, Func, Inst, Reg, Role};
use rucc_target::{PhysReg, RegClass};
use crate::assign::{Assignment, Place};
use crate::live::{Live, Range};
use crate::order::{Order, Point};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Problem {
Nowhere {
reg: Reg,
},
Shared {
first: Reg,
second: Reg,
place: Place,
},
InTheWay {
reg: Reg,
at: PhysReg,
inst: Inst,
},
NotOnTheStack {
reg: Reg,
inst: Inst,
},
}
impl fmt::Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Problem::Nowhere { reg } => write!(f, "{} has nowhere to live", name(*reg)),
Problem::Shared { first, second, place } => {
let (first, second) = (name(*first), name(*second));
write!(f, "{first} and {second} are both live and both in {}", place_name(*place))
}
Problem::InTheWay { reg, at, inst } => {
let reg = name(*reg);
let inst = inst.index();
write!(f, "{reg} is in register {}, which instruction {inst} wants", at.number())
}
Problem::NotOnTheStack { reg, inst } => {
let reg = name(*reg);
write!(f, "{reg} is not on the stack, and instruction {} needs it", inst.index())
}
}
}
}
#[must_use]
pub fn check(func: &Func, order: &Order, live: &Live, assignment: &Assignment) -> Vec<Problem> {
let mut problems = Vec::new();
let reuses = reuses(func, order);
let mut values = Vec::new();
for (number, reuse) in reuses.iter().enumerate() {
let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
let (Some(mut range), Some(class)) = (live.range(reg), func.class_of(reg)) else {
continue;
};
let Some(place) = assignment.place(reg) else {
problems.push(Problem::Nowhere { reg });
continue;
};
if let Some(reuse) = reuse {
range.start = range.start.min(reuse.at);
}
values.push(Value { reg, class, range, place });
}
overlaps(&values, &reuses, live, &mut problems);
instructions(func, order, assignment, &values, &reuses, &mut problems);
problems
}
#[must_use]
pub fn report(problems: &[Problem]) -> String {
let places = if problems.len() == 1 { "place" } else { "places" };
let mut report = format!("the allocation is wrong in {} {places}", problems.len());
for problem in problems {
report.push_str("\n ");
report.push_str(&problem.to_string());
}
report
}
#[derive(Debug, Clone, Copy)]
struct Value {
reg: Reg,
class: RegClass,
range: Range,
place: Place,
}
#[derive(Debug, Clone, Copy)]
struct Reuse {
source: Reg,
at: Point,
}
fn overlaps(values: &[Value], reuses: &[Option<Reuse>], live: &Live, problems: &mut Vec<Problem>) {
let mut sorted = values.to_vec();
sorted.sort_by_key(|value| (value.range.start, value.reg));
let mut active: Vec<Value> = Vec::new();
for value in sorted {
active.retain(|held| held.range.end >= value.range.start);
for held in &active {
if !together(*held, value) || coalesced(*held, value, reuses, live) {
continue;
}
problems.push(Problem::Shared {
first: held.reg,
second: value.reg,
place: value.place,
});
}
active.push(value);
}
}
fn together(first: Value, second: Value) -> bool {
match (first.place, second.place) {
(Place::Reg(first_at), Place::Reg(second_at)) => {
first_at == second_at && first.class == second.class
}
(Place::Slot(first_slot), Place::Slot(second_slot)) => first_slot == second_slot,
_ => false,
}
}
fn coalesced(first: Value, second: Value, reuses: &[Option<Reuse>], live: &Live) -> bool {
let pair = |source: Value, dest: Value| {
let Some(reuse) = reuses[index(dest.reg)] else { return false };
reuse.source == source.reg && live.range(source.reg).is_some_and(|r| r.end == reuse.at)
};
pair(first, second) || pair(second, first)
}
fn instructions(
func: &Func,
order: &Order,
assignment: &Assignment,
values: &[Value],
reuses: &[Option<Reuse>],
problems: &mut Vec<Problem>,
) {
for block in func.blocks() {
for inst in func.insts(block) {
for operand in &func[func[inst].operands] {
if operand.constraint == Constraint::Stack
&& matches!(assignment.place(operand.reg), Some(Place::Reg(_)))
{
problems.push(Problem::NotOnTheStack { reg: operand.reg, inst });
}
let at = match operand.constraint {
Constraint::Fixed(at) => Some(at),
_ => operand.reg.phys(),
};
let Some(at) = at else { continue };
let early = order.early(inst);
let point = if operand.role == Role::Def { order.late(inst) } else { early };
for value in values {
let mine = value.reg == operand.reg
|| reuses[index(value.reg)].is_some_and(|reuse| {
reuse.source == operand.reg
&& reuse.at == early
&& value.place == Place::Reg(at)
});
if mine || value.class != operand.class {
continue;
}
if value.place == Place::Reg(at) && value.range.covers(point) {
problems.push(Problem::InTheWay { reg: value.reg, at, inst });
}
}
}
}
}
}
fn reuses(func: &Func, order: &Order) -> Vec<Option<Reuse>> {
let mut reuses = vec![None; func.vregs()];
for block in func.blocks() {
for inst in func.insts(block) {
let operands = &func[func[inst].operands];
for operand in operands {
let Constraint::Reuse(other) = operand.constraint else { continue };
let number = operand.reg.number().and_then(|number| usize::try_from(number).ok());
let Some(number) = number else { continue };
let source = operands[usize::from(other)].reg;
reuses[number] = Some(Reuse { source, at: order.early(inst) });
}
}
}
reuses
}
fn index(reg: Reg) -> usize {
reg.number().and_then(|number| usize::try_from(number).ok()).unwrap_or(0)
}
fn name(reg: Reg) -> String {
match reg.number() {
Some(number) => format!("%{number}"),
None => format!("register {}", reg.phys().expect("a physical register").number()),
}
}
fn place_name(place: Place) -> String {
match place {
Place::Reg(at) => format!("register {}", at.number()),
Place::Slot(slot) => format!("slot {slot}"),
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{Opcode, Operand};
use rucc_target::x86_64::{GPR, RAX, RCX, SYSV};
use super::*;
use crate::assign::{Env, assign};
fn env() -> Env {
let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
Env::new().with(GPR, order, scratch)
}
fn allocated(func: &Func) -> Vec<String> {
let order = Order::of(func);
let live = Live::of(func, &order);
let assignment = assign(func, &order, &live, &env());
said(func, &order, &live, &assignment)
}
fn said(func: &Func, order: &Order, live: &Live, assignment: &Assignment) -> Vec<String> {
check(func, order, live, assignment).iter().map(ToString::to_string).collect()
}
fn read(func: &Func) -> (Order, Live) {
let order = Order::of(func);
let live = Live::of(func, &order);
(order, live)
}
#[test]
fn an_allocation_the_allocator_worked_out_has_nothing_wrong_with_it() {
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).uses(second, GPR).finish();
assert_eq!(allocated(&func), Vec::<String>::new());
}
#[test]
fn a_value_with_nowhere_to_live_is_found() {
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 only = func.new_vreg(GPR);
func.build(block, opcode).def(only, GPR).finish();
func.build(block, opcode).uses(only, GPR).finish();
let (order, live) = read(&func);
let assignment = Assignment::empty(func.vregs());
assert_eq!(said(&func, &order, &live, &assignment), ["%0 has nowhere to live"]);
}
#[test]
fn two_values_that_are_both_wanted_and_share_a_register_are_found() {
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).uses(second, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(first, Place::Reg(RAX));
assignment.put(second, Place::Reg(RAX));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%0 and %1 are both live and both in register 0"]);
}
#[test]
fn two_values_that_are_both_wanted_and_share_a_slot_are_found() {
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).uses(second, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
let slot = assignment.take_slot(GPR);
assignment.put(first, Place::Slot(slot));
assignment.put(second, Place::Slot(slot));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%0 and %1 are both live and both in slot 0"]);
}
#[test]
fn two_values_that_are_never_both_wanted_may_share_anything() {
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).uses(first, GPR).finish();
func.build(block, opcode).def(second, GPR).finish();
func.build(block, opcode).uses(second, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(first, Place::Reg(RAX));
assignment.put(second, Place::Reg(RAX));
assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
}
#[test]
fn a_value_left_in_a_register_an_instruction_wants_is_found() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let divide = Opcode::new(names.intern("x64.idiv"));
let block = func.create_block();
let held = func.new_vreg(GPR);
let dividend = func.new_vreg(GPR);
func.build(block, nop).def(held, GPR).finish();
func.build(block, nop).def(dividend, GPR).finish();
func.build(block, divide)
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(block, nop).uses(held, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(held, Place::Reg(RAX));
assignment.put(dividend, Place::Reg(RCX));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%0 is in register 0, which instruction 2 wants"]);
}
#[test]
fn the_value_an_instruction_wants_a_register_for_may_be_in_it_already() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let divide = Opcode::new(names.intern("x64.idiv"));
let block = func.create_block();
let dividend = func.new_vreg(GPR);
func.build(block, nop).def(dividend, GPR).finish();
func.build(block, divide)
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(dividend, Place::Reg(RAX));
assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
}
#[test]
fn a_value_that_can_only_be_read_from_memory_and_is_in_a_register_is_found() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let wide = Opcode::new(names.intern("x64.wide"));
let block = func.create_block();
let only = func.new_vreg(GPR);
func.build(block, nop).def(only, GPR).finish();
func.build(block, wide).operand(Operand::read(only, GPR).with(Constraint::Stack)).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(only, Place::Reg(RAX));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%0 is not on the stack, and instruction 1 needs it"]);
}
#[test]
fn a_two_address_instruction_may_write_the_register_it_read_a_finished_value_from() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let add = Opcode::new(names.intern("x64.add"));
let block = func.create_block();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
func.build(block, nop).def(left, GPR).finish();
func.build(block, nop).def(right, GPR).finish();
func.build(block, add)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, nop).uses(sum, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(left, Place::Reg(RAX));
assignment.put(right, Place::Reg(RCX));
assignment.put(sum, Place::Reg(RAX));
assert_eq!(said(&func, &order, &live, &assignment), Vec::<String>::new());
}
#[test]
fn a_two_address_instruction_may_not_write_over_a_value_wanted_afterwards() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let add = Opcode::new(names.intern("x64.add"));
let block = func.create_block();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
func.build(block, nop).def(left, GPR).finish();
func.build(block, nop).def(right, GPR).finish();
func.build(block, add)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, nop).uses(sum, GPR).uses(left, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(left, Place::Reg(RAX));
assignment.put(right, Place::Reg(RCX));
assignment.put(sum, Place::Reg(RAX));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%0 and %2 are both live and both in register 0"]);
}
#[test]
fn a_two_address_instruction_may_not_write_the_register_it_reads_its_other_operand_from() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let nop = Opcode::new(names.intern("x64.nop"));
let add = Opcode::new(names.intern("x64.add"));
let block = func.create_block();
let left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
func.build(block, nop).def(left, GPR).finish();
func.build(block, nop).def(right, GPR).finish();
func.build(block, add)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, nop).uses(sum, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(left, Place::Reg(RAX));
assignment.put(right, Place::Reg(RCX));
assignment.put(sum, Place::Reg(RCX));
let said = said(&func, &order, &live, &assignment);
assert_eq!(said, ["%1 and %2 are both live and both in register 1"]);
}
#[test]
fn a_report_names_every_problem() {
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).uses(second, GPR).finish();
let (order, live) = read(&func);
let mut assignment = Assignment::empty(func.vregs());
assignment.put(first, Place::Reg(RAX));
assignment.put(second, Place::Reg(RAX));
let problems = check(&func, &order, &live, &assignment);
assert_eq!(
report(&problems),
"the allocation is wrong in 1 place\n %0 and %1 are both live and both in register 0"
);
assert_eq!(report(&[]), "the allocation is wrong in 0 places");
}
}