use rucc_mir::{Constraint, Func, Reg};
use rucc_target::{PhysReg, RegClass};
use crate::live::{Live, Range};
use crate::order::{Order, Point};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Place {
Reg(PhysReg),
Slot(u32),
}
#[derive(Debug, Clone, Default)]
pub struct Env {
classes: Vec<Class>,
}
#[derive(Debug, Clone, Default)]
struct Class {
order: Vec<PhysReg>,
scratch: Vec<PhysReg>,
}
impl Env {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with(mut self, class: RegClass, order: &[PhysReg], scratch: &[PhysReg]) -> Self {
let index = usize::from(class.number());
if self.classes.len() <= index {
self.classes.resize(index + 1, Class::default());
}
self.classes[index] = Class { order: order.to_vec(), scratch: scratch.to_vec() };
self
}
#[must_use]
pub fn order(&self, class: RegClass) -> &[PhysReg] {
self.classes.get(usize::from(class.number())).map_or(&[], |class| &class.order)
}
#[must_use]
pub fn scratch(&self, class: RegClass) -> &[PhysReg] {
self.classes.get(usize::from(class.number())).map_or(&[], |class| &class.scratch)
}
}
#[derive(Debug, Clone)]
pub struct Assignment {
places: Vec<Option<Place>>,
slots: Vec<RegClass>,
}
impl Assignment {
#[must_use]
pub fn empty(vregs: usize) -> Self {
Self { places: vec![None; vregs], slots: Vec::new() }
}
pub fn put(&mut self, reg: Reg, place: Place) {
self.places[index(reg)] = Some(place);
}
pub fn take_slot(&mut self, class: RegClass) -> u32 {
let slot = u32::try_from(self.slots.len()).expect("too many spilled values");
self.slots.push(class);
slot
}
#[must_use]
pub fn place(&self, reg: Reg) -> Option<Place> {
self.places.get(usize::try_from(reg.number()?).ok()?).copied().flatten()
}
#[must_use]
pub fn slots(&self) -> &[RegClass] {
&self.slots
}
#[must_use]
pub fn spilled(&self) -> usize {
self.slots.len()
}
fn spill(&mut self, reg: Reg, class: RegClass) {
let slot = self.take_slot(class);
self.put(reg, Place::Slot(slot));
}
}
#[derive(Debug, Clone, Copy)]
struct Interval {
reg: Reg,
class: RegClass,
range: Range,
}
#[derive(Debug, Clone, Copy)]
struct Held {
reg: Reg,
class: RegClass,
range: Range,
at: PhysReg,
}
#[derive(Debug, Clone, Copy)]
struct Blocked {
class: RegClass,
at: PhysReg,
range: Range,
}
#[derive(Debug, Clone, Copy)]
struct Reuse {
source: Reg,
at: Point,
}
#[must_use]
pub fn assign(func: &Func, order: &Order, live: &Live, env: &Env) -> Assignment {
let blocked = blocked(func, order);
let forced = forced(func);
let reuses = reuses(func, order);
let mut intervals = Vec::with_capacity(func.vregs());
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;
};
if let Some(reuse) = reuse {
range.start = range.start.min(reuse.at);
}
intervals.push(Interval { reg, class, range });
}
intervals.sort_by_key(|interval| (interval.range.start, interval.reg));
let mut assignment = Assignment::empty(func.vregs());
let mut active: Vec<Held> = Vec::new();
for interval in intervals {
active.retain(|held| held.range.end >= interval.range.start);
if forced.contains(&interval.reg) {
assignment.spill(interval.reg, interval.class);
continue;
}
assert!(
!env.order(interval.class).is_empty(),
"a value in a class the target hands out no registers from"
);
let two_address = reuses[index(interval.reg)]
.and_then(|reuse| coalesce(&assignment, &active, &blocked, interval, reuse));
let chosen = two_address.or_else(|| {
env.order(interval.class)
.iter()
.copied()
.find(|&at| available(&active, &blocked, interval, at, None))
});
match chosen {
Some(at) => {
assignment.places[index(interval.reg)] = Some(Place::Reg(at));
let reg = interval.reg;
active.push(Held { reg, class: interval.class, range: interval.range, at });
}
None => spill_one(&mut assignment, &mut active, &blocked, interval),
}
}
assignment
}
fn available(
active: &[Held],
blocked: &[Blocked],
interval: Interval,
at: PhysReg,
except: Option<Reg>,
) -> bool {
let taken = active
.iter()
.any(|held| held.at == at && held.class == interval.class && Some(held.reg) != except);
let insisted = blocked.iter().any(|one| {
one.at == at && one.class == interval.class && one.range.overlaps(interval.range)
});
!taken && !insisted
}
fn coalesce(
assignment: &Assignment,
active: &[Held],
blocked: &[Blocked],
interval: Interval,
reuse: Reuse,
) -> Option<PhysReg> {
let Some(Place::Reg(at)) = assignment.place(reuse.source) else { return None };
let source = active.iter().find(|held| held.reg == reuse.source)?;
let dies = source.range.end == reuse.at;
(dies && available(active, blocked, interval, at, Some(reuse.source))).then_some(at)
}
fn spill_one(
assignment: &mut Assignment,
active: &mut Vec<Held>,
blocked: &[Blocked],
interval: Interval,
) {
let victim = active
.iter()
.enumerate()
.filter(|(_, held)| held.class == interval.class)
.filter(|(_, held)| available(&[], blocked, interval, held.at, None))
.max_by_key(|(_, held)| held.range.end)
.map(|(at, held)| (at, held.at, held.range.end));
match victim {
Some((victim, at, end)) if end > interval.range.end => {
let held = active.remove(victim);
assignment.spill(held.reg, held.class);
assignment.places[index(interval.reg)] = Some(Place::Reg(at));
let reg = interval.reg;
active.push(Held { reg, class: interval.class, range: interval.range, at });
}
_ => assignment.spill(interval.reg, interval.class),
}
}
fn blocked(func: &Func, order: &Order) -> Vec<Blocked> {
let mut blocked = Vec::new();
for block in func.blocks() {
for inst in func.insts(block) {
let range = Range { start: order.early(inst), end: order.late(inst) };
for operand in &func[func[inst].operands] {
let at = match operand.constraint {
Constraint::Fixed(at) => Some(at),
_ => operand.reg.phys(),
};
if let Some(at) = at {
blocked.push(Blocked { class: operand.class, at, range });
}
}
}
}
blocked
}
fn forced(func: &Func) -> Vec<Reg> {
let mut forced = Vec::new();
for block in func.blocks() {
for inst in func.insts(block) {
for operand in &func[func[inst].operands] {
if operand.constraint == Constraint::Stack
&& operand.reg.is_virtual()
&& !forced.contains(&operand.reg)
{
forced.push(operand.reg);
}
}
}
}
forced
}
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 {
usize::try_from(reg.number().expect("a virtual register")).expect("a register number")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{BlockCall, Opcode, Operand};
use rucc_target::x86_64::{GPR, R13, R14, R15, RAX, RCX, RDX, REGS, SYSV};
use super::*;
fn env() -> Env {
let (order, scratch) = SYSV.int_order.split_at(SYSV.int_order.len() - 3);
Env::new().with(GPR, order, scratch)
}
fn narrow(count: usize) -> Env {
Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 1])
}
fn named(place: Option<Place>) -> String {
match place {
Some(Place::Reg(reg)) => REGS.name(GPR, reg).expect("a register").to_string(),
Some(Place::Slot(slot)) => format!("slot {slot}"),
None => "nowhere".to_string(),
}
}
fn places(func: &Func, env: &Env) -> Vec<String> {
let order = Order::of(func);
let live = Live::of(func, &order);
let assignment = assign(func, &order, &live, env);
(0..func.vregs())
.map(|number| {
let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
named(assignment.place(reg))
})
.collect()
}
#[test]
fn two_values_that_are_never_both_wanted_share_a_register() {
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();
assert_eq!(places(&func, &env()), ["rax", "rax"]);
}
#[test]
fn two_values_that_are_both_wanted_do_not() {
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.build(block, opcode).uses(second, GPR).finish();
assert_eq!(places(&func, &env()), ["rax", "rcx"]);
}
#[test]
fn the_value_wanted_longest_is_the_one_that_goes_to_the_stack() {
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 long = func.new_vreg(GPR);
let short = func.new_vreg(GPR);
let third = func.new_vreg(GPR);
func.build(block, opcode).def(long, GPR).finish();
func.build(block, opcode).def(short, GPR).finish();
func.build(block, opcode).def(third, GPR).finish();
func.build(block, opcode).uses(short, GPR).finish();
func.build(block, opcode).uses(third, GPR).finish();
func.build(block, opcode).uses(long, GPR).finish();
assert_eq!(places(&func, &narrow(2)), ["slot 0", "rcx", "rax"]);
}
#[test]
fn a_register_an_instruction_insists_on_is_left_alone_over_that_instruction() {
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 across = func.new_vreg(GPR);
let dividend = func.new_vreg(GPR);
let quotient = func.new_vreg(GPR);
let remainder = func.new_vreg(GPR);
func.build(block, opcode).def(across, GPR).finish();
func.build(block, opcode).def(dividend, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
.operand(Operand::write_early(remainder, GPR).with(Constraint::Fixed(RDX)))
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(block, opcode).uses(across, GPR).finish();
assert_eq!(places(&func, &env()), ["rcx", "rsi", "rsi", "rdi"]);
}
#[test]
fn a_value_an_instruction_can_only_read_from_memory_is_on_the_stack() {
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 value = func.new_vreg(GPR);
func.build(block, opcode).def(value, GPR).finish();
func.build(block, opcode)
.operand(Operand::read(value, GPR).with(Constraint::Stack))
.finish();
assert_eq!(places(&func, &env()), ["slot 0"]);
}
#[test]
fn a_two_address_instruction_writes_the_register_it_read_when_it_can() {
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 left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
func.build(block, opcode).def(left, GPR).finish();
func.build(block, opcode).def(right, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, opcode).uses(right, GPR).finish();
assert_eq!(places(&func, &env()), ["rax", "rcx", "rax"]);
}
#[test]
fn a_two_address_instruction_that_cannot_gets_a_register_nothing_it_reads_is_in() {
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 left = func.new_vreg(GPR);
let right = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
func.build(block, opcode).def(left, GPR).finish();
func.build(block, opcode).def(right, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, opcode).uses(left, GPR).finish();
assert_eq!(places(&func, &env()), ["rax", "rcx", "rdx"]);
}
#[test]
fn a_value_live_across_a_whole_loop_holds_its_register_over_all_of_it() {
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 body = func.create_block();
let carried = func.new_vreg(GPR);
let inside = func.new_vreg(GPR);
func.build(head, opcode).def(carried, GPR).finish();
*func.succs_mut(head) = vec![BlockCall::to(body)];
func.build(body, opcode).def(inside, GPR).finish();
func.build(body, opcode).uses(inside, GPR).uses(carried, GPR).finish();
*func.succs_mut(body) = vec![BlockCall::to(body)];
assert_eq!(places(&func, &env()), ["rax", "rcx"]);
}
#[test]
fn a_frame_says_what_each_of_its_slots_is_for() {
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 = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &narrow(1));
assert_eq!(assignment.spilled(), 1);
assert_eq!(assignment.slots(), [GPR]);
assert_eq!(assignment.place(Reg::physical(RCX)), None);
assert_eq!(env().scratch(GPR), [R13, R14, R15]);
}
}