use std::cmp::Reverse;
use rucc_mir::{Constraint, Flags, Func, Inst, Operand, Reg, Role};
use rucc_target::{PhysReg, RegClass};
use crate::live::{Area, Live, Range};
use crate::order::{Order, Point};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
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)
}
pub(crate) fn offered(&self) -> impl Iterator<Item = &[PhysReg]> + '_ {
self.classes.iter().map(|class| class.order.as_slice())
}
#[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>,
commuted: Vec<Inst>,
}
impl Assignment {
pub(crate) fn commute(&mut self, inst: Inst) {
self.commuted.push(inst);
}
#[must_use]
pub fn empty(vregs: usize) -> Self {
Self { places: vec![None; vregs], slots: Vec::new(), commuted: Vec::new() }
}
#[must_use]
pub fn commuted(&self) -> &[Inst] {
&self.commuted
}
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
}
pub fn placed(&self) -> impl Iterator<Item = (Reg, Place)> + '_ {
self.places.iter().enumerate().filter_map(|(number, place)| {
let number = u32::try_from(number).ok()?;
Some((Reg::virtual_reg(number), (*place)?))
})
}
#[must_use]
pub fn spilled(&self) -> usize {
self.slots.len()
}
pub(crate) 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<'a> {
reg: Reg,
class: RegClass,
range: Range,
area: Area<'a>,
}
#[derive(Debug, Clone, Copy)]
struct Held<'a> {
reg: Reg,
class: RegClass,
range: Range,
area: Area<'a>,
at: PhysReg,
since: usize,
}
#[derive(Default)]
struct Active<'a> {
by: Vec<Vec<Held<'a>>>,
count: usize,
}
impl<'a> Active<'a> {
fn at(&self, at: PhysReg) -> &[Held<'a>] {
self.by.get(usize::from(at.number())).map_or(&[], Vec::as_slice)
}
fn push(&mut self, reg: Reg, class: RegClass, range: Range, area: Area<'a>, at: PhysReg) {
let slot = usize::from(at.number());
if self.by.len() <= slot {
self.by.resize_with(slot + 1, Vec::new);
}
self.by[slot].push(Held { reg, class, range, area, at, since: self.count });
self.count += 1;
}
fn expire(&mut self, point: Point) {
for held in &mut self.by {
held.retain(|held| held.range.end >= point);
}
}
}
#[derive(Debug, Clone, Copy)]
struct Blocked {
class: RegClass,
at: PhysReg,
point: Point,
by: Option<Reg>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Reuse {
pub(crate) source: Reg,
pub(crate) second: Option<Reg>,
pub(crate) at: Point,
pub(crate) inst: Inst,
}
#[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 hints = hints(func);
let passed = passed(func);
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 area), Some(class)) = (live.area(reg), func.class_of(reg)) else {
continue;
};
if let Some(reuse) = reuse {
area = area.with(reuse.at);
}
intervals.push(Interval { reg, class, range: area.hull(), area });
}
intervals.sort_by_key(|interval| (interval.range.start, interval.reg));
let mut assignment = Assignment::empty(func.vregs());
let mut active = Active::default();
for interval in intervals {
active.expire(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 class {}, which the target hands out no registers from",
interval.class.number()
);
let reuse = reuses[index(interval.reg)];
let coalesced = |source| coalesce(&assignment, &active, &blocked, live, interval, source);
let first = reuse.and_then(|reuse| coalesced(reuse.source));
let second = reuse.and_then(|reuse| reuse.second).and_then(coalesced);
let hinted_at = |at: Option<PhysReg>| {
at.is_some_and(|at| {
hints[index(interval.reg)].contains(&at)
|| passed[index(interval.reg)]
.iter()
.any(|¶m| assignment.place(param) == Some(Place::Reg(at)))
})
};
let commute =
second.is_some() && (first.is_none() || hinted_at(second) && !hinted_at(first));
let two_address = if commute { second } else { first };
if let (true, Some(reuse)) = (commute, reuse) {
assignment.commuted.push(reuse.inst);
}
let hinted = hints[index(interval.reg)].iter().copied().find(|&at| {
env.order(interval.class).contains(&at)
&& available(&active, &blocked, interval, at, None, Want::Clear)
});
let scan = |want| {
env.order(interval.class)
.iter()
.copied()
.find(|&at| available(&active, &blocked, interval, at, None, want))
};
let chosen =
two_address.or(hinted).or_else(|| scan(Want::Clear)).or_else(|| scan(Want::Allowed));
match chosen {
Some(at) => {
assignment.places[index(interval.reg)] = Some(Place::Reg(at));
active.push(interval.reg, interval.class, interval.range, interval.area, at);
}
None => spill_one(&mut assignment, &mut active, &blocked, interval),
}
}
assignment
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Want {
Clear,
Allowed,
}
pub(crate) struct Blocks {
all: Vec<Blocked>,
}
impl Blocks {
pub(crate) fn insists(
&self,
reg: Reg,
class: RegClass,
area: Area<'_>,
range: Range,
at: PhysReg,
want: Want,
) -> bool {
self.over(class, at, range)
.any(|one| one.by != Some(reg) && (want == Want::Clear || area.covers(one.point)))
}
pub(crate) fn taken(&self) -> impl Iterator<Item = (RegClass, PhysReg, Point)> + '_ {
self.all.iter().filter(|one| one.by.is_none()).map(|one| (one.class, one.at, one.point))
}
fn over(
&self,
class: RegClass,
at: PhysReg,
range: Range,
) -> impl Iterator<Item = &Blocked> + '_ {
let first = self
.all
.partition_point(|one| (one.class, one.at, one.point) < (class, at, range.start));
self.all[first..]
.iter()
.take_while(move |one| one.class == class && one.at == at && one.point <= range.end)
}
}
fn available(
active: &Active<'_>,
blocked: &Blocks,
interval: Interval<'_>,
at: PhysReg,
except: Option<Reg>,
want: Want,
) -> bool {
let taken = active.at(at).iter().any(|held| {
held.class == interval.class
&& Some(held.reg) != except
&& held.area.overlaps(interval.area)
});
let insisted = blocked.over(interval.class, at, interval.range).any(|one| {
one.by != Some(interval.reg) && (want == Want::Clear || interval.area.covers(one.point))
});
!taken && !insisted
}
fn coalesce(
assignment: &Assignment,
active: &Active<'_>,
blocked: &Blocks,
live: &Live,
interval: Interval<'_>,
source: Reg,
) -> Option<PhysReg> {
let Some(Place::Reg(at)) = assignment.place(source) else { return None };
active.at(at).iter().find(|held| held.reg == source)?;
let free = available(active, blocked, interval, at, Some(source), Want::Allowed);
(apart(live, source, interval.reg) && free).then_some(at)
}
pub(crate) fn apart(live: &Live, first: Reg, second: Reg) -> bool {
match (live.area(first), live.area(second)) {
(Some(first), Some(second)) => !first.overlaps(second),
_ => false,
}
}
fn spill_one<'a>(
assignment: &mut Assignment,
active: &mut Active<'a>,
blocked: &Blocks,
interval: Interval<'a>,
) {
let mut costs: Vec<(usize, PhysReg, usize, Point)> = Vec::new();
for held in active.by.iter().flatten() {
if held.class != interval.class || !held.area.overlaps(interval.area) {
continue;
}
match costs.iter_mut().find(|(_, at, _, _)| *at == held.at) {
Some((first, _, count, reach)) => {
*first = (*first).min(held.since);
*count += 1;
*reach = (*reach).max(held.range.end);
}
None => costs.push((held.since, held.at, 1, held.range.end)),
}
}
costs.sort_unstable_by_key(|&(first, _, _, _)| first);
let none = Active::default();
let chosen = costs
.iter()
.filter(|&&(_, at, _, reach)| {
reach > interval.range.end
&& available(&none, blocked, interval, at, None, Want::Allowed)
})
.min_by_key(|&&(_, _, count, reach)| (count, Reverse(reach)))
.map(|&(_, at, _, _)| at);
match chosen {
Some(at) => {
active.by[usize::from(at.number())].retain(|held| {
let goes = held.class == interval.class && held.area.overlaps(interval.area);
if goes {
assignment.spill(held.reg, held.class);
}
!goes
});
assignment.places[index(interval.reg)] = Some(Place::Reg(at));
active.push(interval.reg, interval.class, interval.range, interval.area, at);
}
None => assignment.spill(interval.reg, interval.class),
}
}
pub(crate) fn blocked(func: &Func, order: &Order) -> Blocks {
let mut blocked = Vec::new();
let mut claimed: Vec<(RegClass, PhysReg)> = Vec::new();
for block in func.blocks() {
for inst in func.insts(block) {
let operands = &func[func[inst].operands];
claimed.clear();
for operand in operands {
if let Some(at) = insisted(operand) {
let key = (operand.class, at);
if !claimed.contains(&key) {
claimed.push(key);
}
}
}
for &(class, at) in &claimed {
for (point, role) in [(order.early(inst), Role::Use), (order.late(inst), Role::Def)]
{
let mut named = false;
for operand in operands {
let mine = insisted(operand) == Some(at) && operand.class == class;
if !mine || !(operand.role == role || operand.role == Role::EarlyDef) {
continue;
}
named = true;
let by = operand.reg.is_virtual().then_some(operand.reg);
blocked.push(Blocked { class, at, point, by });
}
if !named && role == Role::Def {
blocked.push(Blocked { class, at, point, by: None });
}
}
}
}
}
blocked.sort_by_key(|one: &Blocked| (one.class, one.at, one.point));
Blocks { all: blocked }
}
fn insisted(operand: &Operand) -> Option<PhysReg> {
match operand.constraint {
Constraint::Fixed(at) => Some(at),
_ => operand.reg.phys(),
}
}
pub(crate) fn hints(func: &Func) -> Vec<Vec<PhysReg>> {
let mut hints = vec![Vec::new(); func.vregs()];
for block in func.blocks() {
for inst in func.insts(block) {
for operand in &func[func[inst].operands] {
let Constraint::Fixed(at) = operand.constraint else { continue };
let number = operand.reg.number().and_then(|number| usize::try_from(number).ok());
let Some(number) = number else { continue };
let wanted: &mut Vec<PhysReg> = &mut hints[number];
if func.class_of(operand.reg) == Some(operand.class) && !wanted.contains(&at) {
wanted.push(at);
}
}
}
}
hints
}
pub(crate) fn passed(func: &Func) -> Vec<Vec<Reg>> {
let mut passed = vec![Vec::new(); func.vregs()];
for block in func.blocks() {
for call in &func[block].succs {
for (&arg, param) in call.args.iter().zip(&func[call.block].params) {
let number = arg.number().and_then(|number| usize::try_from(number).ok());
let Some(number) = number else { continue };
let to: &mut Vec<Reg> = &mut passed[number];
if !to.contains(¶m.reg) {
to.push(param.reg);
}
}
}
}
passed
}
pub(crate) 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
}
pub(crate) 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;
let second = if func[inst].flags.contains(Flags::COMMUTES) {
swappable(operands, usize::from(other))
} else {
None
};
reuses[number] = Some(Reuse { source, second, at: order.early(inst), inst });
}
}
}
reuses
}
fn swappable(operands: &[Operand], other: usize) -> Option<Reg> {
let [answer, first, second] = operands else { return None };
let same = second.class == first.class && second.class == answer.class;
let plain = second.role == Role::Use && second.constraint == Constraint::Reg;
(other == 1 && same && plain && second.reg.is_virtual() && second.reg != first.reg)
.then_some(second.reg)
}
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, Param};
use rucc_target::x86_64::{GPR, R13, R14, R15, RAX, RCX, RDX, REGS, RSI, 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 a_value_written_early_that_nothing_reads_still_holds_its_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 wanted = func.new_vreg(GPR);
let spare = func.new_vreg(GPR);
func.build(block, opcode)
.def(wanted, GPR)
.operand(Operand::write_early(spare, GPR))
.finish();
func.build(block, opcode).uses(wanted, GPR).finish();
assert_eq!(places(&func, &env()), ["rcx", "rax"]);
}
#[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_goes_to_the_values_that_asked_for_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 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", "rax", "rax", "rdx"]);
}
#[test]
fn a_value_that_dies_at_an_instruction_stays_out_of_what_it_fills_first() {
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 divisor = 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).def(divisor, GPR).finish();
func.build(block, opcode)
.operand(Operand::write_early(remainder, GPR).with(Constraint::Fixed(RDX)))
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.operand(Operand::read(divisor, GPR))
.finish();
func.build(block, opcode).uses(across, GPR).uses(remainder, GPR).finish();
assert_eq!(places(&func, &narrow(4)), ["rcx", "rax", "rsi", "rdx"]);
}
#[test]
fn a_value_wanted_after_the_instruction_that_insists_does_not_get_that_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 dividend = func.new_vreg(GPR);
let quotient = func.new_vreg(GPR);
func.build(block, opcode).def(dividend, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(quotient, GPR).with(Constraint::Fixed(RAX)))
.operand(Operand::read(dividend, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(block, opcode).uses(dividend, GPR).finish();
assert_eq!(places(&func, &env()), ["rcx", "rax"]);
}
#[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 an_answer_that_commutes_goes_over_the_source_that_is_finished_with() {
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();
let add = func
.build(block, opcode)
.flags(Flags::COMMUTES)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, opcode).uses(left, GPR).uses(sum, GPR).finish();
assert_eq!(places(&func, &env()), ["rax", "rcx", "rcx"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &env());
assert_eq!(assignment.commuted(), [add]);
let allocation = crate::run(&mut func, &env(), "f", true);
assert!(allocation.edits.is_empty());
let operands = &func[func[add].operands];
let (first, second) = (operands[1].reg.phys(), operands[2].reg.phys());
assert_eq!((first, second), (Some(RCX), Some(RAX)));
}
#[test]
fn an_answer_that_commutes_takes_the_source_something_after_it_wants() {
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)
.operand(Operand::write(right, GPR).with(Constraint::Fixed(RAX)))
.finish();
let add = func
.build(block, opcode)
.flags(Flags::COMMUTES)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(left, GPR)
.uses(right, GPR)
.finish();
func.build(block, opcode)
.operand(Operand::read(sum, GPR).with(Constraint::Fixed(RAX)))
.finish();
let names = places(&func, &env());
assert_eq!(names[2], "rax");
assert_ne!(names[0], "rax");
let allocation = crate::run(&mut func, &env(), "f", true);
assert!(allocation.edits.is_empty());
assert_eq!(func[func[add].operands][1].reg.phys(), Some(RAX));
}
#[test]
fn an_answer_that_commutes_stays_where_the_loop_passes_it() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let head = func.create_block();
let out = func.create_block();
let seed = func.new_vreg(GPR);
let total = func.new_vreg(GPR);
let term = func.new_vreg(GPR);
let next = func.new_vreg(GPR);
func.build(entry, opcode).def(seed, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::with(head, vec![seed])];
func.params_mut(head).push(Param { reg: total, class: GPR });
func.build(head, opcode).def(term, GPR).finish();
let add = func
.build(head, opcode)
.flags(Flags::COMMUTES)
.operand(Operand::write(next, GPR).with(Constraint::Reuse(1)))
.uses(total, GPR)
.uses(term, GPR)
.finish();
func.build(head, opcode)
.operand(Operand::read(next, GPR).with(Constraint::Fixed(RSI)))
.finish();
*func.succs_mut(head) = vec![BlockCall::with(head, vec![next]), BlockCall::to(out)];
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &env());
assert_eq!(assignment.place(next), assignment.place(total));
assert!(!assignment.commuted().contains(&add));
}
#[test]
fn an_answer_that_does_not_commute_leaves_its_sources_where_they_are() {
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)
.flags(Flags::COMMUTES)
.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"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
assert!(assign(&func, &order, &live, &env()).commuted().is_empty());
}
#[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_two_address_answer_already_live_does_not_take_the_register_it_read() {
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 latch = func.create_block();
let out = func.create_block();
let source = func.new_vreg(GPR);
let carried = func.new_vreg(GPR);
func.build(head, opcode).def(source, GPR).finish();
func.build(head, opcode).def(carried, GPR).finish();
*func.succs_mut(head) = vec![BlockCall::to(latch)];
func.build(latch, opcode)
.operand(Operand::write(carried, GPR).with(Constraint::Reuse(1)))
.uses(source, GPR)
.uses(carried, GPR)
.finish();
*func.succs_mut(latch) = vec![BlockCall::to(head), BlockCall::to(out)];
func.build(out, opcode).uses(carried, GPR).finish();
assert_eq!(places(&func, &env()), ["rax", "rcx"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &env());
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
#[test]
fn a_two_address_answer_with_a_hole_in_front_of_it_does_not_take_its_other_operand() {
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 entry = func.create_block();
let head = func.create_block();
let arm = func.create_block();
let latch = func.create_block();
let out = func.create_block();
let seed = func.new_vreg(GPR);
let sum = func.new_vreg(GPR);
let inside = func.new_vreg(GPR);
let loaded = func.new_vreg(GPR);
func.build(entry, nop).def(seed, GPR).finish();
func.build(entry, nop).def(sum, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::to(head)];
func.build(head, nop).uses(sum, GPR).finish();
*func.succs_mut(head) = vec![BlockCall::to(arm), BlockCall::to(latch)];
func.build(arm, nop).def(inside, GPR).finish();
func.build(arm, nop).uses(inside, GPR).finish();
*func.succs_mut(arm) = vec![BlockCall::to(out)];
func.build(latch, nop).def(loaded, GPR).finish();
func.build(latch, add)
.operand(Operand::write(sum, GPR).with(Constraint::Reuse(1)))
.uses(seed, GPR)
.uses(loaded, GPR)
.finish();
*func.succs_mut(latch) = vec![BlockCall::to(head), BlockCall::to(out)];
let places = places(&func, &env());
assert_ne!(places[index(sum)], places[index(loaded)]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &env());
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
#[test]
fn a_sum_a_loop_carries_round_keeps_its_register_past_an_arm_laid_out_after_it() {
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 entry = func.create_block();
let head = func.create_block();
let join = func.create_block();
let arm = func.create_block();
let out = func.create_block();
let seed = func.new_vreg(GPR);
let term = func.new_vreg(GPR);
let next = func.new_vreg(GPR);
func.build(entry, nop).def(seed, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::with(head, vec![seed])];
let total = func.append_param(head, GPR);
func.build(head, nop).def(term, GPR).finish();
*func.succs_mut(head) = vec![BlockCall::to(join), BlockCall::to(arm)];
func.build(join, add)
.operand(Operand::write(next, GPR).with(Constraint::Reuse(1)))
.uses(total, GPR)
.uses(term, GPR)
.finish();
*func.succs_mut(join) =
vec![BlockCall::with(head, vec![next]), BlockCall::with(out, vec![next])];
func.build(arm, nop).def(term, GPR).finish();
*func.succs_mut(arm) = vec![BlockCall::to(join)];
let result = func.append_param(out, GPR);
func.build(out, nop).uses(result, GPR).finish();
let places = places(&func, &env());
assert_eq!(places[index(next)], places[index(total)]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &env());
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
fn arms(reaches: bool) -> Func {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let arm = func.create_block();
let tail = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
func.build(entry, opcode).def(first, GPR).finish();
func.build(entry, opcode).def(second, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::to(arm), BlockCall::to(tail)];
func.build(arm, opcode).operand(Operand::write(Reg::physical(RAX), GPR)).finish();
*func.succs_mut(arm) = if reaches { vec![BlockCall::to(tail)] } else { Vec::new() };
func.build(tail, opcode).uses(first, GPR).uses(second, GPR).finish();
func
}
#[test]
fn a_register_a_clobber_takes_beats_the_stack_for_a_value_not_live_in_that_block() {
let func = arms(false);
assert_eq!(places(&func, &narrow(2)), ["rcx", "rax"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &narrow(2));
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
#[test]
fn a_register_a_clobber_takes_is_not_free_to_a_value_that_is_live_there() {
let func = arms(true);
assert_eq!(places(&func, &narrow(2)), ["rcx", "slot 0"]);
}
fn dies_at_the_clobber(here: bool) -> Func {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let value = func.new_vreg(GPR);
func.build(entry, opcode).def(value, GPR).finish();
let call = func.build(entry, opcode).operand(Operand::write(Reg::physical(RAX), GPR));
if here {
call.uses(value, GPR).finish();
} else {
call.finish();
func.build(entry, opcode).uses(value, GPR).finish();
}
func
}
#[test]
fn a_value_that_dies_where_a_register_is_destroyed_may_be_in_that_register() {
let func = dies_at_the_clobber(true);
assert_eq!(places(&func, &narrow(1)), ["rax"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &narrow(1));
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
#[test]
fn a_value_read_after_the_instruction_that_destroys_a_register_is_not_in_it() {
let func = dies_at_the_clobber(false);
assert_eq!(places(&func, &narrow(1)), ["slot 0"]);
}
#[test]
fn a_hint_is_followed_when_the_register_is_clear_and_not_when_it_is_merely_allowed() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let mid = func.create_block();
let tail = func.create_block();
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
func.build(entry, opcode).def(first, GPR).finish();
func.build(entry, opcode).def(second, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::to(mid), BlockCall::to(tail)];
func.build(mid, opcode)
.operand(Operand::read(second, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(tail, opcode)
.operand(Operand::read(first, GPR).with(Constraint::Fixed(RAX)))
.finish();
assert_eq!(places(&func, &env()), ["rcx", "rax"]);
}
#[test]
fn a_value_living_in_a_hole_of_another_gets_the_same_register() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let entry = func.create_block();
let arm = func.create_block();
let tail = func.create_block();
let across = func.new_vreg(GPR);
let inside = func.new_vreg(GPR);
func.build(entry, opcode).def(across, GPR).finish();
*func.succs_mut(entry) = vec![BlockCall::to(arm), BlockCall::to(tail)];
func.build(arm, opcode).def(inside, GPR).finish();
func.build(arm, opcode).uses(inside, GPR).finish();
func.build(tail, opcode).uses(across, GPR).finish();
assert_eq!(places(&func, &narrow(1)), ["rax", "rax"]);
let order = Order::of(&func);
let live = Live::of(&func, &order);
let assignment = assign(&func, &order, &live, &narrow(1));
assert_eq!(assignment.spilled(), 0);
assert!(crate::check::check(&func, &order, &live, &assignment).is_empty());
}
#[test]
fn a_register_a_clobber_takes_is_the_last_one_offered_rather_than_the_first() {
let func = arms(false);
assert_eq!(places(&func, &narrow(3)), ["rcx", "rdx"]);
}
#[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]);
}
}