use crate::compiler::baseline::masm_x64::Reg64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AbiX64 {
SysV,
Win64,
}
pub const NATIVE_ABI: AbiX64 = if cfg!(target_os = "windows") {
AbiX64::Win64
} else {
AbiX64::SysV
};
impl AbiX64 {
pub const fn entry_arg_register_file(self) -> Reg64 {
match self {
AbiX64::SysV => Reg64::Rdi,
AbiX64::Win64 => Reg64::Rcx,
}
}
pub const fn entry_arg_closure_context(self) -> Reg64 {
match self {
AbiX64::SysV => Reg64::Rsi,
AbiX64::Win64 => Reg64::Rdx,
}
}
pub const fn helper_arg_register(self, i: usize) -> Option<Reg64> {
match (self, i) {
(AbiX64::SysV, 0) => Some(Reg64::Rdi),
(AbiX64::SysV, 1) => Some(Reg64::Rsi),
(AbiX64::SysV, 2) => Some(Reg64::Rdx),
(AbiX64::SysV, 3) => Some(Reg64::Rcx),
(AbiX64::SysV, 4) => Some(Reg64::R8),
(AbiX64::SysV, 5) => Some(Reg64::R9),
(AbiX64::Win64, 0) => Some(Reg64::Rcx),
(AbiX64::Win64, 1) => Some(Reg64::Rdx),
(AbiX64::Win64, 2) => Some(Reg64::R8),
(AbiX64::Win64, 3) => Some(Reg64::R9),
_ => None,
}
}
pub const fn helper_arg_register_count(self) -> usize {
match self {
AbiX64::SysV => 6,
AbiX64::Win64 => 4,
}
}
pub const fn shadow_space_bytes(self) -> i32 {
match self {
AbiX64::SysV => 0,
AbiX64::Win64 => 32,
}
}
pub const fn helper_call_pre_stack_adjust(self) -> i32 {
self.shadow_space_bytes()
}
pub const fn entry_call_pre_stack_adjust(self) -> i32 {
self.shadow_space_bytes()
}
pub const fn call_site_stack_alignment(self) -> i32 {
16
}
pub const fn helper_stack_arg_count(self, total_args: usize) -> usize {
let regs = self.helper_arg_register_count();
if total_args > regs {
total_args - regs
} else {
0
}
}
pub const fn helper_stack_arg_offset(self, i: usize) -> i32 {
let regs = self.helper_arg_register_count();
self.shadow_space_bytes() + ((i - regs) as i32) * 8
}
pub const fn helper_call_pre_stack_adjust_for(self, total_args: usize) -> i32 {
let raw = self.shadow_space_bytes() + (self.helper_stack_arg_count(total_args) as i32) * 8;
let align = self.call_site_stack_alignment();
(raw + align - 1) & !(align - 1)
}
pub const fn extra_entry_callee_saved(self) -> &'static [Reg64] {
match self {
AbiX64::SysV => &[],
AbiX64::Win64 => &[Reg64::Rdi, Reg64::Rsi],
}
}
pub const fn entry_must_preserve_rdi_rsi(self) -> bool {
matches!(self, AbiX64::Win64)
}
pub const fn maglev_allocatable_registers(self) -> &'static [Reg64] {
const BANK: &[Reg64] = &[
Reg64::Rbx,
Reg64::Rcx,
Reg64::Rdx,
Reg64::Rsi,
Reg64::R8,
Reg64::R9,
Reg64::R13,
Reg64::R12,
Reg64::R15,
];
let _ = self;
BANK
}
pub const fn maglev_register_bank_size(self) -> u32 {
self.maglev_allocatable_registers().len() as u32
}
pub const fn maglev_caller_saved_indices(self) -> &'static [u32] {
match self {
AbiX64::SysV => &[1, 2, 3, 4, 5],
AbiX64::Win64 => &[1, 2, 4, 5],
}
}
pub const fn maglev_caller_saved_mask(self) -> u8 {
match self {
AbiX64::SysV => (1u8 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5),
AbiX64::Win64 => (1u8 << 1) | (1 << 2) | (1 << 4) | (1 << 5),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sysv_entry_args_are_rdi_rsi() {
assert_eq!(AbiX64::SysV.entry_arg_register_file(), Reg64::Rdi);
assert_eq!(AbiX64::SysV.entry_arg_closure_context(), Reg64::Rsi);
}
#[test]
fn win64_entry_args_are_rcx_rdx() {
assert_eq!(AbiX64::Win64.entry_arg_register_file(), Reg64::Rcx);
assert_eq!(AbiX64::Win64.entry_arg_closure_context(), Reg64::Rdx);
}
#[test]
fn sysv_helper_args_match_amd64_psabi() {
let abi = AbiX64::SysV;
assert_eq!(abi.helper_arg_register(0), Some(Reg64::Rdi));
assert_eq!(abi.helper_arg_register(1), Some(Reg64::Rsi));
assert_eq!(abi.helper_arg_register(2), Some(Reg64::Rdx));
assert_eq!(abi.helper_arg_register(3), Some(Reg64::Rcx));
assert_eq!(abi.helper_arg_register(4), Some(Reg64::R8));
assert_eq!(abi.helper_arg_register(5), Some(Reg64::R9));
assert_eq!(abi.helper_arg_register(6), None);
assert_eq!(abi.helper_arg_register_count(), 6);
}
#[test]
fn win64_helper_args_match_msft_x64() {
let abi = AbiX64::Win64;
assert_eq!(abi.helper_arg_register(0), Some(Reg64::Rcx));
assert_eq!(abi.helper_arg_register(1), Some(Reg64::Rdx));
assert_eq!(abi.helper_arg_register(2), Some(Reg64::R8));
assert_eq!(abi.helper_arg_register(3), Some(Reg64::R9));
assert_eq!(abi.helper_arg_register(4), None);
assert_eq!(abi.helper_arg_register_count(), 4);
}
#[test]
fn shadow_space_only_on_win64() {
assert_eq!(AbiX64::SysV.shadow_space_bytes(), 0);
assert_eq!(AbiX64::Win64.shadow_space_bytes(), 32);
assert_eq!(
AbiX64::Win64.helper_call_pre_stack_adjust(),
AbiX64::Win64.shadow_space_bytes()
);
assert_eq!(AbiX64::SysV.entry_call_pre_stack_adjust(), 0);
assert_eq!(
AbiX64::Win64.entry_call_pre_stack_adjust(),
AbiX64::Win64.shadow_space_bytes()
);
}
#[test]
fn call_sites_are_16_byte_aligned_under_both_abis() {
assert_eq!(AbiX64::SysV.call_site_stack_alignment(), 16);
assert_eq!(AbiX64::Win64.call_site_stack_alignment(), 16);
}
#[test]
fn helper_stack_arg_count_matches_register_capacity() {
for n in 0..=6 {
assert_eq!(AbiX64::SysV.helper_stack_arg_count(n), 0);
}
assert_eq!(AbiX64::SysV.helper_stack_arg_count(7), 1);
assert_eq!(AbiX64::SysV.helper_stack_arg_count(8), 2);
for n in 0..=4 {
assert_eq!(AbiX64::Win64.helper_stack_arg_count(n), 0);
}
assert_eq!(AbiX64::Win64.helper_stack_arg_count(5), 1);
assert_eq!(AbiX64::Win64.helper_stack_arg_count(7), 3);
assert_eq!(AbiX64::Win64.helper_stack_arg_count(8), 4);
}
#[test]
fn helper_stack_arg_offset_layout() {
assert_eq!(AbiX64::SysV.helper_stack_arg_offset(6), 0);
assert_eq!(AbiX64::SysV.helper_stack_arg_offset(7), 8);
assert_eq!(AbiX64::Win64.helper_stack_arg_offset(4), 32);
assert_eq!(AbiX64::Win64.helper_stack_arg_offset(5), 40);
assert_eq!(AbiX64::Win64.helper_stack_arg_offset(6), 48);
assert_eq!(AbiX64::Win64.helper_stack_arg_offset(7), 56);
}
#[test]
fn helper_call_pre_stack_adjust_for_total_args() {
for n in 0..=6 {
assert_eq!(AbiX64::SysV.helper_call_pre_stack_adjust_for(n), 0);
}
assert_eq!(AbiX64::SysV.helper_call_pre_stack_adjust_for(7), 16);
assert_eq!(AbiX64::SysV.helper_call_pre_stack_adjust_for(8), 16);
assert_eq!(AbiX64::SysV.helper_call_pre_stack_adjust_for(9), 32);
for n in 0..=4 {
assert_eq!(
AbiX64::Win64.helper_call_pre_stack_adjust_for(n),
AbiX64::Win64.shadow_space_bytes()
);
}
assert_eq!(AbiX64::Win64.helper_call_pre_stack_adjust_for(5), 48); assert_eq!(AbiX64::Win64.helper_call_pre_stack_adjust_for(6), 48); assert_eq!(AbiX64::Win64.helper_call_pre_stack_adjust_for(7), 64); assert_eq!(AbiX64::Win64.helper_call_pre_stack_adjust_for(8), 64); assert_eq!(AbiX64::Win64.helper_call_pre_stack_adjust_for(9), 80); }
#[test]
fn helper_call_pre_stack_adjust_for_is_16_byte_aligned() {
for abi in [AbiX64::SysV, AbiX64::Win64] {
for n in 0..=10 {
let adj = abi.helper_call_pre_stack_adjust_for(n);
assert_eq!(
adj % abi.call_site_stack_alignment(),
0,
"pre-call adjust must preserve 16-byte alignment \
(abi = {:?}, total_args = {}, adj = {})",
abi,
n,
adj
);
}
}
}
#[test]
fn pre_stack_adjust_for_matches_simple_helper_adjust_when_no_stack_args() {
for abi in [AbiX64::SysV, AbiX64::Win64] {
let regs = abi.helper_arg_register_count();
for n in 0..=regs {
assert_eq!(
abi.helper_call_pre_stack_adjust_for(n),
abi.helper_call_pre_stack_adjust(),
"no-stack-arg call must reserve only the shadow space \
(abi = {:?}, total_args = {})",
abi,
n
);
}
}
}
#[test]
fn win64_adds_rdi_rsi_to_callee_saved_set() {
assert!(AbiX64::SysV.extra_entry_callee_saved().is_empty());
assert_eq!(
AbiX64::Win64.extra_entry_callee_saved(),
&[Reg64::Rdi, Reg64::Rsi]
);
assert!(!AbiX64::SysV.entry_must_preserve_rdi_rsi());
assert!(AbiX64::Win64.entry_must_preserve_rdi_rsi());
}
#[test]
fn extra_entry_callee_saved_has_even_cardinality() {
for abi in [AbiX64::SysV, AbiX64::Win64] {
let saved = abi.extra_entry_callee_saved();
assert_eq!(
saved.len() % 2,
0,
"extra_entry_callee_saved() must have even length to preserve \
16-byte stack alignment in JIT prologues (abi = {:?}, set = {:?})",
abi,
saved
);
}
}
#[test]
fn extra_entry_callee_saved_disjoint_from_fixed_callee_saved() {
let fixed = [
Reg64::Rbp,
Reg64::Rbx,
Reg64::R12,
Reg64::R13,
Reg64::R14,
Reg64::R15,
];
for abi in [AbiX64::SysV, AbiX64::Win64] {
for reg in abi.extra_entry_callee_saved() {
assert!(
!fixed.contains(reg),
"{:?} must not appear in extra_entry_callee_saved() for {:?} \
(it is already in the fixed callee-saved push block)",
reg,
abi
);
}
}
}
#[test]
fn native_abi_matches_target_os() {
if cfg!(target_os = "windows") {
assert_eq!(NATIVE_ABI, AbiX64::Win64);
} else {
assert_eq!(NATIVE_ABI, AbiX64::SysV);
}
}
#[test]
fn entry_arg_helpers_are_const_evaluable() {
const SYSV_RF: Reg64 = AbiX64::SysV.entry_arg_register_file();
const WIN_RF: Reg64 = AbiX64::Win64.entry_arg_register_file();
const NATIVE_RF: Reg64 = NATIVE_ABI.entry_arg_register_file();
assert_eq!(SYSV_RF, Reg64::Rdi);
assert_eq!(WIN_RF, Reg64::Rcx);
assert_eq!(
NATIVE_RF,
if cfg!(target_os = "windows") {
Reg64::Rcx
} else {
Reg64::Rdi
}
);
}
#[test]
fn maglev_bank_layout_is_stable_for_both_abis() {
let expected: &[Reg64] = &[
Reg64::Rbx,
Reg64::Rcx,
Reg64::Rdx,
Reg64::Rsi,
Reg64::R8,
Reg64::R9,
Reg64::R13,
Reg64::R12,
Reg64::R15,
];
for abi in [AbiX64::SysV, AbiX64::Win64] {
assert_eq!(
abi.maglev_allocatable_registers(),
expected,
"Maglev bank changed for {:?} — review caller-saved \
indices and codegen `phys_reg` mapping together",
abi
);
assert_eq!(abi.maglev_register_bank_size(), expected.len() as u32);
}
}
#[test]
fn maglev_caller_saved_sysv_matches_legacy_indices_1_to_5() {
let abi = AbiX64::SysV;
assert_eq!(abi.maglev_caller_saved_indices(), &[1, 2, 3, 4, 5]);
assert_eq!(
abi.maglev_caller_saved_mask(),
(1u8 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5)
);
}
#[test]
fn maglev_caller_saved_win64_excludes_rsi() {
let abi = AbiX64::Win64;
let bank = abi.maglev_allocatable_registers();
let indices = abi.maglev_caller_saved_indices();
assert_eq!(bank[3], Reg64::Rsi);
assert!(
!indices.contains(&3),
"Win64 maglev_caller_saved_indices must exclude RSI (index 3): {:?}",
indices
);
assert_eq!(indices, &[1, 2, 4, 5]);
let expected_mask = (1u8 << 1) | (1 << 2) | (1 << 4) | (1 << 5);
assert_eq!(abi.maglev_caller_saved_mask(), expected_mask);
assert_eq!(abi.maglev_caller_saved_mask() & (1u8 << 3), 0);
for &i in indices {
let r = bank[i as usize];
assert!(
matches!(r, Reg64::Rcx | Reg64::Rdx | Reg64::R8 | Reg64::R9),
"bank[{}] = {:?} is not a Win64 volatile integer reg",
i,
r
);
}
}
#[test]
fn maglev_caller_saved_mask_matches_indices() {
for abi in [AbiX64::SysV, AbiX64::Win64] {
let bank_size = abi.maglev_register_bank_size();
let mut from_indices: u8 = 0;
for &i in abi.maglev_caller_saved_indices() {
assert!(
i < bank_size,
"caller-saved index {} out of bank ({}) for {:?}",
i,
bank_size,
abi
);
assert!(
i < 8,
"caller-saved index {} would not fit in u8 mask for {:?}",
i,
abi
);
from_indices |= 1u8 << i;
}
assert_eq!(
from_indices,
abi.maglev_caller_saved_mask(),
"indices/mask disagree for {:?}",
abi
);
}
}
#[test]
fn maglev_caller_saved_does_not_overlap_callee_saved() {
let universal_callee_saved = [Reg64::Rbx, Reg64::R12, Reg64::R13, Reg64::R14, Reg64::R15];
for abi in [AbiX64::SysV, AbiX64::Win64] {
let bank = abi.maglev_allocatable_registers();
for &i in abi.maglev_caller_saved_indices() {
let r = bank[i as usize];
assert!(
!universal_callee_saved.contains(&r),
"{:?} bank[{}] = {:?} is callee-saved on both ABIs and \
must not appear in maglev_caller_saved_indices()",
abi,
i,
r
);
}
}
let bank = AbiX64::Win64.maglev_allocatable_registers();
for &i in AbiX64::Win64.maglev_caller_saved_indices() {
let r = bank[i as usize];
assert!(
r != Reg64::Rdi && r != Reg64::Rsi,
"Win64 bank[{}] = {:?} is callee-saved on Win64",
i,
r
);
}
}
#[test]
fn maglev_bank_helpers_are_const_evaluable() {
const SYSV_BANK_LEN: u32 = AbiX64::SysV.maglev_register_bank_size();
const WIN_BANK_LEN: u32 = AbiX64::Win64.maglev_register_bank_size();
const SYSV_MASK: u8 = AbiX64::SysV.maglev_caller_saved_mask();
const WIN_MASK: u8 = AbiX64::Win64.maglev_caller_saved_mask();
const NATIVE_MASK: u8 = NATIVE_ABI.maglev_caller_saved_mask();
assert_eq!(SYSV_BANK_LEN, 9);
assert_eq!(WIN_BANK_LEN, 9);
assert_ne!(SYSV_MASK, 0);
assert_ne!(WIN_MASK, 0);
assert!(NATIVE_MASK == SYSV_MASK || NATIVE_MASK == WIN_MASK);
}
}