use alloc::vec::Vec;
use core::fmt;
use super::Target;
use crate::regs::{PReg, RegSet};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ArgClass {
Int,
Float,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ArgLoc {
Reg(PReg),
Stack(u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AbiError {
TooManyReturns {
index: usize,
},
}
impl fmt::Display for AbiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AbiError::TooManyReturns { index } => write!(
f,
"return value {index} does not fit the return registers; return it through memory"
),
}
}
}
impl core::error::Error for AbiError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RegSeq {
int: &'static [PReg],
float: &'static [PReg],
positional: bool,
}
impl RegSeq {
#[must_use]
pub const fn new(int: &'static [PReg], float: &'static [PReg], positional: bool) -> RegSeq {
RegSeq {
int,
float,
positional,
}
}
#[inline]
#[must_use]
pub const fn int(&self) -> &'static [PReg] {
self.int
}
#[inline]
#[must_use]
pub const fn float(&self) -> &'static [PReg] {
self.float
}
#[inline]
#[must_use]
pub const fn positional(&self) -> bool {
self.positional
}
fn pick(
&self,
class: ArgClass,
index: usize,
ints: &mut usize,
floats: &mut usize,
) -> Option<PReg> {
let (list, counter) = match class {
ArgClass::Int => (self.int, ints),
ArgClass::Float => (self.float, floats),
};
let slot = if self.positional { index } else { *counter };
let reg = list.get(slot).copied();
if reg.is_some() {
*counter += 1;
}
reg
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StackRules {
align: u32,
arg_slot: u32,
shadow_space: u32,
red_zone: u32,
probe_interval: u32,
}
impl StackRules {
#[must_use]
pub const fn new(
align: u32,
arg_slot: u32,
shadow_space: u32,
red_zone: u32,
probe_interval: u32,
) -> StackRules {
StackRules {
align,
arg_slot,
shadow_space,
red_zone,
probe_interval,
}
}
#[inline]
#[must_use]
pub const fn align(&self) -> u32 {
self.align
}
#[inline]
#[must_use]
pub const fn arg_slot(&self) -> u32 {
self.arg_slot
}
#[inline]
#[must_use]
pub const fn shadow_space(&self) -> u32 {
self.shadow_space
}
#[inline]
#[must_use]
pub const fn red_zone(&self) -> u32 {
self.red_zone
}
#[inline]
#[must_use]
pub const fn probe_interval(&self) -> u32 {
self.probe_interval
}
}
#[derive(Debug)]
pub struct Abi {
name: &'static str,
target: &'static Target,
args: RegSeq,
rets: RegSeq,
callee_saved: RegSet,
stack: StackRules,
}
impl Abi {
#[must_use]
pub const fn new(
name: &'static str,
target: &'static Target,
args: RegSeq,
rets: RegSeq,
callee_saved: RegSet,
stack: StackRules,
) -> Abi {
Abi {
name,
target,
args,
rets,
callee_saved,
stack,
}
}
#[inline]
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[inline]
#[must_use]
pub const fn target(&self) -> &'static Target {
self.target
}
#[inline]
#[must_use]
pub const fn args(&self) -> &RegSeq {
&self.args
}
#[inline]
#[must_use]
pub const fn rets(&self) -> &RegSeq {
&self.rets
}
#[inline]
#[must_use]
pub const fn callee_saved(&self) -> RegSet {
self.callee_saved
}
#[inline]
#[must_use]
pub const fn stack(&self) -> &StackRules {
&self.stack
}
#[must_use]
pub fn caller_saved(&self) -> RegSet {
self.target
.classes()
.iter()
.fold(RegSet::EMPTY, |acc, c| acc.union(c.allocatable()))
.difference(self.callee_saved)
}
#[must_use]
pub fn call_clobbers(&self) -> RegSet {
match self.target.flags() {
Some(flags) => self.caller_saved().with(flags),
None => self.caller_saved(),
}
}
pub fn assign_args(&self, args: &[ArgClass], out: &mut Vec<ArgLoc>) -> u32 {
out.reserve(args.len());
let (mut ints, mut floats) = (0, 0);
let mut offset = self.stack.shadow_space;
for (i, &class) in args.iter().enumerate() {
match self.args.pick(class, i, &mut ints, &mut floats) {
Some(reg) => out.push(ArgLoc::Reg(reg)),
None => {
out.push(ArgLoc::Stack(offset));
offset = offset.saturating_add(self.stack.arg_slot);
}
}
}
offset
}
pub fn assign_rets(&self, rets: &[ArgClass], out: &mut Vec<ArgLoc>) -> Result<(), AbiError> {
let start = out.len();
let (mut ints, mut floats) = (0, 0);
for (i, &class) in rets.iter().enumerate() {
match self.rets.pick(class, i, &mut ints, &mut floats) {
Some(reg) => out.push(ArgLoc::Reg(reg)),
None => {
out.truncate(start);
return Err(AbiError::TooManyReturns { index: i });
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use super::*;
use crate::x86_64::*;
fn args(abi: &Abi, classes: &[ArgClass]) -> (Vec<ArgLoc>, u32) {
let mut out = Vec::new();
let n = abi.assign_args(classes, &mut out);
(out, n)
}
#[test]
fn test_sysv_args_interleaved_int_and_float_counters_are_independent() {
use ArgClass::{Float as F, Int as I};
let (locs, stack) = args(&SYSV, &[F, I, F, I, I, I, I, I, I, F]);
let regs = |r: &[PReg]| r.iter().map(|&r| ArgLoc::Reg(r)).collect::<Vec<_>>();
assert_eq!(
locs,
[
regs(&[XMM0, RDI, XMM1, RSI, RDX, RCX, R8, R9]),
vec![ArgLoc::Stack(0), ArgLoc::Reg(XMM2)]
]
.concat()
);
assert_eq!(stack, 8);
}
#[test]
fn test_sysv_float_args_spill_after_eight() {
let (locs, stack) = args(&SYSV, &[ArgClass::Float; 10]);
assert_eq!(locs[7], ArgLoc::Reg(XMM7));
assert_eq!(locs[8], ArgLoc::Stack(0));
assert_eq!(locs[9], ArgLoc::Stack(8));
assert_eq!(stack, 16);
}
#[test]
fn test_win64_positional_and_shadow_space() {
use ArgClass::{Float as F, Int as I};
let (locs, stack) = args(&WIN64, &[F, F, I, F, I, F]);
assert_eq!(
locs,
[
ArgLoc::Reg(XMM0),
ArgLoc::Reg(XMM1),
ArgLoc::Reg(R8),
ArgLoc::Reg(XMM3),
ArgLoc::Stack(32),
ArgLoc::Stack(40)
]
);
assert_eq!(stack, 48);
assert_eq!(args(&WIN64, &[]).1, 32);
assert_eq!(args(&SYSV, &[]).1, 0);
}
#[test]
fn test_returns_per_convention() {
let mut out = Vec::new();
assert_eq!(SYSV.assign_rets(&[ArgClass::Int; 2], &mut out), Ok(()));
assert_eq!(out, [ArgLoc::Reg(RAX), ArgLoc::Reg(RDX)]);
out.clear();
assert_eq!(
SYSV.assign_rets(&[ArgClass::Int; 3], &mut out),
Err(AbiError::TooManyReturns { index: 2 })
);
assert!(out.is_empty());
assert_eq!(WIN64.assign_rets(&[ArgClass::Float], &mut out), Ok(()));
assert_eq!(out, [ArgLoc::Reg(XMM0)]);
out.clear();
assert_eq!(
WIN64.assign_rets(&[ArgClass::Float, ArgClass::Int], &mut out),
Err(AbiError::TooManyReturns { index: 1 })
);
assert_eq!(
AbiError::TooManyReturns { index: 1 }.to_string(),
"return value 1 does not fit the return registers; return it through memory"
);
}
#[test]
fn test_callee_saved_sets_match_the_published_conventions() {
let sysv: Vec<_> = SYSV.callee_saved().iter().collect();
assert_eq!(sysv, [RBX, RBP, R12, R13, R14, R15]);
let win: Vec<_> = WIN64.callee_saved().iter().collect();
assert_eq!(
win,
[
RBX, RBP, RSI, RDI, R12, R13, R14, R15, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11,
XMM12, XMM13, XMM14, XMM15
]
);
for abi in [&SYSV, &WIN64] {
let c = abi.call_clobbers();
assert!(!c.contains(RSP));
assert!(c.contains(FLAGS));
assert!(c.intersect(abi.callee_saved()).is_empty());
}
assert_eq!(SYSV.call_clobbers().len(), 9 + 16 + 1);
assert_eq!(WIN64.call_clobbers().len(), 7 + 6 + 1);
}
#[test]
fn test_stack_offsets_saturate_instead_of_wrapping() {
let many = vec![ArgClass::Int; 1 << 16];
let (locs, stack) = args(&SYSV, &many);
assert_eq!(locs.len(), 1 << 16);
assert_eq!(stack, 8 * ((1 << 16) - 6));
}
}