use alloc::vec::Vec;
use core::fmt;
use crate::mir::StackSlot;
use crate::regs::{PReg, RegClass, RegSet};
use crate::target::Target;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Loc {
Reg(PReg),
Slot(StackSlot),
}
impl Loc {
fn key(self) -> u64 {
match self {
Loc::Reg(r) => r.index() as u64,
Loc::Slot(s) => (1 << 32) | s.index() as u64,
}
}
#[inline]
#[must_use]
pub const fn is_mem(self) -> bool {
matches!(self, Loc::Slot(_))
}
}
impl From<PReg> for Loc {
#[inline]
fn from(r: PReg) -> Loc {
Loc::Reg(r)
}
}
impl From<StackSlot> for Loc {
#[inline]
fn from(s: StackSlot) -> Loc {
Loc::Slot(s)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Source {
Loc(Loc),
Imm(i64),
}
impl From<Loc> for Source {
#[inline]
fn from(l: Loc) -> Source {
Source::Loc(l)
}
}
impl From<PReg> for Source {
#[inline]
fn from(r: PReg) -> Source {
Source::Loc(Loc::Reg(r))
}
}
impl From<StackSlot> for Source {
#[inline]
fn from(s: StackSlot) -> Source {
Source::Loc(Loc::Slot(s))
}
}
impl From<i64> for Source {
#[inline]
fn from(v: i64) -> Source {
Source::Imm(v)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Move {
pub dst: Loc,
pub src: Source,
pub class: RegClass,
}
impl Move {
#[must_use]
pub fn new(dst: impl Into<Loc>, src: impl Into<Source>, class: RegClass) -> Move {
Move {
dst: dst.into(),
src: src.into(),
class,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Step {
Move {
dst: Loc,
src: Source,
class: RegClass,
},
Swap {
a: PReg,
b: PReg,
class: RegClass,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MoveEnv {
pub free: RegSet,
pub scratch_slot: Option<StackSlot>,
pub victim_slot: Option<StackSlot>,
pub swap: bool,
}
impl Default for MoveEnv {
fn default() -> MoveEnv {
MoveEnv::new()
}
}
impl MoveEnv {
#[must_use]
pub const fn new() -> MoveEnv {
MoveEnv {
free: RegSet::EMPTY,
scratch_slot: None,
victim_slot: None,
swap: true,
}
}
#[must_use]
pub const fn with_free(self, free: RegSet) -> MoveEnv {
MoveEnv { free, ..self }
}
#[must_use]
pub const fn with_scratch_slot(self, slot: StackSlot) -> MoveEnv {
MoveEnv {
scratch_slot: Some(slot),
..self
}
}
#[must_use]
pub const fn with_victim_slot(self, slot: StackSlot) -> MoveEnv {
MoveEnv {
victim_slot: Some(slot),
..self
}
}
#[must_use]
pub const fn with_swap(self, swap: bool) -> MoveEnv {
MoveEnv { swap, ..self }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MoveError {
DuplicateDestination {
index: usize,
},
ClassMismatch {
index: usize,
},
NoScratch {
class: RegClass,
},
NoTemp {
class: RegClass,
},
TooMany,
}
impl fmt::Display for MoveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MoveError::DuplicateDestination { index } => write!(
f,
"move {index} writes a destination an earlier move already writes"
),
MoveError::ClassMismatch { index } => {
write!(f, "move {index} names a register outside its class")
}
MoveError::NoScratch { class } => write!(
f,
"a cycle of class {} needs a free register or a scratch slot",
class.index()
),
MoveError::NoTemp { class } => write!(
f,
"a memory-to-memory move of class {} needs a free register or a victim slot",
class.index()
),
MoveError::TooMany => f.write_str("too many moves in one parallel copy"),
}
}
}
impl core::error::Error for MoveError {}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Resolved {
pub steps: usize,
pub scratch_slot: bool,
pub victim_slot: bool,
}
const NONE: u32 = u32::MAX;
const SCRATCH: u32 = u32::MAX - 1;
const IMM: u32 = u32::MAX - 2;
#[derive(Clone, Debug, Default)]
pub struct MoveResolver {
keys: Vec<(u64, u64)>,
locs: Vec<Loc>,
writer: Vec<u32>,
readers: Vec<u32>,
alias: Vec<u32>,
dst: Vec<u32>,
src: Vec<u32>,
imm: Vec<i64>,
class: Vec<RegClass>,
origin: Vec<u32>,
ready: Vec<u32>,
cycle: Vec<u32>,
}
struct Legal<'a> {
target: &'a Target,
env: &'a MoveEnv,
involved: RegSet,
scratch: Option<Loc>,
used: Resolved,
}
impl Legal<'_> {
fn free_reg(&self, class: RegClass) -> Option<PReg> {
let mut free = self
.env
.free
.intersect(self.target.class(class).regs())
.difference(self.involved);
if let Some(Loc::Reg(r)) = self.scratch {
free = free.without(r);
}
free.first()
}
fn push(&mut self, out: &mut Vec<Step>, step: Step) {
out.push(step);
self.used.steps += 1;
}
fn emit(
&mut self,
out: &mut Vec<Step>,
dst: Loc,
src: Source,
class: RegClass,
) -> Result<(), MoveError> {
let caps = self.target.move_caps();
let needs_temp = dst.is_mem()
&& match src {
Source::Loc(l) => l.is_mem() && !caps.mem_to_mem(),
Source::Imm(v) => !caps.store_imm().fits(v),
};
if !needs_temp {
self.push(out, Step::Move { dst, src, class });
return Ok(());
}
if let Some(t) = self.free_reg(class) {
self.push(
out,
Step::Move {
dst: Loc::Reg(t),
src,
class,
},
);
self.push(
out,
Step::Move {
dst,
src: Source::Loc(Loc::Reg(t)),
class,
},
);
return Ok(());
}
let slot = self.env.victim_slot.ok_or(MoveError::NoTemp { class })?;
let mut pool = self.target.class(class).allocatable();
if let Some(Loc::Reg(r)) = self.scratch {
pool = pool.without(r);
}
let v = pool.first().ok_or(MoveError::NoTemp { class })?;
let (vl, sl) = (Loc::Reg(v), Loc::Slot(slot));
self.used.victim_slot = true;
self.push(
out,
Step::Move {
dst: sl,
src: Source::Loc(vl),
class,
},
);
self.push(
out,
Step::Move {
dst: vl,
src,
class,
},
);
self.push(
out,
Step::Move {
dst,
src: Source::Loc(vl),
class,
},
);
self.push(
out,
Step::Move {
dst: vl,
src: Source::Loc(sl),
class,
},
);
Ok(())
}
}
impl MoveResolver {
#[must_use]
pub fn new() -> MoveResolver {
MoveResolver::default()
}
pub fn resolve(
&mut self,
target: &Target,
moves: &[Move],
env: &MoveEnv,
out: &mut Vec<Step>,
) -> Result<Resolved, MoveError> {
if moves.len() > (u32::MAX - 3) as usize {
return Err(MoveError::TooMany);
}
let involved = self.number(target, moves)?;
let mut legal = Legal {
target,
env,
involved,
scratch: None,
used: Resolved::default(),
};
let count = self.dst.len();
self.ready.clear();
for m in (0..count).rev() {
if self.readers[self.dst[m] as usize] == 0 {
self.ready.push(m as u32);
}
}
let mut cursor = 0usize;
loop {
self.drain(&mut legal, out)?;
while cursor < count && self.writer[self.dst[cursor] as usize] != cursor as u32 {
cursor += 1;
}
if cursor == count {
break;
}
self.break_cycle(cursor as u32, &mut legal, out)?;
}
Ok(legal.used)
}
fn number(&mut self, target: &Target, moves: &[Move]) -> Result<RegSet, MoveError> {
self.keys.clear();
self.dst.clear();
self.src.clear();
self.imm.clear();
self.class.clear();
self.origin.clear();
let mut involved = RegSet::EMPTY;
for (i, m) in moves.iter().enumerate() {
if !target.has_class(m.class) {
return Err(MoveError::ClassMismatch { index: i });
}
let regs = target.class(m.class).regs();
let in_class = |l: Loc| match l {
Loc::Reg(r) => target.has_reg(r) && regs.contains(r),
Loc::Slot(_) => true,
};
if !in_class(m.dst) || matches!(m.src, Source::Loc(l) if !in_class(l)) {
return Err(MoveError::ClassMismatch { index: i });
}
if m.src == Source::Loc(m.dst) {
self.keys.push((m.dst.key(), (i as u64) << 1));
if let Loc::Reg(r) = m.dst {
involved = involved.with(r);
}
continue;
}
let k = self.dst.len() as u32;
self.keys.push((m.dst.key(), (i as u64) << 1));
self.dst.push(NONE);
self.class.push(m.class);
self.origin.push(i as u32);
match m.src {
Source::Loc(l) => {
self.keys.push((l.key(), (u64::from(k) << 1) | 1));
self.src.push(NONE);
self.imm.push(0);
}
Source::Imm(v) => {
self.src.push(IMM);
self.imm.push(v);
}
}
for l in [
Some(m.dst),
if let Source::Loc(l) = m.src {
Some(l)
} else {
None
},
]
.into_iter()
.flatten()
{
if let Loc::Reg(r) = l {
involved = involved.with(r);
}
}
}
self.keys.sort_unstable();
let compact_of =
|input: u32, origin: &[u32]| -> Option<usize> { origin.binary_search(&input).ok() };
self.locs.clear();
let mut prev_key = u64::MAX;
let mut prev_was_dst = false;
for &(key, tag) in &self.keys {
if key != prev_key || self.locs.is_empty() {
prev_key = key;
prev_was_dst = false;
self.locs.push(if key >> 32 == 0 {
Loc::Reg(PReg::from_index(key as u8))
} else {
Loc::Slot(StackSlot::from_index((key & 0xFFFF_FFFF) as usize))
});
}
let id = (self.locs.len() - 1) as u32;
if tag & 1 == 0 {
let input = (tag >> 1) as u32;
if prev_was_dst {
return Err(MoveError::DuplicateDestination {
index: input as usize,
});
}
prev_was_dst = true;
if let Some(m) = compact_of(input, &self.origin) {
self.dst[m] = id;
}
} else {
self.src[(tag >> 1) as usize] = id;
}
}
let n = self.locs.len();
self.writer.clear();
self.writer.resize(n, NONE);
self.readers.clear();
self.readers.resize(n, 0);
self.alias.clear();
self.alias.resize(n, NONE);
for m in 0..self.dst.len() {
self.writer[self.dst[m] as usize] = m as u32;
let s = self.src[m];
if s < IMM {
self.readers[s as usize] += 1;
}
}
Ok(involved)
}
fn drain(&mut self, legal: &mut Legal<'_>, out: &mut Vec<Step>) -> Result<(), MoveError> {
while let Some(m) = self.ready.pop() {
let m = m as usize;
let d = self.dst[m];
let class = self.class[m];
let src = match self.src[m] {
IMM => Source::Imm(self.imm[m]),
SCRATCH => match legal.scratch.take() {
Some(l) => Source::Loc(l),
None => return Err(MoveError::NoScratch { class }),
},
s => Source::Loc(self.locs[s as usize]),
};
legal.emit(out, self.locs[d as usize], src, class)?;
self.writer[d as usize] = NONE;
let s = self.src[m];
if s < IMM {
let a = &mut self.alias[s as usize];
if *a == NONE
|| (self.locs[*a as usize].is_mem() && !self.locs[d as usize].is_mem())
{
*a = d;
}
self.release(s);
}
}
Ok(())
}
fn release(&mut self, s: u32) {
let r = &mut self.readers[s as usize];
*r -= 1;
if *r == 0 {
let w = self.writer[s as usize];
if w != NONE {
self.ready.push(w);
}
}
}
fn break_cycle(
&mut self,
start: u32,
legal: &mut Legal<'_>,
out: &mut Vec<Step>,
) -> Result<(), MoveError> {
self.cycle.clear();
let mut m = start;
loop {
self.cycle.push(m);
let s = self.src[m as usize];
let next = self.writer.get(s as usize).copied().unwrap_or(NONE);
if next == start || next == NONE {
break;
}
m = next;
}
for &m in &self.cycle {
let s = self.src[m as usize];
let a = self.alias[s as usize];
if a != NONE {
self.src[m as usize] = a;
self.readers[a as usize] += 1;
self.release(s);
return Ok(());
}
}
let class = self.class[start as usize];
let all_regs_one_class = self.cycle.iter().all(|&m| {
!self.locs[self.dst[m as usize] as usize].is_mem() && self.class[m as usize] == class
});
if all_regs_one_class && legal.env.swap && legal.target.move_caps().swap() {
for w in 0..self.cycle.len().saturating_sub(1) {
let a = self.locs[self.dst[self.cycle[w] as usize] as usize];
let b = self.locs[self.dst[self.cycle[w + 1] as usize] as usize];
if let (Loc::Reg(a), Loc::Reg(b)) = (a, b) {
legal.push(out, Step::Swap { a, b, class });
}
}
for &m in &self.cycle {
let d = self.dst[m as usize] as usize;
self.writer[d] = NONE;
self.readers[d] = 0;
}
return Ok(());
}
let pick = self
.cycle
.iter()
.copied()
.find(|&m| {
!self.locs[self.src[m as usize] as usize].is_mem()
&& !self.locs[self.dst[m as usize] as usize].is_mem()
})
.unwrap_or(start);
let class = self.class[pick as usize];
let s = self.src[pick as usize];
let scratch = match legal.free_reg(class) {
Some(r) => Loc::Reg(r),
None => {
let slot = legal
.env
.scratch_slot
.ok_or(MoveError::NoScratch { class })?;
legal.used.scratch_slot = true;
Loc::Slot(slot)
}
};
legal.emit(out, scratch, Source::Loc(self.locs[s as usize]), class)?;
legal.scratch = Some(scratch);
self.src[pick as usize] = SCRATCH;
self.release(s);
Ok(())
}
}
pub fn resolve(target: &Target, moves: &[Move], env: &MoveEnv) -> Result<Vec<Step>, MoveError> {
let mut out = Vec::with_capacity(moves.len() + 1);
let _used = MoveResolver::new().resolve(target, moves, env, &mut out)?;
Ok(out)
}