use std::fmt;
use rucc_abi::{AbiDescription, Convention, StackArgs};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassInfo {
pub name: &'static str,
pub bits: u32,
pub regs: &'static [&'static str],
pub allocatable: bool,
}
#[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 allocatable(&self, class: RegClass) -> bool {
self.class(class).is_some_and(|info| info.allocatable)
}
#[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 enum Segment {
Fs,
Gs,
Es,
Cs,
Ss,
Ds,
}
impl Segment {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Segment::Fs => "fs",
Segment::Gs => "gs",
Segment::Es => "es",
Segment::Cs => "cs",
Segment::Ss => "ss",
Segment::Ds => "ds",
}
}
#[must_use]
pub fn named(name: &str) -> Option<Segment> {
[Segment::Es, Segment::Cs, Segment::Ss, Segment::Ds, Segment::Fs, Segment::Gs]
.into_iter()
.find(|segment| segment.name() == name)
}
#[must_use]
pub const fn number(self) -> u8 {
match self {
Segment::Es => 0,
Segment::Cs => 1,
Segment::Ss => 2,
Segment::Ds => 3,
Segment::Fs => 4,
Segment::Gs => 5,
}
}
#[must_use]
pub const fn prefix(self) -> u8 {
match self {
Segment::Es => 0x26,
Segment::Cs => 0x2E,
Segment::Ss => 0x36,
Segment::Ds => 0x3E,
Segment::Fs => 0x64,
Segment::Gs => 0x65,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Guard {
pub segment: Option<Segment>,
pub symbol: Option<&'static str>,
pub table: bool,
pub at: i32,
pub fail: &'static str,
}
impl Guard {
#[must_use]
pub const fn in_segment(segment: Segment, at: i32) -> Self {
Self { segment: Some(segment), symbol: None, table: false, at, fail: "__stack_chk_fail" }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Trace {
pub early: &'static str,
pub late: &'static str,
pub fentry: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Chkstk {
pub name: &'static str,
pub size: PhysReg,
pub shift: u8,
pub moves: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CallRegs {
pub abi: &'static AbiDescription,
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 sse_kept: Option<u8>,
pub int_order: &'static [PhysReg],
pub sse_order: &'static [PhysReg],
pub stack_pointer: PhysReg,
pub frame_pointer: PhysReg,
pub late_frame_pointer: bool,
pub unwind_codes: bool,
pub vector_count: Option<PhysReg>,
pub red_zone: u32,
pub shadow: u32,
pub home: u32,
pub stack_align: u32,
pub trusted_align: u32,
pub return_address: u32,
pub word: u32,
pub total_store_order: bool,
pub unaligned: bool,
pub byte_swaps: &'static [u32],
pub push: u32,
pub link: Option<PhysReg>,
pub sret: Option<PhysReg>,
pub chain: Option<PhysReg>,
pub list: crate::VaList,
pub dwarf: &'static [&'static [u16]],
pub dwarf_return_address: u16,
pub guard: Option<Guard>,
pub trace: Option<Trace>,
pub chkstk: Option<Chkstk>,
pub conventions: Conventions,
}
#[derive(Clone, Copy)]
pub struct Conventions(pub &'static [(Convention, &'static CallRegs)]);
impl Conventions {
pub const ONLY: Conventions = Conventions(&[]);
}
impl fmt::Debug for Conventions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.0.iter().map(|(convention, _)| convention)).finish()
}
}
impl PartialEq for Conventions {
fn eq(&self, other: &Self) -> bool {
self.0.len() == other.0.len()
&& self.0.iter().zip(other.0).all(|((a, x), (b, y))| a == b && std::ptr::eq(*x, *y))
}
}
impl Eq for Conventions {}
impl CallRegs {
pub fn return_pointer_popped(&self) -> u32 {
match self.abi.return_pointer {
rucc_abi::ReturnPointer::FirstArgumentPopped => self.word,
_ => 0,
}
}
#[must_use]
pub fn under(&self, convention: Convention) -> Option<&CallRegs> {
match self.conventions.0.iter().find(|(named, _)| *named == convention) {
Some(&(_, regs)) => Some(regs),
None if convention == Convention::Target => Some(self),
None => None,
}
}
#[must_use]
pub fn homed(&self) -> CallRegs {
if self.home == 0 {
return *self;
}
CallRegs { shadow: self.home, shared_positions: true, sse_args: &[], ..*self }
}
#[must_use]
pub fn aligned_to(&'static self, bytes: u32) -> &'static CallRegs {
assert!(bytes.is_power_of_two(), "a stack boundary that is not a power of 2");
if bytes == self.stack_align {
return self;
}
let trusted_align = if self.trusted_align == self.stack_align {
bytes
} else {
self.trusted_align.min(bytes)
};
made(CallRegs { stack_align: bytes, trusted_align, ..*self })
}
#[must_use]
pub fn interrupted(&'static self, code: bool) -> &'static CallRegs {
let return_address = self.return_address + if code { self.word } else { 0 };
made(CallRegs { return_address, red_zone: 0, ..*self })
}
#[must_use]
pub fn without_vectors(&'static self) -> &'static CallRegs {
if self.sse_args.is_empty() {
return self;
}
made(CallRegs { sse_args: &[], ..*self })
}
#[must_use]
pub fn guarded_by(&'static self, guard: Guard) -> &'static CallRegs {
if self.guard == Some(guard) {
return self;
}
made(CallRegs { guard: Some(guard), ..*self })
}
#[must_use]
pub fn dwarf(&self, class: RegClass, reg: PhysReg) -> Option<u16> {
self.dwarf.get(usize::from(class.number()))?.get(usize::from(reg.number())).copied()
}
#[must_use]
pub fn machine(&self, class: RegClass, dwarf: u16) -> Option<PhysReg> {
let numbers = self.dwarf.get(usize::from(class.number()))?;
let at = numbers.iter().position(|&number| number == dwarf)?;
Some(PhysReg::new(u8::try_from(at).ok()?))
}
#[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, bytes: u32) -> Where {
match self.regs.int_args.get(self.position(false)) {
Some(®) => {
self.int += 1;
Where::Reg(reg)
}
None => self.scalar(bytes),
}
}
pub fn float(&mut self, bytes: u32) -> Where {
match self.regs.sse_args.get(self.position(true)) {
Some(®) => {
self.sse += 1;
Where::Reg(reg)
}
None => self.scalar(bytes),
}
}
pub fn scalar(&mut self, bytes: u32) -> Where {
if self.regs.abi.stack_args == StackArgs::Packed {
let bytes = bytes.max(1);
let at = self.stack.next_multiple_of(bytes);
self.stack = at.saturating_add(bytes);
return Where::Stack(at);
}
let align = if bytes <= 8 { bytes.min(self.regs.word) } else { bytes };
self.on_stack(bytes, align)
}
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)
}
pub fn object(&mut self, size: u32, align: u32) -> Where {
if self.regs.abi.stack_args != StackArgs::Packed {
return self.on_stack(size, align);
}
let align = align.max(1);
let at = self.stack.next_multiple_of(align);
self.stack = at.saturating_add(size.next_multiple_of(align));
Where::Stack(at)
}
pub fn drain_integers(&mut self) {
self.int = self.regs.int_args.len();
}
pub fn drain_floats(&mut self) {
self.sse = self.regs.sse_args.len();
}
#[must_use]
pub fn size(&self) -> u32 {
self.stack
}
#[must_use]
pub fn integers(&self) -> usize {
self.int
}
#[must_use]
pub fn floats(&self) -> usize {
self.sse
}
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(())
}
}
fn made(wanted: CallRegs) -> &'static CallRegs {
use std::sync::Mutex;
static MADE: Mutex<Vec<&'static CallRegs>> = Mutex::new(Vec::new());
let mut made = MADE.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(®s) = made.iter().find(|&®s| *regs == wanted) {
return regs;
}
let regs: &'static CallRegs = Box::leak(Box::new(wanted));
made.push(regs);
regs
}
#[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, allocatable: true },
ClassInfo { name: "xmm", bits: 128, regs: &XMM, allocatable: true },
];
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, allocatable: true },
ClassInfo { name: "shadow", bits: 64, regs: &GPR, allocatable: true },
];
assert_eq!(RegFile::new(&BOTH).duplicate(), Some("rax"));
}
#[test]
fn a_class_nothing_allocates_from_is_still_a_class_in_every_other_way() {
static WITH_STACK: [ClassInfo; 2] = [
ClassInfo { name: "gpr", bits: 64, regs: &GPR, allocatable: true },
ClassInfo { name: "x87", bits: 80, regs: &XMM, allocatable: false },
];
let file = RegFile::new(&WITH_STACK);
let stack = file.class_named("x87").expect("the file has an x87 class");
assert!(!file.allocatable(stack));
assert!(file.allocatable(file.class_named("gpr").expect("the file has a gpr class")));
assert_eq!(file.len(stack), 2);
assert_eq!(file.name(stack, PhysReg::new(1)), Some("xmm1"));
assert_eq!(file.reg_named("xmm1"), Some((stack, PhysReg::new(1))));
}
#[test]
fn a_class_the_file_does_not_have_is_not_one_to_allocate_from_either() {
assert!(!FILE.allocatable(RegClass::new(7)));
}
#[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] = [];
let abi: &'static AbiDescription =
if shared { &rucc_abi::abis::WIN64 } else { &rucc_abi::abis::SYSV_AMD64 };
CallRegs {
abi,
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,
sse_kept: None,
int_order: &INT,
sse_order: &SSE,
stack_pointer: PhysReg::new(4),
frame_pointer: PhysReg::new(5),
late_frame_pointer: false,
unwind_codes: false,
vector_count: None,
red_zone: 0,
shadow,
home: 0,
stack_align: 16,
trusted_align: 16,
return_address: 8,
word: 8,
total_store_order: true,
unaligned: true,
byte_swaps: &[16, 32, 64],
push: 8,
link: None,
sret: None,
chain: None,
list: if shared { crate::VaList::CharPointer } else { crate::VaList::SysV },
dwarf: &[],
dwarf_return_address: 16,
guard: None,
trace: None,
chkstk: None,
conventions: Conventions::ONLY,
}
}
#[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(8), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(8), Where::Reg(PhysReg::new(1)));
assert_eq!(places.float(8), 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(8), Where::Reg(PhysReg::new(0)));
assert_eq!(places.float(8), Where::Reg(PhysReg::new(11)));
assert_eq!(places.integer(8), 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(8), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(8), Where::Reg(PhysReg::new(1)));
assert_eq!(places.integer(8), Where::Stack(0));
assert_eq!(places.float(8), 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);
}
#[test]
fn a_float_wider_than_a_word_takes_two_of_them_once_the_registers_are_gone() {
let regs = convention(false, 0);
let mut places = Places::new(®s);
assert_eq!(places.float(16), Where::Reg(PhysReg::new(10)));
assert_eq!(places.float(16), Where::Reg(PhysReg::new(11)));
assert_eq!(places.integer(8), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(8), Where::Reg(PhysReg::new(1)));
assert_eq!(places.integer(8), Where::Stack(0));
assert_eq!(places.float(16), Where::Stack(16));
assert_eq!(places.float(16), Where::Stack(32));
assert_eq!(places.size(), 48);
assert_eq!(places.float(4), Where::Stack(48));
assert_eq!(places.size(), 56);
}
#[test]
fn apple_packs_the_arguments_that_got_no_register_at_their_own_size() {
let mut regs = convention(false, 0);
regs.abi = &rucc_abi::abis::DARWIN_ARM64;
let mut places = Places::new(®s);
assert_eq!(places.integer(4), Where::Reg(PhysReg::new(0)));
assert_eq!(places.integer(4), Where::Reg(PhysReg::new(1)));
assert_eq!(places.integer(1), Where::Stack(0));
assert_eq!(places.integer(2), Where::Stack(2));
assert_eq!(places.integer(4), Where::Stack(4));
assert_eq!(places.float(8), Where::Reg(PhysReg::new(10)));
assert_eq!(places.float(4), Where::Reg(PhysReg::new(11)));
assert_eq!(places.float(4), Where::Stack(8));
assert_eq!(places.integer(8), Where::Stack(16));
assert_eq!(places.size(), 24);
assert_eq!(places.object(12, 4), Where::Stack(24));
assert_eq!(places.object(3, 8), Where::Stack(40));
assert_eq!(places.size(), 48);
assert_eq!(places.on_stack(1, 1), Where::Stack(48));
assert_eq!(places.size(), 56);
}
#[test]
fn draining_one_kind_of_register_sends_the_rest_of_that_kind_to_memory_and_no_other() {
let regs = convention(false, 0);
let mut places = Places::new(®s);
assert_eq!(places.float(4), Where::Reg(PhysReg::new(10)));
places.drain_floats();
assert_eq!(places.float(4), Where::Stack(0));
assert_eq!(places.integer(8), Where::Reg(PhysReg::new(0)));
places.drain_integers();
assert_eq!(places.integer(8), Where::Stack(8));
assert_eq!((places.integers(), places.floats()), (2, 2));
}
}