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>>>,
soonest: Vec<Point>,
count: usize,
pieces: Vec<Vec<Pieces>>,
costs: Vec<(usize, PhysReg, usize, Point)>,
}
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.soonest.resize(slot + 1, Point::MAX);
}
self.by[slot].push(Held { reg, class, range, area, at, since: self.count });
self.soonest[slot] = self.soonest[slot].min(range.end);
self.count += 1;
let held = &self.by[slot];
let pieces = pieces_mut(&mut self.pieces, class, slot);
if pieces.kept {
pieces.drop_before(range.start);
for piece in area.pieces() {
pieces.insert(piece, reg);
}
} else if held.len() > FEW {
pieces.kept = true;
for held in held.iter().filter(|held| held.class == class) {
for piece in held.area.pieces() {
pieces.insert(piece, held.reg);
}
}
}
}
fn taken(&self, class: RegClass, at: PhysReg, area: Area<'_>, except: Option<Reg>) -> bool {
let held = self.at(at);
if held.len() > FEW {
if let Some(answer) = self.listed(class, at, area, except) {
return answer;
}
}
held.iter()
.any(|held| held.class == class && Some(held.reg) != except && held.area.overlaps(area))
}
#[inline(never)]
fn listed(
&self,
class: RegClass,
at: PhysReg,
area: Area<'_>,
except: Option<Reg>,
) -> Option<bool> {
let by = self.pieces.get(usize::from(class.number()))?;
let pieces = by.get(usize::from(at.number()))?;
(pieces.kept && !pieces.broken).then(|| pieces.touch(area, except))
}
#[inline(never)]
fn owners(&self, class: RegClass, slot: usize, area: Area<'_>) -> Option<Vec<Reg>> {
let pieces = self.pieces.get(usize::from(class.number()))?.get(slot)?;
if pieces.kept { pieces.owners(area) } else { None }
}
fn evict(
&mut self,
class: RegClass,
at: PhysReg,
goes: impl Fn(&Held<'a>) -> bool,
mut gone: impl FnMut(&Held<'a>),
) {
let slot = usize::from(at.number());
let mut taken = Vec::new();
self.by[slot].retain(|held| {
let out = held.class == class && goes(held);
if out {
gone(held);
taken.push((held.reg, held.area));
}
!out
});
let pieces = pieces_mut(&mut self.pieces, class, slot);
if pieces.kept {
for (reg, area) in taken {
for piece in area.pieces() {
pieces.remove(piece, reg);
}
}
}
}
fn expire(&mut self, point: Point) {
for (held, soonest) in self.by.iter_mut().zip(&mut self.soonest) {
if *soonest >= point {
continue;
}
held.retain(|held| held.range.end >= point);
*soonest = held.iter().map(|held| held.range.end).min().unwrap_or(Point::MAX);
}
}
}
pub(crate) const FEW: usize = 4;
fn pieces_mut(pieces: &mut Vec<Vec<Pieces>>, class: RegClass, slot: usize) -> &mut Pieces {
let class = usize::from(class.number());
if pieces.len() <= class {
pieces.resize_with(class + 1, Vec::new);
}
let by = &mut pieces[class];
if by.len() <= slot {
by.resize_with(slot + 1, Pieces::default);
}
&mut by[slot]
}
#[derive(Default)]
pub(crate) struct Pieces {
list: Vec<(Point, Point, Reg)>,
broken: bool,
pub(crate) kept: bool,
}
impl Pieces {
#[inline(never)]
pub(crate) fn insert(&mut self, piece: Range, reg: Reg) {
let key = (piece.start, piece.end);
let at = self.list.partition_point(|&(start, end, _)| (start, end) <= key);
let after = at == 0 || self.list[at - 1].1 <= piece.end;
let before = self.list.get(at).is_none_or(|next| piece.end <= next.1);
if !(after && before) {
self.broken = true;
}
self.list.insert(at, (piece.start, piece.end, reg));
}
pub(crate) fn remove(&mut self, piece: Range, reg: Reg) {
let from = self.list.partition_point(|&(start, _, _)| start < piece.start);
let found = self.list[from..]
.iter()
.take_while(|&&(start, _, _)| start == piece.start)
.position(|&(_, end, owner)| end == piece.end && owner == reg);
if let Some(offset) = found {
self.list.remove(from + offset);
}
}
fn drop_before(&mut self, point: Point) {
if !self.broken {
let gone = self.list.partition_point(|&(_, end, _)| end < point);
self.list.drain(..gone);
}
}
fn touch(&self, area: Area<'_>, except: Option<Reg>) -> bool {
area.pieces().any(|piece| {
let below = self.list.partition_point(|&(start, _, _)| start <= piece.end);
self.list[..below]
.iter()
.rev()
.find(|&&(_, _, owner)| Some(owner) != except)
.is_some_and(|&(_, end, _)| end >= piece.start)
})
}
pub(crate) fn owners(&self, area: Area<'_>) -> Option<Vec<Reg>> {
if self.broken {
return None;
}
let mut owners = Vec::new();
for piece in area.pieces() {
let below = self.list.partition_point(|&(start, _, _)| start <= piece.end);
let touching =
self.list[..below].iter().rev().take_while(|&&(_, end, _)| end >= piece.start);
owners.extend(touching.map(|&(_, _, owner)| owner));
}
owners.sort_unstable();
owners.dedup();
Some(owners)
}
}
#[derive(Debug, Clone, Copy)]
struct Blocked {
class: RegClass,
at: PhysReg,
point: Point,
by: Option<Reg>,
above: Option<u8>,
}
impl Blocked {
fn reaches(&self, width: Option<u8>) -> bool {
match (self.above, width) {
(Some(above), Some(width)) => width > above,
_ => true,
}
}
}
#[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>,
points: Vec<Point>,
spans: Vec<(usize, usize)>,
stride: usize,
widths: Vec<Option<u8>>,
}
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)
&& one.reaches(self.width(reg))
&& ((want == Want::Clear && one.by.is_some()) || area.covers(one.point))
})
}
fn width(&self, reg: Reg) -> Option<u8> {
let number = usize::try_from(reg.number()?).ok()?;
self.widths.get(number).copied().flatten()
}
pub(crate) fn taken(&self) -> impl Iterator<Item = (RegClass, PhysReg, Point)> + '_ {
self.all
.iter()
.filter(|one| one.by.is_none() && one.above.is_none())
.map(|one| (one.class, one.at, one.point))
}
#[inline]
fn over(
&self,
class: RegClass,
at: PhysReg,
range: Range,
) -> impl Iterator<Item = &Blocked> + '_ {
let (low, high) = if usize::from(at.number()) < self.stride {
let key = usize::from(class.number()) * self.stride + usize::from(at.number());
self.spans.get(key).copied().unwrap_or((0, 0))
} else {
(0, 0)
};
let first = low + self.points[low..high].partition_point(|&point| point < range.start);
self.all[first..high].iter().take_while(move |one| one.point <= range.end)
}
}
fn available(
active: &Active<'_>,
blocked: &Blocks,
interval: Interval<'_>,
at: PhysReg,
except: Option<Reg>,
want: Want,
) -> bool {
let taken = active.taken(interval.class, at, interval.area, except);
let width = blocked.width(interval.reg);
let insisted = blocked.over(interval.class, at, interval.range).any(|one| {
one.by != Some(interval.reg)
&& one.reaches(width)
&& ((want == Want::Clear && one.by.is_some()) || 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 = std::mem::take(&mut active.costs);
costs.clear();
for (slot, values) in active.by.iter().enumerate() {
let owners = if values.len() > FEW {
active.owners(interval.class, slot, interval.area)
} else {
None
};
for held in values {
if held.class != interval.class {
continue;
}
let touches = match &owners {
Some(owners) => owners.binary_search(&held.reg).is_ok(),
None => held.area.overlaps(interval.area),
};
if !touches {
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);
active.costs = costs;
match chosen {
Some(at) => {
active.evict(
interval.class,
at,
|held| held.area.overlaps(interval.area),
|held| assignment.spill(held.reg, held.class),
);
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);
let above = match operand.constraint {
Constraint::Above(above) => Some(above),
_ => None,
};
blocked.push(Blocked { class, at, point, by, above });
}
if !named && role == Role::Def {
blocked.push(Blocked { class, at, point, by: None, above: None });
}
}
}
}
}
blocked.sort_by_key(|one: &Blocked| (one.class, one.at, one.point));
let widths = (0..func.vregs())
.map(|number| func.width(Reg::virtual_reg(u32::try_from(number).ok()?)))
.collect();
let points = blocked.iter().map(|one| one.point).collect();
let stride = blocked.iter().map(|one| usize::from(one.at.number()) + 1).max().unwrap_or(0);
let classes = blocked.last().map_or(0, |one| usize::from(one.class.number()) + 1);
let mut spans = vec![(0, 0); classes * stride];
for (index, one) in blocked.iter().enumerate() {
let key = usize::from(one.class.number()) * stride + usize::from(one.at.number());
let span = &mut spans[key];
if span.1 == 0 {
span.0 = index;
}
span.1 = index + 1;
}
Blocks { all: blocked, points, spans, stride, widths }
}
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"]);
}
fn over_the_top(width: u32) -> Func {
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 held = func.new_vreg(GPR);
func.set_width(held, width);
func.build(block, opcode).def(held, GPR).finish();
func.build(block, opcode)
.operand(Operand::write(Reg::physical(RAX), GPR))
.operand(Operand::write(Reg::physical(RCX), GPR).with(Constraint::Above(8)))
.finish();
func.build(block, opcode).uses(held, GPR).finish();
func
}
#[test]
fn a_value_that_fits_under_what_an_instruction_writes_stays_in_the_register() {
assert_eq!(places(&over_the_top(8), &narrow(2)), ["rcx"]);
assert_eq!(places(&over_the_top(4), &narrow(2)), ["rcx"]);
}
#[test]
fn a_value_wider_than_that_or_of_no_known_width_does_not() {
assert_eq!(places(&over_the_top(16), &narrow(2)), ["slot 0"]);
assert_eq!(places(&over_the_top(0), &narrow(2)), ["slot 0"]);
}
#[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)), ["rax", "rcx"]);
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());
}
fn handed_in_the_arm() -> 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);
let own = 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).def(own, GPR).finish();
func.build(arm, opcode)
.operand(Operand::read(own, GPR).with(Constraint::Fixed(RAX)))
.finish();
func.build(tail, opcode).uses(first, GPR).uses(second, GPR).finish();
func
}
#[test]
fn a_register_another_value_is_handed_is_the_last_one_offered_rather_than_the_first() {
let func = handed_in_the_arm();
assert_eq!(places(&func, &narrow(3)), ["rcx", "rdx", "rax"]);
}
#[test]
fn a_register_a_clobber_takes_is_clear_to_a_value_dead_where_it_is_taken() {
let func = arms(false);
assert_eq!(places(&func, &narrow(3)), ["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]);
}
fn ranges(pairs: &[(Point, Point)]) -> Vec<Range> {
pairs.iter().map(|&(start, end)| Range { start, end }).collect()
}
#[test]
fn the_pieces_of_a_register_answer_what_a_walk_over_its_values_would() {
let held = [
(Reg::virtual_reg(0), ranges(&[(0, 4), (20, 30)])),
(Reg::virtual_reg(1), ranges(&[(5, 9)])),
(Reg::virtual_reg(2), ranges(&[(10, 19), (31, 40)])),
];
let mut pieces = Pieces::default();
for (reg, list) in &held {
for &piece in list {
pieces.insert(piece, *reg);
}
}
assert!(!pieces.broken);
let asked = [
ranges(&[(41, 50)]),
ranges(&[(40, 50)]),
ranges(&[(9, 9)]),
ranges(&[(3, 3), (41, 42)]),
ranges(&[(50, 60)]),
ranges(&[(15, 15)]),
];
for list in &asked {
let area = Area::of_pieces(list);
for except in [None, Some(Reg::virtual_reg(0)), Some(Reg::virtual_reg(2))] {
let walked = held.iter().any(|(reg, pieces)| {
Some(*reg) != except && Area::of_pieces(pieces).overlaps(area)
});
assert_eq!(pieces.touch(area, except), walked, "{list:?} except {except:?}");
}
}
}
#[test]
fn the_owners_of_the_pieces_an_area_touches_are_the_values_a_walk_would_find() {
let held = [
(Reg::virtual_reg(0), ranges(&[(0, 4), (20, 30)])),
(Reg::virtual_reg(1), ranges(&[(5, 9)])),
(Reg::virtual_reg(2), ranges(&[(10, 19), (30, 40)])),
];
let mut pieces = Pieces::default();
for (reg, list) in &held {
for &piece in list {
pieces.insert(piece, *reg);
}
}
let asked = [
ranges(&[(41, 50)]),
ranges(&[(30, 30)]),
ranges(&[(3, 12)]),
ranges(&[(3, 3), (25, 42)]),
ranges(&[(0, 50)]),
];
for list in &asked {
let area = Area::of_pieces(list);
let walked: Vec<Reg> = held
.iter()
.filter(|(_, pieces)| Area::of_pieces(pieces).overlaps(area))
.map(|&(reg, _)| reg)
.collect();
assert_eq!(pieces.owners(area), Some(walked), "{list:?}");
}
pieces.insert(Range { start: 2, end: 3 }, Reg::virtual_reg(3));
assert_eq!(pieces.owners(Area::of_pieces(&asked[0])), None);
}
#[test]
fn pieces_that_end_out_of_order_are_marked() {
let mut pieces = Pieces::default();
pieces.insert(Range { start: 0, end: 10 }, Reg::virtual_reg(0));
pieces.insert(Range { start: 12, end: 20 }, Reg::virtual_reg(1));
assert!(!pieces.broken);
pieces.insert(Range { start: 2, end: 3 }, Reg::virtual_reg(2));
assert!(pieces.broken);
}
#[test]
fn a_value_taken_out_of_a_register_leaves_its_pieces_with_it() {
let mut pieces = Pieces::default();
let (first, second) = (Reg::virtual_reg(0), Reg::virtual_reg(1));
pieces.insert(Range { start: 0, end: 10 }, first);
pieces.insert(Range { start: 12, end: 20 }, second);
let asked = ranges(&[(15, 16)]);
assert!(pieces.touch(Area::of_pieces(&asked), None));
pieces.remove(Range { start: 12, end: 20 }, second);
assert!(!pieces.touch(Area::of_pieces(&asked), None));
pieces.drop_before(11);
assert!(pieces.list.is_empty());
}
}