use rucc_mir::{Constraint, Func, Inst, Operand, Reg, Role};
use rucc_target::{PhysReg, RegClass};
use crate::assign::{Assignment, Env, Place};
use crate::moves::Move;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Legal {
pub operands: Vec<Operand>,
pub before: Vec<(Move<Place>, RegClass)>,
pub after: Vec<(Move<Place>, RegClass)>,
}
#[must_use]
pub fn instruction(
func: &Func,
assignment: &mut Assignment,
env: &Env,
spare: &mut Spare,
inst: Inst,
) -> Legal {
let list = func[inst].operands;
let mut operands: Vec<Operand> = func[list].to_vec();
let mut before = Moves::new();
let mut after = Moves::new();
let mut taken = Taken::new();
let places: Vec<Place> =
operands.iter().map(|operand| place(assignment, operand.reg)).collect();
let mut reusing: Vec<usize> = Vec::new();
let mut claimed = Claimed::default();
for (operand, place) in operands.iter().zip(&places) {
if let Place::Reg(at) = *place {
claimed.named(operand, at);
}
if let Constraint::Fixed(at) = operand.constraint {
claimed.named(operand, at);
}
}
let mut scratch = Scratch::new(env, assignment, spare, claimed);
for (index, operand) in operands.iter_mut().enumerate() {
let fixed = match operand.constraint {
Constraint::Fixed(at) => Some(at),
_ => None,
};
let at = match (place(scratch.assignment, operand.reg), fixed) {
(Place::Reg(at), None) => at,
(Place::Reg(at), Some(fixed)) => {
if at != fixed {
let (there, here) = (Place::Reg(fixed), Place::Reg(at));
push(&mut before, &mut after, operand, Move::new(there, here));
}
fixed
}
(Place::Slot(_), None) if matches!(operand.constraint, Constraint::Reuse(_)) => {
reusing.push(index);
continue;
}
(Place::Slot(slot), fixed) => {
let at = match fixed {
Some(fixed) => fixed,
None if operand.role.is_def() => {
taken.written_into(operand.class, &mut scratch)
}
None => taken.read_into(operand.class, &mut scratch),
};
push(
&mut before,
&mut after,
operand,
Move::new(Place::Reg(at), Place::Slot(slot)),
);
at
}
};
operand.reg = Reg::physical(at);
}
for index in reusing {
let Constraint::Reuse(other) = operands[index].constraint else {
unreachable!("only an operand that reuses another was left for this pass")
};
let Place::Slot(slot) = places[index] else {
unreachable!("only a spilled operand was left for this pass")
};
let other = usize::from(other);
let at = match places[other] {
Place::Slot(_) => phys(operands[other].reg),
Place::Reg(_) => taken.read_into(operands[index].class, &mut scratch),
};
push(
&mut before,
&mut after,
&operands[index],
Move::new(Place::Reg(at), Place::Slot(slot)),
);
operands[index].reg = Reg::physical(at);
}
for index in 0..operands.len() {
let Constraint::Reuse(other) = operands[index].constraint else { continue };
let (to, from) = (operands[index], operands[usize::from(other)]);
if to.reg != from.reg {
let mov = Move::new(Place::Reg(phys(to.reg)), Place::Reg(phys(from.reg)));
before.push((mov, to.class));
}
}
let (saves, restores) = scratch.finish();
let mut first = saves;
first.extend(before);
let mut last = after;
last.extend(restores);
Legal { operands, before: first, after: last }
}
#[derive(Debug, Default)]
struct Taken {
read: Vec<usize>,
written: Vec<usize>,
}
impl Taken {
fn new() -> Self {
Self::default()
}
fn read_into(&mut self, class: RegClass, scratch: &mut Scratch<'_>) -> PhysReg {
Self::take(&mut self.read, class, scratch, Role::Use)
}
fn written_into(&mut self, class: RegClass, scratch: &mut Scratch<'_>) -> PhysReg {
Self::take(&mut self.written, class, scratch, Role::Def)
}
fn take(
counts: &mut Vec<usize>,
class: RegClass,
scratch: &mut Scratch<'_>,
role: Role,
) -> PhysReg {
let index = usize::from(class.number());
if counts.len() <= index {
counts.resize(index + 1, 0);
}
let held: &[PhysReg] = scratch.env.scratch(class);
while held.get(counts[index]).is_some_and(|®| scratch.claimed.clashes(role, class, reg))
{
counts[index] += 1;
}
if let Some(&at) = held.get(counts[index]) {
counts[index] += 1;
return at;
}
scratch.borrow(class)
}
}
#[derive(Debug, Default)]
struct Claimed {
reads: Vec<(RegClass, PhysReg)>,
writes: Vec<(RegClass, PhysReg)>,
}
impl Claimed {
fn named(&mut self, operand: &Operand, at: PhysReg) {
self.side_mut(operand.role).push((operand.class, at));
}
fn taken(&mut self, class: RegClass, at: PhysReg) {
self.reads.push((class, at));
self.writes.push((class, at));
}
fn clashes(&self, role: Role, class: RegClass, at: PhysReg) -> bool {
self.side(role).contains(&(class, at))
}
fn names(&self, class: RegClass, at: PhysReg) -> bool {
self.reads.contains(&(class, at)) || self.writes.contains(&(class, at))
}
fn side(&self, role: Role) -> &Vec<(RegClass, PhysReg)> {
if role.is_def() { &self.writes } else { &self.reads }
}
fn side_mut(&mut self, role: Role) -> &mut Vec<(RegClass, PhysReg)> {
if role.is_def() { &mut self.writes } else { &mut self.reads }
}
}
type Moves = Vec<(Move<Place>, RegClass)>;
pub type Spare = Vec<Vec<u32>>;
struct Scratch<'a> {
env: &'a Env,
assignment: &'a mut Assignment,
spare: &'a mut Spare,
claimed: Claimed,
borrowed: Vec<usize>,
saves: Moves,
restores: Moves,
}
impl<'a> Scratch<'a> {
fn new(
env: &'a Env,
assignment: &'a mut Assignment,
spare: &'a mut Spare,
claimed: Claimed,
) -> Self {
Self {
env,
assignment,
spare,
claimed,
borrowed: Vec::new(),
saves: Vec::new(),
restores: Vec::new(),
}
}
fn borrow(&mut self, class: RegClass) -> PhysReg {
let index = usize::from(class.number());
let at = *self
.env
.order(class)
.iter()
.find(|&®| !self.claimed.names(class, reg))
.expect("an instruction naming every register of its class at once");
if self.borrowed.len() <= index {
self.borrowed.resize(index + 1, 0);
}
if self.spare.len() <= index {
self.spare.resize(index + 1, Vec::new());
}
let nth = self.borrowed[index];
if self.spare[index].len() <= nth {
let slot = self.assignment.take_slot(class);
self.spare[index].push(slot);
}
let slot = self.spare[index][nth];
self.borrowed[index] = nth + 1;
self.claimed.taken(class, at);
self.saves.push((Move::new(Place::Slot(slot), Place::Reg(at)), class));
self.restores.push((Move::new(Place::Reg(at), Place::Slot(slot)), class));
at
}
fn finish(self) -> (Moves, Moves) {
(self.saves, self.restores)
}
}
fn push(before: &mut Moves, after: &mut Moves, operand: &Operand, mov: Move<Place>) {
if operand.role.is_def() {
after.push((Move::new(mov.from, mov.to), operand.class));
} else {
before.push((mov, operand.class));
}
}
pub(crate) fn place(assignment: &Assignment, reg: Reg) -> Place {
assignment.place(reg).unwrap_or_else(|| Place::Reg(phys(reg)))
}
pub(crate) fn phys(reg: Reg) -> PhysReg {
reg.phys().expect("a register the assignment says nothing about and that is not a register")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::Opcode;
use rucc_target::x86_64::{GPR, RAX, RCX, SYSV};
use super::*;
fn env() -> Env {
Env::new().with(GPR, &SYSV.int_order[..2], &SYSV.int_order[2..4])
}
fn func() -> (Func, Opcode, rucc_mir::Block) {
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();
(func, opcode, block)
}
fn legal(func: &Func, assignment: &mut Assignment, inst: Inst) -> Legal {
instruction(func, assignment, &env(), &mut Spare::new(), inst)
}
fn regs(legal: &Legal) -> Vec<PhysReg> {
legal.operands.iter().map(|operand| phys(operand.reg)).collect()
}
#[test]
fn an_instruction_whose_values_are_where_it_wants_them_needs_nothing() {
let (mut func, opcode, block) = func();
let value = func.new_vreg(GPR);
let inst = func.build(block, opcode).uses(value, GPR).finish();
let mut assignment = Assignment::empty(func.vregs());
assignment.put(value, Place::Reg(RCX));
let legal = legal(&func, &mut assignment, inst);
assert_eq!(regs(&legal), [RCX]);
assert!(legal.before.is_empty() && legal.after.is_empty());
}
#[test]
fn a_register_the_instruction_insists_on_is_filled_in_front_of_it() {
let (mut func, opcode, block) = func();
let value = func.new_vreg(GPR);
let inst = func
.build(block, opcode)
.operand(Operand::read(value, GPR).with(Constraint::Fixed(RAX)))
.finish();
let mut assignment = Assignment::empty(func.vregs());
assignment.put(value, Place::Reg(RCX));
let legal = legal(&func, &mut assignment, inst);
assert_eq!(regs(&legal), [RAX]);
assert_eq!(legal.before, [(Move::new(Place::Reg(RAX), Place::Reg(RCX)), GPR)]);
assert!(legal.after.is_empty());
}
#[test]
fn an_answer_written_where_it_does_not_live_is_taken_away_behind_it() {
let (mut func, opcode, block) = func();
let value = func.new_vreg(GPR);
let inst = func
.build(block, opcode)
.operand(Operand::write(value, GPR).with(Constraint::Fixed(RAX)))
.finish();
let mut assignment = Assignment::empty(func.vregs());
assignment.put(value, Place::Reg(RCX));
let legal = legal(&func, &mut assignment, inst);
assert_eq!(regs(&legal), [RAX]);
assert!(legal.before.is_empty());
assert_eq!(legal.after, [(Move::new(Place::Reg(RCX), Place::Reg(RAX)), GPR)]);
}
#[test]
fn a_value_on_the_stack_is_read_into_a_register_held_back() {
let (mut func, opcode, block) = func();
let value = func.new_vreg(GPR);
let inst = func.build(block, opcode).uses(value, GPR).finish();
let mut assignment = Assignment::empty(func.vregs());
let slot = assignment.take_slot(GPR);
assignment.put(value, Place::Slot(slot));
let legal = legal(&func, &mut assignment, inst);
let scratch = env().scratch(GPR)[0];
assert_eq!(regs(&legal), [scratch]);
assert_eq!(legal.before, [(Move::new(Place::Reg(scratch), Place::Slot(slot)), GPR)]);
}
#[test]
fn a_two_address_answer_apart_from_its_source_is_copied_into_first() {
let (mut func, opcode, block) = func();
let source = func.new_vreg(GPR);
let answer = func.new_vreg(GPR);
let inst = func
.build(block, opcode)
.operand(Operand::write(answer, GPR).with(Constraint::Reuse(1)))
.uses(source, GPR)
.finish();
let mut assignment = Assignment::empty(func.vregs());
assignment.put(source, Place::Reg(RCX));
assignment.put(answer, Place::Reg(RAX));
let legal = legal(&func, &mut assignment, inst);
assert_eq!(regs(&legal), [RAX, RCX]);
assert_eq!(legal.before, [(Move::new(Place::Reg(RAX), Place::Reg(RCX)), GPR)]);
}
#[test]
fn a_third_value_on_the_stack_borrows_a_register_and_gives_it_back() {
let (mut func, opcode, block) = func();
let values: Vec<Reg> = (0..3).map(|_| func.new_vreg(GPR)).collect();
let build = func.build(block, opcode);
let inst = values.iter().fold(build, |build, &value| build.uses(value, GPR)).finish();
let mut assignment = Assignment::empty(func.vregs());
for &value in &values {
let slot = assignment.take_slot(GPR);
assignment.put(value, Place::Slot(slot));
}
let legal = legal(&func, &mut assignment, inst);
let borrowed = regs(&legal)[2];
assert!(!env().scratch(GPR).contains(&borrowed));
let spare = Place::Slot(3);
assert_eq!(legal.before[0], (Move::new(spare, Place::Reg(borrowed)), GPR));
assert_eq!(legal.after.last(), Some(&(Move::new(Place::Reg(borrowed), spare), GPR)));
assert_eq!(assignment.spilled(), 4);
}
}