use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassInfo {
pub name: &'static str,
pub bits: u32,
pub regs: &'static [&'static str],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegClass(u8);
impl RegClass {
#[must_use]
pub const fn new(number: u8) -> Self {
Self(number)
}
#[must_use]
pub const fn number(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PhysReg(u8);
impl PhysReg {
#[must_use]
pub const fn new(number: u8) -> Self {
Self(number)
}
#[must_use]
pub const fn number(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegFile {
classes: &'static [ClassInfo],
}
impl RegFile {
pub const EMPTY: Self = Self::new(&[]);
#[must_use]
pub const fn new(classes: &'static [ClassInfo]) -> Self {
Self { classes }
}
pub fn classes(&self) -> impl Iterator<Item = (RegClass, &'static ClassInfo)> + use<> {
self.classes.iter().enumerate().map(|(number, info)| (RegClass::new(number as u8), info))
}
#[must_use]
pub fn class(&self, class: RegClass) -> Option<&'static ClassInfo> {
self.classes.get(usize::from(class.number()))
}
#[must_use]
pub fn class_named(&self, name: &str) -> Option<RegClass> {
self.classes().find(|(_, info)| info.name == name).map(|(class, _)| class)
}
#[must_use]
pub fn len(&self, class: RegClass) -> usize {
self.class(class).map_or(0, |info| info.regs.len())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.classes.is_empty()
}
#[must_use]
pub fn name(&self, class: RegClass, reg: PhysReg) -> Option<&'static str> {
self.class(class)?.regs.get(usize::from(reg.number())).copied()
}
#[must_use]
pub fn reg_named(&self, name: &str) -> Option<(RegClass, PhysReg)> {
for (class, info) in self.classes() {
if let Some(number) = info.regs.iter().position(|®| reg == name) {
return Some((class, PhysReg::new(number as u8)));
}
}
None
}
#[must_use]
pub fn duplicate(&self) -> Option<&'static str> {
let mut seen: Vec<&'static str> = Vec::new();
for (_, info) in self.classes() {
for ® in info.regs {
if seen.contains(®) {
return Some(reg);
}
seen.push(reg);
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CallRegs {
pub int_class: RegClass,
pub sse_class: RegClass,
pub int_args: &'static [PhysReg],
pub sse_args: &'static [PhysReg],
pub shared_positions: bool,
pub int_returns: &'static [PhysReg],
pub sse_returns: &'static [PhysReg],
pub x87_returns: &'static [PhysReg],
pub int_saved: &'static [PhysReg],
pub sse_saved: &'static [PhysReg],
pub int_order: &'static [PhysReg],
pub sse_order: &'static [PhysReg],
pub stack_pointer: PhysReg,
pub frame_pointer: PhysReg,
pub vector_count: Option<PhysReg>,
pub red_zone: u32,
pub shadow: u32,
pub stack_align: u32,
pub return_address: u32,
pub word: u32,
}
impl CallRegs {
#[must_use]
pub fn preserves_int(&self, reg: PhysReg) -> bool {
self.int_saved.contains(®)
}
#[must_use]
pub fn preserves_sse(&self, reg: PhysReg) -> bool {
self.sse_saved.contains(®)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Where {
Reg(PhysReg),
Stack(u32),
}
#[derive(Debug, Clone)]
pub struct Places<'a> {
regs: &'a CallRegs,
int: usize,
sse: usize,
stack: u32,
}
impl<'a> Places<'a> {
#[must_use]
pub fn new(regs: &'a CallRegs) -> Self {
Self { regs, int: 0, sse: 0, stack: regs.shadow }
}
pub fn integer(&mut self) -> Where {
match self.regs.int_args.get(self.position(false)) {
Some(®) => {
self.int += 1;
Where::Reg(reg)
}
None => self.on_stack(self.regs.word, self.regs.word),
}
}
pub fn float(&mut self) -> Where {
match self.regs.sse_args.get(self.position(true)) {
Some(®) => {
self.sse += 1;
Where::Reg(reg)
}
None => self.on_stack(self.regs.word, self.regs.word),
}
}
pub fn on_stack(&mut self, size: u32, align: u32) -> Where {
let word = self.regs.word;
let at = self.stack.next_multiple_of(align.max(word));
self.stack = at.saturating_add(size.max(word).next_multiple_of(word));
Where::Stack(at)
}
#[must_use]
pub fn size(&self) -> u32 {
self.stack
}
fn position(&self, sse: bool) -> usize {
if self.regs.shared_positions {
self.int + self.sse
} else if sse {
self.sse
} else {
self.int
}
}
}
impl fmt::Display for RegFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (_, info) in self.classes() {
writeln!(f, "class {} : i{} = {}", info.name, info.bits, info.regs.join(", "))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
static GPR: [&str; 3] = ["rax", "rcx", "rdx"];
static XMM: [&str; 2] = ["xmm0", "xmm1"];
static CLASSES: [ClassInfo; 2] = [
ClassInfo { name: "gpr", bits: 64, regs: &GPR },
ClassInfo { name: "xmm", bits: 128, regs: &XMM },
];
static FILE: RegFile = RegFile::new(&CLASSES);
#[test]
fn a_class_is_found_by_its_name() {
let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
assert_eq!(FILE.len(gpr), 3);
assert_eq!(FILE.class(gpr).map(|info| info.bits), Some(64));
assert_eq!(FILE.class_named("vec"), None);
}
#[test]
fn a_register_is_found_by_its_name_and_names_itself_back() {
let (class, reg) = FILE.reg_named("xmm1").expect("the file has xmm1");
assert_eq!(FILE.class(class).map(|info| info.name), Some("xmm"));
assert_eq!(reg.number(), 1);
assert_eq!(FILE.name(class, reg), Some("xmm1"));
assert_eq!(FILE.reg_named("r15"), None);
}
#[test]
fn a_number_past_the_end_of_a_class_has_no_name() {
let gpr = FILE.class_named("gpr").expect("the file has a gpr class");
assert_eq!(FILE.name(gpr, PhysReg::new(3)), None);
assert_eq!(FILE.name(RegClass::new(7), PhysReg::new(0)), None);
}
#[test]
fn a_file_that_names_two_registers_alike_says_so() {
assert_eq!(FILE.duplicate(), None);
static BOTH: [ClassInfo; 2] = [
ClassInfo { name: "gpr", bits: 64, regs: &GPR },
ClassInfo { name: "shadow", bits: 64, regs: &GPR },
];
assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
}
#[test]
fn the_file_prints_one_class_to_a_line() {
assert_eq!(
FILE.to_string(),
"class gpr : i64 = rax, rcx, rdx\nclass xmm : i128 = xmm0, xmm1\n"
);
}
fn convention(shared: bool, shadow: u32) -> CallRegs {
static INT: [PhysReg; 2] = [PhysReg::new(0), PhysReg::new(1)];
static SSE: [PhysReg; 2] = [PhysReg::new(10), PhysReg::new(11)];
static NONE: [PhysReg; 0] = [];
CallRegs {
int_class: RegClass::new(0),
sse_class: RegClass::new(1),
int_args: &INT,
sse_args: &SSE,
shared_positions: shared,
int_returns: &INT,
sse_returns: &SSE,
x87_returns: &NONE,
int_saved: &NONE,
sse_saved: &NONE,
int_order: &INT,
sse_order: &SSE,
stack_pointer: PhysReg::new(4),
frame_pointer: PhysReg::new(5),
vector_count: None,
red_zone: 0,
shadow,
stack_align: 16,
return_address: 8,
word: 8,
}
}
#[test]
fn counting_each_kind_separately_leaves_the_first_vector_register_to_the_first_float() {
let regs = convention(false, 0);
let mut places = Places::new(®s);
assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
assert_eq!(places.size(), 0);
}
#[test]
fn counting_one_position_for_both_skips_the_register_the_other_kind_would_have_used() {
let regs = convention(true, 0);
let mut places = Places::new(®s);
assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
assert_eq!(places.float(), Where::Reg(PhysReg::new(11)));
assert_eq!(places.integer(), Where::Stack(0));
}
#[test]
fn running_out_of_one_kind_of_register_does_not_touch_the_other() {
let regs = convention(false, 0);
let mut places = Places::new(®s);
assert_eq!(places.integer(), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(), Where::Reg(PhysReg::new(1)));
assert_eq!(places.integer(), Where::Stack(0));
assert_eq!(places.float(), Where::Reg(PhysReg::new(10)));
assert_eq!(places.size(), 8);
}
#[test]
fn the_argument_area_starts_above_the_shadow_space_and_keeps_every_value_aligned() {
let regs = convention(false, 32);
let mut places = Places::new(®s);
assert_eq!(places.size(), 32);
assert_eq!(places.on_stack(4, 4), Where::Stack(32));
assert_eq!(places.on_stack(16, 16), Where::Stack(48));
assert_eq!(places.on_stack(8, 8), Where::Stack(64));
assert_eq!(places.size(), 72);
}
}