use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum X86Reg {
RAX,
RCX,
RDX,
RBX,
RSP,
RBP,
RSI,
RDI,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
EAX,
ECX,
EDX,
EBX,
ESP,
EBP,
ESI,
EDI,
AX,
CX,
DX,
BX,
SP,
BP,
SI,
DI,
AL,
CL,
DL,
BL,
AH,
CH,
DH,
BH,
SPL,
BPL,
SIL,
DIL,
R8B,
R9B,
R10B,
R11B,
R12B,
R13B,
R14B,
R15B,
XMM0,
XMM1,
XMM2,
XMM3,
XMM4,
XMM5,
XMM6,
XMM7,
XMM8,
XMM9,
XMM10,
XMM11,
XMM12,
XMM13,
XMM14,
XMM15,
XMM16,
XMM17,
XMM18,
XMM19,
XMM20,
XMM21,
XMM22,
XMM23,
XMM24,
XMM25,
XMM26,
XMM27,
XMM28,
XMM29,
XMM30,
XMM31,
YMM0,
YMM1,
YMM2,
YMM3,
YMM4,
YMM5,
YMM6,
YMM7,
YMM8,
YMM9,
YMM10,
YMM11,
YMM12,
YMM13,
YMM14,
YMM15,
YMM16,
YMM17,
YMM18,
YMM19,
YMM20,
YMM21,
YMM22,
YMM23,
YMM24,
YMM25,
YMM26,
YMM27,
YMM28,
YMM29,
YMM30,
YMM31,
ZMM0,
ZMM1,
ZMM2,
ZMM3,
ZMM4,
ZMM5,
ZMM6,
ZMM7,
ZMM8,
ZMM9,
ZMM10,
ZMM11,
ZMM12,
ZMM13,
ZMM14,
ZMM15,
ZMM16,
ZMM17,
ZMM18,
ZMM19,
ZMM20,
ZMM21,
ZMM22,
ZMM23,
ZMM24,
ZMM25,
ZMM26,
ZMM27,
ZMM28,
ZMM29,
ZMM30,
ZMM31,
K0,
K1,
K2,
K3,
K4,
K5,
K6,
K7,
ST0,
ST1,
ST2,
ST3,
ST4,
ST5,
ST6,
ST7,
CS,
DS,
ES,
SS,
FS,
GS,
CR0,
CR2,
CR3,
CR4,
DR0,
DR1,
DR2,
DR3,
DR6,
DR7,
MM0,
MM1,
MM2,
MM3,
MM4,
MM5,
MM6,
MM7,
BND0,
BND1,
BND2,
BND3,
TMM0,
TMM1,
TMM2,
TMM3,
TMM4,
TMM5,
TMM6,
TMM7,
}
impl X86Reg {
pub fn is_gpr(&self) -> bool {
matches!(
self,
Self::RAX
| Self::RCX
| Self::RDX
| Self::RBX
| Self::RSP
| Self::RBP
| Self::RSI
| Self::RDI
| Self::R8
| Self::R9
| Self::R10
| Self::R11
| Self::R12
| Self::R13
| Self::R14
| Self::R15
| Self::EAX
| Self::ECX
| Self::EDX
| Self::EBX
| Self::ESP
| Self::EBP
| Self::ESI
| Self::EDI
| Self::AX
| Self::CX
| Self::DX
| Self::BX
| Self::SP
| Self::BP
| Self::SI
| Self::DI
| Self::AL
| Self::CL
| Self::DL
| Self::BL
| Self::AH
| Self::CH
| Self::DH
| Self::BH
| Self::SPL
| Self::BPL
| Self::SIL
| Self::DIL
| Self::R8B
| Self::R9B
| Self::R10B
| Self::R11B
| Self::R12B
| Self::R13B
| Self::R14B
| Self::R15B
)
}
pub fn is_xmm(&self) -> bool {
matches!(
*self,
Self::XMM0
| Self::XMM1
| Self::XMM2
| Self::XMM3
| Self::XMM4
| Self::XMM5
| Self::XMM6
| Self::XMM7
| Self::XMM8
| Self::XMM9
| Self::XMM10
| Self::XMM11
| Self::XMM12
| Self::XMM13
| Self::XMM14
| Self::XMM15
| Self::XMM16
| Self::XMM17
| Self::XMM18
| Self::XMM19
| Self::XMM20
| Self::XMM21
| Self::XMM22
| Self::XMM23
| Self::XMM24
| Self::XMM25
| Self::XMM26
| Self::XMM27
| Self::XMM28
| Self::XMM29
| Self::XMM30
| Self::XMM31
)
}
pub fn is_ymm(&self) -> bool {
matches!(
*self,
Self::YMM0
| Self::YMM1
| Self::YMM2
| Self::YMM3
| Self::YMM4
| Self::YMM5
| Self::YMM6
| Self::YMM7
| Self::YMM8
| Self::YMM9
| Self::YMM10
| Self::YMM11
| Self::YMM12
| Self::YMM13
| Self::YMM14
| Self::YMM15
| Self::YMM16
| Self::YMM17
| Self::YMM18
| Self::YMM19
| Self::YMM20
| Self::YMM21
| Self::YMM22
| Self::YMM23
| Self::YMM24
| Self::YMM25
| Self::YMM26
| Self::YMM27
| Self::YMM28
| Self::YMM29
| Self::YMM30
| Self::YMM31
)
}
pub fn is_zmm(&self) -> bool {
matches!(
*self,
Self::ZMM0
| Self::ZMM1
| Self::ZMM2
| Self::ZMM3
| Self::ZMM4
| Self::ZMM5
| Self::ZMM6
| Self::ZMM7
| Self::ZMM8
| Self::ZMM9
| Self::ZMM10
| Self::ZMM11
| Self::ZMM12
| Self::ZMM13
| Self::ZMM14
| Self::ZMM15
| Self::ZMM16
| Self::ZMM17
| Self::ZMM18
| Self::ZMM19
| Self::ZMM20
| Self::ZMM21
| Self::ZMM22
| Self::ZMM23
| Self::ZMM24
| Self::ZMM25
| Self::ZMM26
| Self::ZMM27
| Self::ZMM28
| Self::ZMM29
| Self::ZMM30
| Self::ZMM31
)
}
pub fn is_vector(&self) -> bool {
self.is_xmm() || self.is_ymm() || self.is_zmm()
}
pub fn is_mask(&self) -> bool {
matches!(
*self,
Self::K0 | Self::K1 | Self::K2 | Self::K3 | Self::K4 | Self::K5 | Self::K6 | Self::K7
)
}
pub fn is_x87(&self) -> bool {
matches!(
*self,
Self::ST0
| Self::ST1
| Self::ST2
| Self::ST3
| Self::ST4
| Self::ST5
| Self::ST6
| Self::ST7
)
}
pub fn is_mmx(&self) -> bool {
matches!(
*self,
Self::MM0
| Self::MM1
| Self::MM2
| Self::MM3
| Self::MM4
| Self::MM5
| Self::MM6
| Self::MM7
)
}
pub fn gpr_index(&self) -> Option<usize> {
match self {
Self::RAX | Self::EAX => Some(0),
Self::RCX | Self::ECX => Some(1),
Self::RDX | Self::EDX => Some(2),
Self::RBX | Self::EBX => Some(3),
Self::RSP | Self::ESP => Some(4),
Self::RBP | Self::EBP => Some(5),
Self::RSI | Self::ESI => Some(6),
Self::RDI | Self::EDI => Some(7),
Self::R8 => Some(8),
Self::R9 => Some(9),
Self::R10 => Some(10),
Self::R11 => Some(11),
Self::R12 => Some(12),
Self::R13 => Some(13),
Self::R14 => Some(14),
Self::R15 => Some(15),
_ => None,
}
}
pub fn xmm_index(&self) -> Option<usize> {
match self {
Self::XMM0 => Some(0),
Self::XMM1 => Some(1),
Self::XMM2 => Some(2),
Self::XMM3 => Some(3),
Self::XMM4 => Some(4),
Self::XMM5 => Some(5),
Self::XMM6 => Some(6),
Self::XMM7 => Some(7),
Self::XMM8 => Some(8),
Self::XMM9 => Some(9),
Self::XMM10 => Some(10),
Self::XMM11 => Some(11),
Self::XMM12 => Some(12),
Self::XMM13 => Some(13),
Self::XMM14 => Some(14),
Self::XMM15 => Some(15),
Self::XMM16 => Some(16),
Self::XMM17 => Some(17),
Self::XMM18 => Some(18),
Self::XMM19 => Some(19),
Self::XMM20 => Some(20),
Self::XMM21 => Some(21),
Self::XMM22 => Some(22),
Self::XMM23 => Some(23),
Self::XMM24 => Some(24),
Self::XMM25 => Some(25),
Self::XMM26 => Some(26),
Self::XMM27 => Some(27),
Self::XMM28 => Some(28),
Self::XMM29 => Some(29),
Self::XMM30 => Some(30),
Self::XMM31 => Some(31),
_ => None,
}
}
pub fn mask_index(&self) -> Option<usize> {
match self {
Self::K0 => Some(0),
Self::K1 => Some(1),
Self::K2 => Some(2),
Self::K3 => Some(3),
Self::K4 => Some(4),
Self::K5 => Some(5),
Self::K6 => Some(6),
Self::K7 => Some(7),
_ => None,
}
}
pub fn reg_class(&self) -> RegClass {
if self.is_gpr() {
RegClass::GPR
} else if self.is_xmm() {
RegClass::XMM
} else if self.is_ymm() {
RegClass::YMM
} else if self.is_zmm() {
RegClass::ZMM
} else if self.is_mask() {
RegClass::Mask
} else if self.is_x87() {
RegClass::X87
} else if self.is_mmx() {
RegClass::MMX
} else {
RegClass::Other
}
}
}
impl fmt::Display for X86Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RegClass {
GPR,
XMM,
YMM,
ZMM,
Mask,
X87,
MMX,
Other,
}
impl RegClass {
pub fn size_bytes(&self) -> usize {
match self {
Self::GPR => 8,
Self::XMM => 16,
Self::YMM => 32,
Self::ZMM => 64,
Self::Mask => 8,
Self::X87 => 10,
Self::MMX => 8,
Self::Other => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallingConvention {
Cdecl,
StdCall,
FastCall,
ThisCallMSVC,
ThisCallGNU,
VectorCall,
RegCall,
Pascal,
Borland,
Watcom,
RegParm3,
SysV_AMD64,
Win64,
IntelOCL,
Wine64,
PreserveAll,
PreserveMost,
GHC,
HiPE,
AnyReg,
WebKitJS,
Cold,
Tail,
RegParm(u8),
}
impl CallingConvention {
pub fn name(&self) -> &'static str {
match self {
Self::Cdecl => "cdecl",
Self::StdCall => "stdcall",
Self::FastCall => "fastcall",
Self::ThisCallMSVC => "thiscall_msvc",
Self::ThisCallGNU => "thiscall_gnu",
Self::VectorCall => "vectorcall",
Self::RegCall => "regcall",
Self::Pascal => "pascal",
Self::Borland => "borland",
Self::Watcom => "watcom",
Self::RegParm3 => "regparm3",
Self::SysV_AMD64 => "sysv_amd64",
Self::Win64 => "win64",
Self::IntelOCL => "intel_ocl",
Self::Wine64 => "wine64",
Self::PreserveAll => "preserve_all",
Self::PreserveMost => "preserve_most",
Self::GHC => "ghc",
Self::HiPE => "hipe",
Self::AnyReg => "anyreg",
Self::WebKitJS => "webkit_js",
Self::Cold => "cold",
Self::Tail => "tail",
Self::RegParm(n) => return "regparm", }
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
Self::SysV_AMD64
| Self::Win64
| Self::IntelOCL
| Self::Wine64
| Self::PreserveAll
| Self::PreserveMost
| Self::GHC
| Self::HiPE
| Self::AnyReg
| Self::WebKitJS
| Self::Cold
| Self::Tail
)
}
pub fn is_32bit(&self) -> bool {
!self.is_64bit()
}
pub fn callee_cleans_stack(&self) -> bool {
matches!(
self,
Self::StdCall
| Self::FastCall
| Self::ThisCallMSVC
| Self::VectorCall
| Self::Pascal
| Self::Borland
| Self::Win64
)
}
pub fn uses_register_params(&self) -> bool {
!matches!(
self,
Self::Cdecl | Self::StdCall | Self::Pascal | Self::Borland | Self::Watcom
)
}
pub fn int_param_reg_count(&self) -> usize {
match self {
Self::SysV_AMD64 => 6, Self::Win64 => 4, Self::FastCall => 2, Self::ThisCallMSVC => 1, Self::ThisCallGNU => 1, Self::RegCall => 5, Self::VectorCall => 2, Self::RegParm3 => 3, Self::RegParm(n) => *n as usize,
Self::IntelOCL => 0,
Self::GHC => 0,
Self::HiPE => 0,
Self::PreserveAll => 0,
Self::PreserveMost => 0,
_ => 0,
}
}
pub fn sse_param_reg_count(&self) -> usize {
match self {
Self::SysV_AMD64 => 8, Self::Win64 => 4, Self::VectorCall => 6, Self::RegCall => 8, _ => 0,
}
}
pub fn stack_alignment(&self) -> usize {
match self {
Self::SysV_AMD64 => 16,
Self::Win64 => 16,
Self::IntelOCL => 16,
Self::Wine64 => 16,
Self::VectorCall => 16,
_ => 4, }
}
pub fn red_zone_size(&self) -> usize {
match self {
Self::SysV_AMD64 => 128,
_ => 0,
}
}
pub fn shadow_space_size(&self) -> usize {
match self {
Self::Win64 => 32, _ => 0,
}
}
pub fn has_home_space(&self) -> bool {
self.shadow_space_size() > 0
}
pub fn needs_sret_demotion(&self) -> bool {
matches!(self, Self::SysV_AMD64 | Self::Win64)
}
}
impl fmt::Display for CallingConvention {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ArgLocation {
Register(X86Reg),
RegisterPair(X86Reg, X86Reg),
RegisterTriple(X86Reg, X86Reg, X86Reg),
X87Stack,
Stack {
offset: usize,
size: usize,
alignment: usize,
},
Split {
regs: Vec<X86Reg>,
stack_part: Box<ArgLocation>,
},
Indirect {
ptr_reg: X86Reg,
size: usize,
},
InAlloca {
size: usize,
alignment: usize,
},
MaskRegister(X86Reg),
}
impl ArgLocation {
pub fn is_in_register(&self) -> bool {
matches!(
self,
Self::Register(_)
| Self::RegisterPair(_, _)
| Self::RegisterTriple(_, _, _)
| Self::X87Stack
| Self::Indirect { .. }
| Self::MaskRegister(_)
)
}
pub fn reg_count(&self) -> usize {
match self {
Self::Register(_) => 1,
Self::RegisterPair(_, _) => 2,
Self::RegisterTriple(_, _, _) => 3,
Self::Split { regs, .. } => regs.len(),
Self::Indirect { .. } => 1,
Self::MaskRegister(_) => 1,
_ => 0,
}
}
}
impl fmt::Display for ArgLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Register(r) => write!(f, "reg({})", r),
Self::RegisterPair(r1, r2) => write!(f, "pair({},{})", r1, r2),
Self::Stack {
offset,
size,
alignment: _,
} => write!(f, "stack({},{})", offset, size),
Self::Indirect { ptr_reg, size } => write!(f, "indirect({},{})", ptr_reg, size),
Self::MaskRegister(r) => write!(f, "mask({})", r),
Self::InAlloca { size, alignment: _ } => write!(f, "inalloca({})", size),
_ => write!(f, "argloc"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructPassingRule {
ExpandToFields,
InRegisters,
Indirect,
OnStack,
InAlloca,
}
#[derive(Debug, Clone)]
pub struct ArgClassifyResult {
pub location: ArgLocation,
pub in_regs: Vec<X86Reg>,
pub stack_offset: usize,
pub size: usize,
pub alignment: usize,
pub is_byval: bool,
pub is_sret: bool,
pub is_inalloca: bool,
pub eightbyte_classes: Vec<EightByteClass>,
}
impl Default for ArgClassifyResult {
fn default() -> Self {
Self {
location: ArgLocation::Stack {
offset: 0,
size: 0,
alignment: 0,
},
in_regs: Vec::new(),
stack_offset: 0,
size: 0,
alignment: 0,
is_byval: false,
is_sret: false,
is_inalloca: false,
eightbyte_classes: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EightByteClass {
NoClass,
Integer,
SSE,
SSEUp,
X87,
X87Up,
ComplexX87,
Memory,
}
impl EightByteClass {
pub fn is_register_class(&self) -> bool {
matches!(
self,
Self::Integer | Self::SSE | Self::SSEUp | Self::X87 | Self::X87Up
)
}
pub fn needs_gpr(&self) -> bool {
matches!(self, Self::Integer)
}
pub fn needs_sse(&self) -> bool {
matches!(self, Self::SSE | Self::SSEUp)
}
pub fn gpr_needed(&self) -> usize {
if self.needs_gpr() {
1
} else {
0
}
}
pub fn sse_needed(&self) -> usize {
if self.needs_sse() {
1
} else {
0
}
}
}
impl fmt::Display for EightByteClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoClass => write!(f, "NOCLASS"),
Self::Integer => write!(f, "INTEGER"),
Self::SSE => write!(f, "SSE"),
Self::SSEUp => write!(f, "SSEUP"),
Self::X87 => write!(f, "X87"),
Self::X87Up => write!(f, "X87UP"),
Self::ComplexX87 => write!(f, "COMPLEX_X87"),
Self::Memory => write!(f, "MEMORY"),
}
}
}
#[derive(Debug, Clone)]
pub struct HFADetector {
pub max_hfa_elements: usize,
pub recognized_base_types: HashSet<HfaBaseType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HfaBaseType {
Float,
Double,
Float128,
Float16,
BFloat16,
Vector64,
Vector128,
Vector256,
Vector512,
}
impl HfaBaseType {
pub fn size_bytes(&self) -> usize {
match self {
Self::Float => 4,
Self::Double => 8,
Self::Float128 => 16,
Self::Float16 => 2,
Self::BFloat16 => 2,
Self::Vector64 => 8,
Self::Vector128 => 16,
Self::Vector256 => 32,
Self::Vector512 => 64,
}
}
pub fn from_size_and_kind(size: usize, is_float: bool, is_vector: bool) -> Option<Self> {
match (size, is_float, is_vector) {
(2, true, false) => Some(Self::Float16),
(4, true, false) => Some(Self::Float),
(8, true, false) => Some(Self::Double),
(16, true, false) => Some(Self::Float128),
(8, false, true) => Some(Self::Vector64),
(16, false, true) => Some(Self::Vector128),
(32, false, true) => Some(Self::Vector256),
(64, false, true) => Some(Self::Vector512),
_ => None,
}
}
}
impl Default for HFADetector {
fn default() -> Self {
let mut base_types = HashSet::new();
base_types.insert(HfaBaseType::Float);
base_types.insert(HfaBaseType::Double);
base_types.insert(HfaBaseType::Float128);
base_types.insert(HfaBaseType::Vector128);
base_types.insert(HfaBaseType::Vector256);
Self {
max_hfa_elements: 4,
recognized_base_types: base_types,
}
}
}
impl HFADetector {
pub fn new() -> Self {
Self::default()
}
pub fn detect_hfa(&self, fields: &[AbiFieldInfo]) -> Option<(HfaBaseType, usize)> {
if fields.is_empty() {
return None;
}
let base_type = fields[0].hfa_base_type?;
let base_size = base_type.size_bytes();
let mut count = 0usize;
for field in fields {
if field.hfa_base_type != Some(base_type) {
return None;
}
count += field.size / base_size;
}
if count > 0 && count <= self.max_hfa_elements {
Some((base_type, count))
} else {
None
}
}
pub fn detect_hva(&self, fields: &[AbiFieldInfo]) -> Option<(HfaBaseType, usize)> {
self.detect_hfa(fields).filter(|(t, _)| {
matches!(
t,
HfaBaseType::Vector64
| HfaBaseType::Vector128
| HfaBaseType::Vector256
| HfaBaseType::Vector512
)
})
}
}
#[derive(Debug, Clone)]
pub struct AbiFieldInfo {
pub offset: usize,
pub size: usize,
pub alignment: usize,
pub is_float: bool,
pub is_vector: bool,
pub is_integer: bool,
pub is_pointer: bool,
pub is_complex: bool,
pub hfa_base_type: Option<HfaBaseType>,
pub field_count: usize,
}
impl AbiFieldInfo {
pub fn new(offset: usize, size: usize) -> Self {
Self {
offset,
size,
alignment: size.min(8),
is_float: false,
is_vector: false,
is_integer: true,
is_pointer: false,
is_complex: false,
hfa_base_type: None,
field_count: 1,
}
}
pub fn float_field(offset: usize, size: usize) -> Self {
Self {
offset,
size,
alignment: size,
is_float: true,
is_vector: false,
is_integer: false,
is_pointer: false,
is_complex: false,
hfa_base_type: HfaBaseType::from_size_and_kind(size, true, false),
field_count: 1,
}
}
pub fn vector_field(offset: usize, size: usize) -> Self {
Self {
offset,
size,
alignment: size.min(64),
is_float: false,
is_vector: true,
is_integer: false,
is_pointer: false,
is_complex: false,
hfa_base_type: HfaBaseType::from_size_and_kind(size, false, true),
field_count: 1,
}
}
pub fn pointer_field(offset: usize) -> Self {
Self {
offset,
size: 8,
alignment: 8,
is_float: false,
is_vector: false,
is_integer: false,
is_pointer: true,
is_complex: false,
hfa_base_type: None,
field_count: 1,
}
}
}
#[derive(Debug, Clone)]
pub struct ReturnClassifyResult {
pub location: RetLocation,
pub uses_sret: bool,
pub sret_ptr_reg: Option<X86Reg>,
pub size: usize,
pub is_multi_reg: bool,
pub regs: Vec<X86Reg>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RetLocation {
GPR(X86Reg),
GPRPair(X86Reg, X86Reg),
SSE(X86Reg),
SSEPair(X86Reg, X86Reg),
X87,
ComplexX87,
Mask(X86Reg),
YMM(X86Reg),
ZMM(X86Reg),
Memory,
Void,
}
impl Default for ReturnClassifyResult {
fn default() -> Self {
Self {
location: RetLocation::Void,
uses_sret: false,
sret_ptr_reg: None,
size: 0,
is_multi_reg: false,
regs: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct AbiRegAssignments {
pub convention: CallingConvention,
pub int_arg_regs: Vec<X86Reg>,
pub sse_arg_regs: Vec<X86Reg>,
pub mask_arg_regs: Vec<X86Reg>,
pub ret_gpr: Vec<X86Reg>,
pub ret_sse: Vec<X86Reg>,
pub ret_mask: Vec<X86Reg>,
pub callee_saved_gprs: Vec<X86Reg>,
pub callee_saved_xmms: Vec<X86Reg>,
pub callee_saved_masks: Vec<X86Reg>,
pub caller_saved_gprs: Vec<X86Reg>,
pub caller_saved_xmms: Vec<X86Reg>,
pub caller_saved_masks: Vec<X86Reg>,
pub frame_pointer_reg: X86Reg,
pub stack_pointer_reg: X86Reg,
pub thread_pointer_reg: Option<X86Reg>,
}
impl AbiRegAssignments {
pub fn sysv_amd64() -> Self {
Self {
convention: CallingConvention::SysV_AMD64,
int_arg_regs: vec![
X86Reg::RDI,
X86Reg::RSI,
X86Reg::RDX,
X86Reg::RCX,
X86Reg::R8,
X86Reg::R9,
],
sse_arg_regs: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
],
mask_arg_regs: vec![
X86Reg::K0,
X86Reg::K1,
X86Reg::K2,
X86Reg::K3,
X86Reg::K4,
X86Reg::K5,
X86Reg::K6,
X86Reg::K7,
],
ret_gpr: vec![X86Reg::RAX, X86Reg::RDX],
ret_sse: vec![X86Reg::XMM0, X86Reg::XMM1],
ret_mask: vec![X86Reg::K0],
callee_saved_gprs: vec![
X86Reg::RBX,
X86Reg::RBP,
X86Reg::R12,
X86Reg::R13,
X86Reg::R14,
X86Reg::R15,
],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![
X86Reg::RAX,
X86Reg::RCX,
X86Reg::RDX,
X86Reg::RSI,
X86Reg::RDI,
X86Reg::R8,
X86Reg::R9,
X86Reg::R10,
X86Reg::R11,
],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
X86Reg::XMM8,
X86Reg::XMM9,
X86Reg::XMM10,
X86Reg::XMM11,
X86Reg::XMM12,
X86Reg::XMM13,
X86Reg::XMM14,
X86Reg::XMM15,
],
caller_saved_masks: vec![
X86Reg::K0,
X86Reg::K1,
X86Reg::K2,
X86Reg::K3,
X86Reg::K4,
X86Reg::K5,
X86Reg::K6,
X86Reg::K7,
],
frame_pointer_reg: X86Reg::RBP,
stack_pointer_reg: X86Reg::RSP,
thread_pointer_reg: Some(X86Reg::FS),
}
}
pub fn win64() -> Self {
Self {
convention: CallingConvention::Win64,
int_arg_regs: vec![X86Reg::RCX, X86Reg::RDX, X86Reg::R8, X86Reg::R9],
sse_arg_regs: vec![X86Reg::XMM0, X86Reg::XMM1, X86Reg::XMM2, X86Reg::XMM3],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::RAX],
ret_sse: vec![X86Reg::XMM0],
ret_mask: vec![],
callee_saved_gprs: vec![
X86Reg::RBX,
X86Reg::RBP,
X86Reg::RDI,
X86Reg::RSI,
X86Reg::R12,
X86Reg::R13,
X86Reg::R14,
X86Reg::R15,
],
callee_saved_xmms: vec![
X86Reg::XMM6,
X86Reg::XMM7,
X86Reg::XMM8,
X86Reg::XMM9,
X86Reg::XMM10,
X86Reg::XMM11,
X86Reg::XMM12,
X86Reg::XMM13,
X86Reg::XMM14,
X86Reg::XMM15,
],
callee_saved_masks: vec![],
caller_saved_gprs: vec![
X86Reg::RAX,
X86Reg::RCX,
X86Reg::RDX,
X86Reg::R8,
X86Reg::R9,
X86Reg::R10,
X86Reg::R11,
],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::RBP,
stack_pointer_reg: X86Reg::RSP,
thread_pointer_reg: Some(X86Reg::GS),
}
}
pub fn cdecl_32() -> Self {
Self {
convention: CallingConvention::Cdecl,
int_arg_regs: vec![],
sse_arg_regs: vec![],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0, X86Reg::XMM1],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP, X86Reg::ESI, X86Reg::EDI],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![X86Reg::EAX, X86Reg::ECX, X86Reg::EDX],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
pub fn fastcall_32() -> Self {
Self {
convention: CallingConvention::FastCall,
int_arg_regs: vec![X86Reg::ECX, X86Reg::EDX],
sse_arg_regs: vec![],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP, X86Reg::ESI, X86Reg::EDI],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![X86Reg::EAX, X86Reg::ECX, X86Reg::EDX],
caller_saved_xmms: vec![X86Reg::XMM0, X86Reg::XMM1, X86Reg::XMM2, X86Reg::XMM3],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
pub fn thiscall_msvc_32() -> Self {
Self {
convention: CallingConvention::ThisCallMSVC,
int_arg_regs: vec![X86Reg::ECX], sse_arg_regs: vec![],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP, X86Reg::ESI, X86Reg::EDI],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![X86Reg::EAX, X86Reg::ECX, X86Reg::EDX],
caller_saved_xmms: vec![X86Reg::XMM0, X86Reg::XMM1, X86Reg::XMM2, X86Reg::XMM3],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
pub fn vectorcall_32() -> Self {
Self {
convention: CallingConvention::VectorCall,
int_arg_regs: vec![X86Reg::ECX, X86Reg::EDX],
sse_arg_regs: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0, X86Reg::XMM1],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP, X86Reg::ESI, X86Reg::EDI],
callee_saved_xmms: vec![X86Reg::XMM6, X86Reg::XMM7],
callee_saved_masks: vec![],
caller_saved_gprs: vec![X86Reg::EAX, X86Reg::ECX, X86Reg::EDX],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
pub fn regcall_32() -> Self {
Self {
convention: CallingConvention::RegCall,
int_arg_regs: vec![
X86Reg::EAX,
X86Reg::ECX,
X86Reg::EDX,
X86Reg::EDI,
X86Reg::ESI,
],
sse_arg_regs: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0, X86Reg::XMM1],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![
X86Reg::EAX,
X86Reg::ECX,
X86Reg::EDX,
X86Reg::ESI,
X86Reg::EDI,
],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
pub fn intel_ocl_64() -> Self {
Self {
convention: CallingConvention::IntelOCL,
int_arg_regs: vec![],
sse_arg_regs: vec![],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::RAX],
ret_sse: vec![X86Reg::XMM0],
ret_mask: vec![],
callee_saved_gprs: vec![
X86Reg::RBX,
X86Reg::RBP,
X86Reg::R12,
X86Reg::R13,
X86Reg::R14,
X86Reg::R15,
X86Reg::RSI,
X86Reg::RDI,
],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![
X86Reg::RAX,
X86Reg::RCX,
X86Reg::RDX,
X86Reg::R8,
X86Reg::R9,
X86Reg::R10,
X86Reg::R11,
],
caller_saved_xmms: vec![
X86Reg::XMM0,
X86Reg::XMM1,
X86Reg::XMM2,
X86Reg::XMM3,
X86Reg::XMM4,
X86Reg::XMM5,
X86Reg::XMM6,
X86Reg::XMM7,
],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::RBP,
stack_pointer_reg: X86Reg::RSP,
thread_pointer_reg: None,
}
}
pub fn pascal_32() -> Self {
Self {
convention: CallingConvention::Pascal,
int_arg_regs: vec![],
sse_arg_regs: vec![],
mask_arg_regs: vec![],
ret_gpr: vec![X86Reg::EAX, X86Reg::EDX],
ret_sse: vec![X86Reg::XMM0],
ret_mask: vec![],
callee_saved_gprs: vec![X86Reg::EBX, X86Reg::EBP, X86Reg::ESI, X86Reg::EDI],
callee_saved_xmms: vec![],
callee_saved_masks: vec![],
caller_saved_gprs: vec![X86Reg::EAX, X86Reg::ECX, X86Reg::EDX],
caller_saved_xmms: vec![X86Reg::XMM0, X86Reg::XMM1, X86Reg::XMM2, X86Reg::XMM3],
caller_saved_masks: vec![],
frame_pointer_reg: X86Reg::EBP,
stack_pointer_reg: X86Reg::ESP,
thread_pointer_reg: None,
}
}
}
#[derive(Debug, Clone)]
pub struct VarArgsConfig {
pub convention: CallingConvention,
pub gp_save_size: usize,
pub fp_save_size: usize,
pub num_gp_saved: usize,
pub num_fp_saved: usize,
pub va_list_type: VaListType,
pub overflow_arg_area_reg_save_area: bool,
pub reg_save_area_offset: usize,
pub gp_offset_offset: usize,
pub fp_offset_offset: usize,
pub overflow_arg_area_offset: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VaListType {
SysV,
MsX64,
X86_32,
}
impl VarArgsConfig {
pub fn sysv_amd64() -> Self {
Self {
convention: CallingConvention::SysV_AMD64,
gp_save_size: 48, fp_save_size: 128, num_gp_saved: 6,
num_fp_saved: 8,
va_list_type: VaListType::SysV,
overflow_arg_area_reg_save_area: false,
reg_save_area_offset: 16, gp_offset_offset: 0,
fp_offset_offset: 4,
overflow_arg_area_offset: 8,
}
}
pub fn win64() -> Self {
Self {
convention: CallingConvention::Win64,
gp_save_size: 32, fp_save_size: 0,
num_gp_saved: 4,
num_fp_saved: 0,
va_list_type: VaListType::MsX64,
overflow_arg_area_reg_save_area: false,
reg_save_area_offset: 0,
gp_offset_offset: 0,
fp_offset_offset: 0,
overflow_arg_area_offset: 0,
}
}
pub fn x86_32() -> Self {
Self {
convention: CallingConvention::Cdecl,
gp_save_size: 0,
fp_save_size: 0,
num_gp_saved: 0,
num_fp_saved: 0,
va_list_type: VaListType::X86_32,
overflow_arg_area_reg_save_area: false,
reg_save_area_offset: 0,
gp_offset_offset: 0,
fp_offset_offset: 0,
overflow_arg_area_offset: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct StackFrame {
pub convention: CallingConvention,
pub total_size: usize,
pub local_var_size: usize,
pub callee_saved_size: usize,
pub outgoing_args_size: usize,
pub red_zone_size: usize,
pub shadow_space_size: usize,
pub return_addr_offset: isize,
pub saved_fp_offset: isize,
pub stack_alignment: usize,
pub has_frame_pointer: bool,
pub is_var_sized: bool,
}
impl StackFrame {
pub fn new(
conv: CallingConvention,
local_size: usize,
callee_saved: usize,
outgoing_args: usize,
) -> Self {
let red = conv.red_zone_size();
let shadow = conv.shadow_space_size();
let align = conv.stack_alignment();
let total = local_size + callee_saved + outgoing_args + shadow;
let total_aligned = (total + align - 1) & !(align - 1);
Self {
convention: conv,
total_size: total_aligned,
local_var_size: local_size,
callee_saved_size: callee_saved,
outgoing_args_size: outgoing_args,
red_zone_size: red,
shadow_space_size: shadow,
return_addr_offset: -(total_aligned as isize) - 8,
saved_fp_offset: -(total_aligned as isize),
stack_alignment: align,
has_frame_pointer: true,
is_var_sized: false,
}
}
pub fn with_frame_pointer(mut self, enabled: bool) -> Self {
self.has_frame_pointer = enabled;
self
}
pub fn with_var_sized(mut self, vs: bool) -> Self {
self.is_var_sized = vs;
self
}
pub fn can_use_red_zone(&self) -> bool {
self.red_zone_size > 0 && self.outgoing_args_size == 0
}
}
impl Default for StackFrame {
fn default() -> Self {
Self::new(CallingConvention::SysV_AMD64, 0, 0, 0)
}
}
#[derive(Debug, Clone)]
pub struct ByValConfig {
pub size: usize,
pub alignment: usize,
pub copy_addr_space: usize,
pub is_immutable: bool,
pub uses_hidden_copy: bool,
}
impl Default for ByValConfig {
fn default() -> Self {
Self {
size: 0,
alignment: 1,
copy_addr_space: 0,
is_immutable: false,
uses_hidden_copy: true,
}
}
}
#[derive(Debug, Clone)]
pub struct InAllocaConfig {
pub size: usize,
pub alignment: usize,
pub arg_memory_offsets: Vec<usize>,
}
impl InAllocaConfig {
pub fn new(size: usize, alignment: usize) -> Self {
Self {
size,
alignment,
arg_memory_offsets: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct AbiCompatibilityResult {
pub caller_conv: CallingConvention,
pub callee_conv: CallingConvention,
pub is_compatible: bool,
pub warnings: Vec<String>,
pub errors: Vec<String>,
pub mismatched_params: Vec<AbiParamMismatch>,
}
#[derive(Debug, Clone)]
pub struct AbiParamMismatch {
pub param_index: usize,
pub caller_location: ArgLocation,
pub callee_location: ArgLocation,
pub issue: String,
}
impl AbiCompatibilityResult {
pub fn compatible(caller: CallingConvention, callee: CallingConvention) -> Self {
Self {
caller_conv: caller,
callee_conv: callee,
is_compatible: true,
warnings: Vec::new(),
errors: Vec::new(),
mismatched_params: Vec::new(),
}
}
pub fn incompatible(
caller: CallingConvention,
callee: CallingConvention,
errors: Vec<String>,
) -> Self {
Self {
caller_conv: caller,
callee_conv: callee,
is_compatible: false,
warnings: Vec::new(),
errors,
mismatched_params: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86CallingConvDeep {
pub is_64bit: bool,
pub conventions: HashMap<CallingConvention, AbiRegAssignments>,
pub hfa_detector: HFADetector,
pub varargs_configs: HashMap<CallingConvention, VarArgsConfig>,
pub compatibility_cache:
HashMap<(CallingConvention, CallingConvention), AbiCompatibilityResult>,
pub total_calls_analyzed: usize,
}
impl X86CallingConvDeep {
pub fn new(is_64bit: bool) -> Self {
let mut conventions = HashMap::new();
let mut varargs = HashMap::new();
if is_64bit {
let sysv = AbiRegAssignments::sysv_amd64();
conventions.insert(CallingConvention::SysV_AMD64, sysv);
varargs.insert(CallingConvention::SysV_AMD64, VarArgsConfig::sysv_amd64());
let win = AbiRegAssignments::win64();
conventions.insert(CallingConvention::Win64, win);
varargs.insert(CallingConvention::Win64, VarArgsConfig::win64());
let ocl = AbiRegAssignments::intel_ocl_64();
conventions.insert(CallingConvention::IntelOCL, ocl);
}
let cdecl = AbiRegAssignments::cdecl_32();
conventions.insert(CallingConvention::Cdecl, cdecl);
varargs.insert(CallingConvention::Cdecl, VarArgsConfig::x86_32());
let fastcall = AbiRegAssignments::fastcall_32();
conventions.insert(CallingConvention::FastCall, fastcall);
varargs.insert(CallingConvention::FastCall, VarArgsConfig::x86_32());
let thiscall = AbiRegAssignments::thiscall_msvc_32();
conventions.insert(CallingConvention::ThisCallMSVC, thiscall);
varargs.insert(CallingConvention::ThisCallMSVC, VarArgsConfig::x86_32());
let vc = AbiRegAssignments::vectorcall_32();
conventions.insert(CallingConvention::VectorCall, vc);
varargs.insert(CallingConvention::VectorCall, VarArgsConfig::x86_32());
let regcall = AbiRegAssignments::regcall_32();
conventions.insert(CallingConvention::RegCall, regcall);
varargs.insert(CallingConvention::RegCall, VarArgsConfig::x86_32());
let pascal = AbiRegAssignments::pascal_32();
conventions.insert(CallingConvention::Borland, pascal.clone());
conventions.insert(CallingConvention::Watcom, pascal.clone());
conventions.insert(CallingConvention::Pascal, pascal);
varargs.insert(CallingConvention::Pascal, VarArgsConfig::x86_32());
let thiscall_gnu = AbiRegAssignments::thiscall_msvc_32();
conventions.insert(CallingConvention::ThisCallGNU, thiscall_gnu);
if is_64bit {
conventions.insert(CallingConvention::Wine64, AbiRegAssignments::win64());
varargs.insert(CallingConvention::Wine64, VarArgsConfig::win64());
}
Self {
is_64bit,
conventions,
hfa_detector: HFADetector::new(),
varargs_configs: varargs,
compatibility_cache: HashMap::new(),
total_calls_analyzed: 0,
}
}
pub fn get_reg_assignments(&self, cc: CallingConvention) -> Option<&AbiRegAssignments> {
self.conventions.get(&cc)
}
pub fn register_convention(&mut self, cc: CallingConvention, assignments: AbiRegAssignments) {
self.conventions.insert(cc, assignments);
}
pub fn classify_param(
&self,
cc: CallingConvention,
param_size: usize,
param_align: usize,
is_float: bool,
is_vector: bool,
is_hfa: bool,
field_count: usize,
) -> ArgClassifyResult {
let mut result = ArgClassifyResult::default();
result.size = param_size;
result.alignment = param_align;
let assignments = match self.get_reg_assignments(cc) {
Some(a) => a.clone(),
None => return result,
};
match cc {
CallingConvention::SysV_AMD64 => {
self.classify_sysv(
param_size,
is_float,
is_vector,
is_hfa,
&mut result,
&assignments,
);
}
CallingConvention::Win64 | CallingConvention::Wine64 => {
self.classify_win64(param_size, is_float, is_vector, &mut result, &assignments);
}
CallingConvention::Cdecl
| CallingConvention::StdCall
| CallingConvention::Pascal
| CallingConvention::Borland
| CallingConvention::Watcom => {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
CallingConvention::FastCall => {
if !is_float && !is_vector && param_size <= 4 && result.in_regs.len() < 2 {
result
.in_regs
.push(assignments.int_arg_regs[result.in_regs.len()]);
if result.in_regs.len() == 1 {
result.location = ArgLocation::Register(result.in_regs[0]);
} else {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
}
}
CallingConvention::ThisCallMSVC => {
if !is_float && param_size <= 4 && result.in_regs.is_empty() {
result.in_regs.push(X86Reg::ECX);
result.location = ArgLocation::Register(X86Reg::ECX);
} else {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
}
CallingConvention::VectorCall => {
if is_vector || is_float {
if result.in_regs.len() < assignments.sse_arg_regs.len() {
let reg = assignments.sse_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
}
} else if result.in_regs.len() < assignments.int_arg_regs.len() {
let reg = assignments.int_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
} else {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
}
CallingConvention::RegCall => {
if (is_float || is_vector) && result.in_regs.len() < assignments.sse_arg_regs.len()
{
let reg = assignments.sse_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
} else if result.in_regs.len() < assignments.int_arg_regs.len() {
let reg = assignments.int_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
} else {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
}
_ => {
result.location = ArgLocation::Stack {
offset: 0,
size: param_size,
alignment: param_align,
};
}
}
result
}
fn classify_sysv(
&self,
size: usize,
is_float: bool,
is_vector: bool,
is_hfa: bool,
result: &mut ArgClassifyResult,
_assignments: &AbiRegAssignments,
) {
if size > 16 {
result.location = ArgLocation::Indirect {
ptr_reg: X86Reg::RDI,
size,
};
result.is_byval = true;
return;
}
if is_hfa {
let (base_type, count) = (HfaBaseType::Float, size / 4); let sse_regs: Vec<X86Reg> = (0..count.min(8))
.map(|i| match i {
0 => X86Reg::XMM0,
1 => X86Reg::XMM1,
2 => X86Reg::XMM2,
3 => X86Reg::XMM3,
4 => X86Reg::XMM4,
5 => X86Reg::XMM5,
6 => X86Reg::XMM6,
7 => X86Reg::XMM7,
_ => X86Reg::XMM0,
})
.collect();
result.eightbyte_classes = vec![EightByteClass::SSE; count];
if count == 1 {
result.location = ArgLocation::Register(sse_regs[0]);
} else if count == 2 {
result.location = ArgLocation::RegisterPair(sse_regs[0], sse_regs[1]);
}
return;
}
if is_float {
if size <= 8 {
result.eightbyte_classes = vec![EightByteClass::SSE];
result.location = ArgLocation::Register(X86Reg::XMM0);
} else if size <= 16 {
result.eightbyte_classes = vec![EightByteClass::SSE, EightByteClass::SSEUp];
result.location = ArgLocation::RegisterPair(X86Reg::XMM0, X86Reg::XMM1);
}
return;
}
if is_vector {
if size <= 16 {
result.eightbyte_classes = vec![EightByteClass::SSE];
result.location = ArgLocation::Register(X86Reg::XMM0);
} else if size <= 32 {
result.eightbyte_classes = vec![EightByteClass::SSE, EightByteClass::SSEUp];
result.location = ArgLocation::RegisterPair(X86Reg::XMM0, X86Reg::XMM1);
}
return;
}
let num_eightbytes = (size + 7) / 8;
match num_eightbytes {
1 => {
result.eightbyte_classes = vec![EightByteClass::Integer];
result.location = ArgLocation::Register(X86Reg::RDI);
}
2 => {
result.eightbyte_classes = vec![EightByteClass::Integer, EightByteClass::Integer];
result.location = ArgLocation::RegisterPair(X86Reg::RDI, X86Reg::RSI);
}
_ => {
result.location = ArgLocation::Stack {
offset: 0,
size,
alignment: 8,
};
}
}
}
fn classify_win64(
&self,
size: usize,
is_float: bool,
is_vector: bool,
result: &mut ArgClassifyResult,
assignments: &AbiRegAssignments,
) {
if size > 8 {
result.location = ArgLocation::Indirect {
ptr_reg: assignments.int_arg_regs[0],
size,
};
result.is_byval = true;
return;
}
if is_float || is_vector {
if result.in_regs.len() < assignments.sse_arg_regs.len() {
let reg = assignments.sse_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
}
} else {
if result.in_regs.len() < assignments.int_arg_regs.len() {
let reg = assignments.int_arg_regs[result.in_regs.len()];
result.in_regs.push(reg);
result.location = ArgLocation::Register(reg);
}
}
if result.in_regs.is_empty() {
result.location = ArgLocation::Stack {
offset: 0,
size,
alignment: 8,
};
}
}
pub fn classify_return(
&self,
cc: CallingConvention,
size: usize,
is_float: bool,
is_vector: bool,
is_hfa: bool,
) -> ReturnClassifyResult {
let mut ret = ReturnClassifyResult::default();
ret.size = size;
match cc {
CallingConvention::SysV_AMD64 => {
if size == 0 {
return ret;
}
if size > 16 || is_hfa && size > 64 {
ret.uses_sret = true;
ret.sret_ptr_reg = Some(X86Reg::RDI);
ret.location = RetLocation::Memory;
return ret;
}
if is_float {
if size <= 8 {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else if size <= 16 {
ret.location = RetLocation::SSEPair(X86Reg::XMM0, X86Reg::XMM1);
}
} else if is_vector {
if size <= 16 {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else if size <= 32 {
ret.location = RetLocation::SSEPair(X86Reg::XMM0, X86Reg::XMM1);
}
} else {
if size <= 8 {
ret.location = RetLocation::GPR(X86Reg::RAX);
} else if size <= 16 {
ret.location = RetLocation::GPRPair(X86Reg::RAX, X86Reg::RDX);
ret.is_multi_reg = true;
}
}
}
CallingConvention::Win64 => {
if size == 0 {
return ret;
}
if size > 8 && !is_float && !is_vector {
ret.uses_sret = true;
ret.sret_ptr_reg = Some(X86Reg::RCX);
ret.location = RetLocation::Memory;
return ret;
}
if is_float || is_vector {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else {
ret.location = RetLocation::GPR(X86Reg::RAX);
}
}
CallingConvention::Cdecl
| CallingConvention::StdCall
| CallingConvention::FastCall
| CallingConvention::ThisCallMSVC
| CallingConvention::ThisCallGNU
| CallingConvention::Pascal => {
if size == 0 {
return ret;
}
if is_float {
ret.location = RetLocation::X87;
} else {
if size <= 4 {
ret.location = RetLocation::GPR(X86Reg::EAX);
} else if size <= 8 {
ret.location = RetLocation::GPRPair(X86Reg::EAX, X86Reg::EDX);
ret.is_multi_reg = true;
} else {
ret.uses_sret = true;
ret.location = RetLocation::Memory;
}
}
}
CallingConvention::VectorCall => {
if size == 0 {
return ret;
}
if is_vector {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else if is_float {
if size <= 4 {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else {
ret.location = RetLocation::SSEPair(X86Reg::XMM0, X86Reg::XMM1);
}
} else {
if size <= 8 {
ret.location = RetLocation::GPR(X86Reg::RAX);
} else {
ret.location = RetLocation::GPRPair(X86Reg::RAX, X86Reg::RDX);
}
}
}
CallingConvention::RegCall => {
if size == 0 {
return ret;
}
if is_float || is_vector {
ret.location = RetLocation::SSE(X86Reg::XMM0);
} else {
if size <= 8 {
ret.location = RetLocation::GPR(X86Reg::RAX);
} else {
ret.location = RetLocation::GPRPair(X86Reg::RAX, X86Reg::RDX);
ret.is_multi_reg = true;
}
}
}
_ => {
ret.location = RetLocation::GPR(X86Reg::RAX);
}
}
ret
}
pub fn check_compatibility(
&mut self,
caller: CallingConvention,
callee: CallingConvention,
) -> AbiCompatibilityResult {
let key = (caller, callee);
if let Some(cached) = self.compatibility_cache.get(&key) {
return cached.clone();
}
let mut result = AbiCompatibilityResult::compatible(caller, callee);
if caller.is_64bit() != callee.is_64bit() {
result.errors.push(format!(
"Bitness mismatch: caller is {}bit, callee is {}bit",
if caller.is_64bit() { 64 } else { 32 },
if callee.is_64bit() { 64 } else { 32 }
));
result.is_compatible = false;
}
if caller.callee_cleans_stack() != callee.callee_cleans_stack() {
result.warnings.push(format!(
"Stack cleanup responsibility differs: {} cleans={}, {} cleans={}",
caller.name(),
caller.callee_cleans_stack(),
callee.name(),
callee.callee_cleans_stack()
));
}
if callee.shadow_space_size() > caller.shadow_space_size() {
result.warnings.push(format!(
"Callee requires {} bytes shadow space but caller provides {}",
callee.shadow_space_size(),
caller.shadow_space_size()
));
}
if callee.red_zone_size() > 0 && caller.red_zone_size() == 0 {
result
.warnings
.push("Callee uses red zone but caller does not".into());
}
self.compatibility_cache.insert(key, result.clone());
result
}
pub fn is_compatible(&mut self, caller: CallingConvention, callee: CallingConvention) -> bool {
self.check_compatibility(caller, callee).is_compatible
}
pub fn callee_saved_regs(&self, cc: CallingConvention) -> Vec<X86Reg> {
self.get_reg_assignments(cc)
.map(|a| a.callee_saved_gprs.clone())
.unwrap_or_default()
}
pub fn caller_saved_regs(&self, cc: CallingConvention) -> Vec<X86Reg> {
self.get_reg_assignments(cc)
.map(|a| a.caller_saved_gprs.clone())
.unwrap_or_default()
}
pub fn varargs_config(&self, cc: CallingConvention) -> Option<&VarArgsConfig> {
self.varargs_configs.get(&cc)
}
pub fn needs_sret(
&self,
cc: CallingConvention,
return_size: usize,
is_aggregate: bool,
is_hfa: bool,
) -> bool {
match cc {
CallingConvention::SysV_AMD64 => is_aggregate && !is_hfa && return_size > 16,
CallingConvention::Win64 => is_aggregate && return_size > 8,
_ => is_aggregate && return_size > 8,
}
}
pub fn stack_alignment(&self, cc: CallingConvention) -> usize {
cc.stack_alignment()
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("=== X86CallingConvDeep Summary ===\n");
s.push_str(&format!(
"Mode: {}bit\n",
if self.is_64bit { 64 } else { 32 }
));
s.push_str(&format!(
"Registered conventions: {}\n",
self.conventions.len()
));
s.push_str(&format!(
"Varargs configs: {}\n",
self.varargs_configs.len()
));
s.push_str(&format!(
"Compatibility checks cached: {}\n",
self.compatibility_cache.len()
));
s.push_str(&format!(
"Total calls analyzed: {}\n",
self.total_calls_analyzed
));
for (cc, assignments) in &self.conventions {
s.push_str(&format!(
" {:25} GPRs={:2} SSE={:2} CalleeGPRs={:2} CalleeXMMs={:2}\n",
cc.name(),
assignments.int_arg_regs.len(),
assignments.sse_arg_regs.len(),
assignments.callee_saved_gprs.len(),
assignments.callee_saved_xmms.len()
));
}
s
}
pub fn reset(&mut self) {
self.compatibility_cache.clear();
self.total_calls_analyzed = 0;
}
}
impl Default for X86CallingConvDeep {
fn default() -> Self {
Self::new(true)
}
}
pub fn make_x86_64_calling_conv_deep() -> X86CallingConvDeep {
X86CallingConvDeep::new(true)
}
pub fn make_x86_32_calling_conv_deep() -> X86CallingConvDeep {
X86CallingConvDeep::new(false)
}
pub fn all_calling_convention_names() -> Vec<&'static str> {
vec![
"cdecl",
"stdcall",
"fastcall",
"thiscall_msvc",
"thiscall_gnu",
"vectorcall",
"regcall",
"pascal",
"borland",
"watcom",
"regparm3",
"sysv_amd64",
"win64",
"intel_ocl",
"wine64",
"preserve_all",
"preserve_most",
"ghc",
"hipe",
"anyreg",
"webkit_js",
"cold",
"tail",
]
}
pub fn check_abi_compatibility(
caller: CallingConvention,
callee: CallingConvention,
) -> AbiCompatibilityResult {
let mut deep = X86CallingConvDeep::default();
deep.check_compatibility(caller, callee)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_deep_64() -> X86CallingConvDeep {
X86CallingConvDeep::new(true)
}
fn make_deep_32() -> X86CallingConvDeep {
X86CallingConvDeep::new(false)
}
#[test]
fn test_x86_reg_is_gpr() {
assert!(X86Reg::RAX.is_gpr());
assert!(X86Reg::R12.is_gpr());
assert!(X86Reg::EAX.is_gpr());
assert!(X86Reg::AL.is_gpr());
assert!(!X86Reg::XMM0.is_gpr());
assert!(!X86Reg::ST0.is_gpr());
}
#[test]
fn test_x86_reg_is_xmm() {
assert!(X86Reg::XMM0.is_xmm());
assert!(X86Reg::XMM15.is_xmm());
assert!(X86Reg::XMM31.is_xmm());
assert!(!X86Reg::YMM0.is_xmm());
assert!(!X86Reg::RAX.is_xmm());
}
#[test]
fn test_x86_reg_is_ymm() {
assert!(X86Reg::YMM0.is_ymm());
assert!(!X86Reg::XMM0.is_ymm());
}
#[test]
fn test_x86_reg_is_zmm() {
assert!(X86Reg::ZMM0.is_zmm());
assert!(!X86Reg::YMM0.is_zmm());
}
#[test]
fn test_x86_reg_is_vector() {
assert!(X86Reg::XMM0.is_vector());
assert!(X86Reg::YMM0.is_vector());
assert!(X86Reg::ZMM0.is_vector());
assert!(!X86Reg::RAX.is_vector());
}
#[test]
fn test_x86_reg_is_mask() {
assert!(X86Reg::K0.is_mask());
assert!(X86Reg::K7.is_mask());
assert!(!X86Reg::XMM0.is_mask());
}
#[test]
fn test_x86_reg_is_x87() {
assert!(X86Reg::ST0.is_x87());
assert!(X86Reg::ST7.is_x87());
}
#[test]
fn test_x86_reg_gpr_index() {
assert_eq!(X86Reg::RAX.gpr_index(), Some(0));
assert_eq!(X86Reg::RCX.gpr_index(), Some(1));
assert_eq!(X86Reg::R15.gpr_index(), Some(15));
assert_eq!(X86Reg::XMM0.gpr_index(), None);
}
#[test]
fn test_x86_reg_xmm_index() {
assert_eq!(X86Reg::XMM0.xmm_index(), Some(0));
assert_eq!(X86Reg::XMM15.xmm_index(), Some(15));
assert_eq!(X86Reg::XMM31.xmm_index(), Some(31));
assert_eq!(X86Reg::RAX.xmm_index(), None);
}
#[test]
fn test_x86_reg_mask_index() {
assert_eq!(X86Reg::K0.mask_index(), Some(0));
assert_eq!(X86Reg::K7.mask_index(), Some(7));
assert_eq!(X86Reg::RAX.mask_index(), None);
}
#[test]
fn test_x86_reg_class() {
assert_eq!(X86Reg::RAX.reg_class(), RegClass::GPR);
assert_eq!(X86Reg::XMM0.reg_class(), RegClass::XMM);
assert_eq!(X86Reg::YMM0.reg_class(), RegClass::YMM);
assert_eq!(X86Reg::ZMM0.reg_class(), RegClass::ZMM);
assert_eq!(X86Reg::K0.reg_class(), RegClass::Mask);
assert_eq!(X86Reg::ST0.reg_class(), RegClass::X87);
assert_eq!(X86Reg::MM0.reg_class(), RegClass::MMX);
}
#[test]
fn test_reg_class_size_bytes() {
assert_eq!(RegClass::GPR.size_bytes(), 8);
assert_eq!(RegClass::XMM.size_bytes(), 16);
assert_eq!(RegClass::YMM.size_bytes(), 32);
assert_eq!(RegClass::ZMM.size_bytes(), 64);
assert_eq!(RegClass::X87.size_bytes(), 10);
}
#[test]
fn test_cc_name() {
assert_eq!(CallingConvention::Cdecl.name(), "cdecl");
assert_eq!(CallingConvention::SysV_AMD64.name(), "sysv_amd64");
assert_eq!(CallingConvention::Win64.name(), "win64");
assert_eq!(CallingConvention::VectorCall.name(), "vectorcall");
}
#[test]
fn test_cc_is_64bit() {
assert!(CallingConvention::SysV_AMD64.is_64bit());
assert!(CallingConvention::Win64.is_64bit());
assert!(!CallingConvention::Cdecl.is_64bit());
assert!(!CallingConvention::FastCall.is_64bit());
}
#[test]
fn test_cc_callee_cleans_stack() {
assert!(CallingConvention::StdCall.callee_cleans_stack());
assert!(CallingConvention::Win64.callee_cleans_stack());
assert!(!CallingConvention::Cdecl.callee_cleans_stack());
assert!(!CallingConvention::SysV_AMD64.callee_cleans_stack());
}
#[test]
fn test_cc_int_param_reg_count() {
assert_eq!(CallingConvention::SysV_AMD64.int_param_reg_count(), 6);
assert_eq!(CallingConvention::Win64.int_param_reg_count(), 4);
assert_eq!(CallingConvention::FastCall.int_param_reg_count(), 2);
assert_eq!(CallingConvention::Cdecl.int_param_reg_count(), 0);
}
#[test]
fn test_cc_sse_param_reg_count() {
assert_eq!(CallingConvention::SysV_AMD64.sse_param_reg_count(), 8);
assert_eq!(CallingConvention::Win64.sse_param_reg_count(), 4);
assert_eq!(CallingConvention::VectorCall.sse_param_reg_count(), 6);
assert_eq!(CallingConvention::Cdecl.sse_param_reg_count(), 0);
}
#[test]
fn test_cc_stack_alignment() {
assert_eq!(CallingConvention::SysV_AMD64.stack_alignment(), 16);
assert_eq!(CallingConvention::Win64.stack_alignment(), 16);
assert_eq!(CallingConvention::Cdecl.stack_alignment(), 4);
}
#[test]
fn test_cc_red_zone() {
assert_eq!(CallingConvention::SysV_AMD64.red_zone_size(), 128);
assert_eq!(CallingConvention::Win64.red_zone_size(), 0);
}
#[test]
fn test_cc_shadow_space() {
assert_eq!(CallingConvention::Win64.shadow_space_size(), 32);
assert_eq!(CallingConvention::SysV_AMD64.shadow_space_size(), 0);
}
#[test]
fn test_cc_needs_sret_demotion() {
assert!(CallingConvention::SysV_AMD64.needs_sret_demotion());
assert!(CallingConvention::Win64.needs_sret_demotion());
assert!(!CallingConvention::Cdecl.needs_sret_demotion());
}
#[test]
fn test_sysv_amd64_assignments() {
let a = AbiRegAssignments::sysv_amd64();
assert_eq!(a.int_arg_regs.len(), 6);
assert_eq!(a.sse_arg_regs.len(), 8);
assert_eq!(a.ret_gpr.len(), 2);
assert_eq!(a.callee_saved_gprs.len(), 6);
assert_eq!(a.caller_saved_gprs.len(), 9);
assert_eq!(a.frame_pointer_reg, X86Reg::RBP);
assert_eq!(a.stack_pointer_reg, X86Reg::RSP);
}
#[test]
fn test_win64_assignments() {
let a = AbiRegAssignments::win64();
assert_eq!(a.int_arg_regs.len(), 4);
assert_eq!(a.sse_arg_regs.len(), 4);
assert_eq!(a.callee_saved_gprs.len(), 8);
assert_eq!(a.callee_saved_xmms.len(), 10);
assert_eq!(a.convention, CallingConvention::Win64);
}
#[test]
fn test_cdecl_32_assignments() {
let a = AbiRegAssignments::cdecl_32();
assert!(a.int_arg_regs.is_empty());
assert_eq!(a.ret_gpr[0], X86Reg::EAX);
assert_eq!(a.callee_saved_gprs.len(), 4);
}
#[test]
fn test_fastcall_32_assignments() {
let a = AbiRegAssignments::fastcall_32();
assert_eq!(a.int_arg_regs, vec![X86Reg::ECX, X86Reg::EDX]);
}
#[test]
fn test_thiscall_msvc_32_assignments() {
let a = AbiRegAssignments::thiscall_msvc_32();
assert_eq!(a.int_arg_regs, vec![X86Reg::ECX]);
}
#[test]
fn test_vectorcall_32_assignments() {
let a = AbiRegAssignments::vectorcall_32();
assert_eq!(a.int_arg_regs.len(), 2);
assert_eq!(a.sse_arg_regs.len(), 6);
assert!(a.callee_saved_xmms.contains(&X86Reg::XMM6));
}
#[test]
fn test_regcall_32_assignments() {
let a = AbiRegAssignments::regcall_32();
assert_eq!(a.int_arg_regs.len(), 5);
assert_eq!(a.sse_arg_regs.len(), 8);
}
#[test]
fn test_classify_param_sysv_integer() {
let deep = make_deep_64();
let result =
deep.classify_param(CallingConvention::SysV_AMD64, 8, 8, false, false, false, 0);
assert!(matches!(
result.eightbyte_classes.as_slice(),
[EightByteClass::Integer]
));
}
#[test]
fn test_classify_param_sysv_float() {
let deep = make_deep_64();
let result =
deep.classify_param(CallingConvention::SysV_AMD64, 8, 8, true, false, false, 0);
assert!(matches!(
result.eightbyte_classes.as_slice(),
[EightByteClass::SSE]
));
}
#[test]
fn test_classify_param_sysv_large_struct() {
let deep = make_deep_64();
let result =
deep.classify_param(CallingConvention::SysV_AMD64, 32, 8, false, false, false, 0);
assert!(result.is_byval);
assert!(matches!(result.location, ArgLocation::Indirect { .. }));
}
#[test]
fn test_classify_param_win64_integer() {
let deep = make_deep_64();
let result = deep.classify_param(CallingConvention::Win64, 8, 8, false, false, false, 0);
assert!(matches!(
result.location,
ArgLocation::Register(X86Reg::RCX)
));
}
#[test]
fn test_classify_param_win64_float() {
let deep = make_deep_64();
let result = deep.classify_param(CallingConvention::Win64, 8, 8, true, false, false, 0);
assert!(matches!(
result.location,
ArgLocation::Register(X86Reg::XMM0)
));
}
#[test]
fn test_classify_param_win64_large_struct() {
let deep = make_deep_64();
let result = deep.classify_param(CallingConvention::Win64, 24, 8, false, false, false, 0);
assert!(result.is_byval);
}
#[test]
fn test_classify_param_cdecl() {
let deep = make_deep_32();
let result = deep.classify_param(CallingConvention::Cdecl, 4, 4, false, false, false, 0);
assert!(matches!(result.location, ArgLocation::Stack { .. }));
}
#[test]
fn test_classify_param_fastcall() {
let deep = make_deep_32();
let result = deep.classify_param(CallingConvention::FastCall, 4, 4, false, false, false, 0);
assert!(result.in_regs.contains(&X86Reg::ECX));
}
#[test]
fn test_classify_param_vectorcall_float() {
let deep = make_deep_32();
let result =
deep.classify_param(CallingConvention::VectorCall, 8, 8, true, false, false, 0);
assert!(matches!(
result.location,
ArgLocation::Register(X86Reg::XMM0)
));
}
#[test]
fn test_classify_return_sysv_int() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::SysV_AMD64, 8, false, false, false);
assert!(matches!(ret.location, RetLocation::GPR(X86Reg::RAX)));
}
#[test]
fn test_classify_return_sysv_float() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::SysV_AMD64, 8, true, false, false);
assert!(matches!(ret.location, RetLocation::SSE(X86Reg::XMM0)));
}
#[test]
fn test_classify_return_sysv_large() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::SysV_AMD64, 32, false, false, false);
assert!(ret.uses_sret);
assert_eq!(ret.sret_ptr_reg, Some(X86Reg::RDI));
}
#[test]
fn test_classify_return_win64_int() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::Win64, 8, false, false, false);
assert!(matches!(ret.location, RetLocation::GPR(X86Reg::RAX)));
}
#[test]
fn test_classify_return_win64_large() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::Win64, 16, false, false, false);
assert!(ret.uses_sret);
}
#[test]
fn test_classify_return_cdecl_int() {
let deep = make_deep_32();
let ret = deep.classify_return(CallingConvention::Cdecl, 4, false, false, false);
assert!(matches!(ret.location, RetLocation::GPR(X86Reg::EAX)));
}
#[test]
fn test_classify_return_cdecl_float() {
let deep = make_deep_32();
let ret = deep.classify_return(CallingConvention::Cdecl, 4, true, false, false);
assert!(matches!(ret.location, RetLocation::X87));
}
#[test]
fn test_classify_return_void() {
let deep = make_deep_64();
let ret = deep.classify_return(CallingConvention::SysV_AMD64, 0, false, false, false);
assert!(matches!(ret.location, RetLocation::Void));
}
#[test]
fn test_compatibility_same() {
let mut deep = make_deep_64();
let result =
deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::SysV_AMD64);
assert!(result.is_compatible);
assert!(result.errors.is_empty());
}
#[test]
fn test_compatibility_bitness_mismatch() {
let mut deep = make_deep_64();
let result =
deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::Cdecl);
assert!(!result.is_compatible);
assert!(!result.errors.is_empty());
}
#[test]
fn test_compatibility_win64_to_sysv() {
let mut deep = make_deep_64();
let result =
deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::Win64);
assert!(result.is_compatible); assert!(!result.warnings.is_empty()); }
#[test]
fn test_compatibility_cdecl_to_stdcall() {
let mut deep = make_deep_32();
let result = deep.check_compatibility(CallingConvention::Cdecl, CallingConvention::StdCall);
assert!(result.is_compatible); assert!(!result.warnings.is_empty()); }
#[test]
fn test_compatibility_cache() {
let mut deep = make_deep_64();
let _ = deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::Win64);
assert_eq!(deep.compatibility_cache.len(), 1);
let _ = deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::Win64);
assert_eq!(deep.compatibility_cache.len(), 1);
}
#[test]
fn test_hfa_detector_empty() {
let det = HFADetector::default();
assert!(det.detect_hfa(&[]).is_none());
}
#[test]
fn test_hfa_detector_float_fields() {
let det = HFADetector::default();
let fields = vec![
AbiFieldInfo::float_field(0, 4),
AbiFieldInfo::float_field(4, 4),
];
let result = det.detect_hfa(&fields);
assert!(result.is_some());
let (base, count) = result.unwrap();
assert_eq!(base, HfaBaseType::Float);
assert_eq!(count, 2);
}
#[test]
fn test_hfa_detector_mixed() {
let det = HFADetector::default();
let fields = vec![
AbiFieldInfo::float_field(0, 4),
AbiFieldInfo::new(4, 4), ];
assert!(det.detect_hfa(&fields).is_none());
}
#[test]
fn test_hfa_base_type_size() {
assert_eq!(HfaBaseType::Float.size_bytes(), 4);
assert_eq!(HfaBaseType::Double.size_bytes(), 8);
assert_eq!(HfaBaseType::Float128.size_bytes(), 16);
assert_eq!(HfaBaseType::Vector128.size_bytes(), 16);
assert_eq!(HfaBaseType::Vector256.size_bytes(), 32);
assert_eq!(HfaBaseType::Vector512.size_bytes(), 64);
}
#[test]
fn test_varargs_sysv() {
let v = VarArgsConfig::sysv_amd64();
assert_eq!(v.gp_save_size, 48);
assert_eq!(v.fp_save_size, 128);
assert_eq!(v.num_gp_saved, 6);
assert_eq!(v.num_fp_saved, 8);
assert_eq!(v.va_list_type, VaListType::SysV);
}
#[test]
fn test_varargs_win64() {
let v = VarArgsConfig::win64();
assert_eq!(v.gp_save_size, 32);
assert_eq!(v.num_gp_saved, 4);
assert_eq!(v.va_list_type, VaListType::MsX64);
}
#[test]
fn test_varargs_x86_32() {
let v = VarArgsConfig::x86_32();
assert_eq!(v.va_list_type, VaListType::X86_32);
}
#[test]
fn test_stack_frame_new_sysv() {
let frame = StackFrame::new(CallingConvention::SysV_AMD64, 32, 8, 0);
assert_eq!(frame.red_zone_size, 128);
assert_eq!(frame.stack_alignment, 16);
assert!(frame.can_use_red_zone());
}
#[test]
fn test_stack_frame_new_win64() {
let frame = StackFrame::new(CallingConvention::Win64, 32, 8, 0);
assert_eq!(frame.red_zone_size, 0);
assert_eq!(frame.shadow_space_size, 32);
assert!(!frame.can_use_red_zone());
}
#[test]
fn test_stack_frame_with_args() {
let frame = StackFrame::new(CallingConvention::SysV_AMD64, 32, 8, 64);
assert!(!frame.can_use_red_zone());
}
#[test]
fn test_byval_config_default() {
let bv = ByValConfig::default();
assert_eq!(bv.size, 0);
assert!(bv.uses_hidden_copy);
}
#[test]
fn test_inalloca_config() {
let ia = InAllocaConfig::new(64, 16);
assert_eq!(ia.size, 64);
assert_eq!(ia.alignment, 16);
assert!(ia.arg_memory_offsets.is_empty());
}
#[test]
fn test_deep_64_new() {
let deep = make_deep_64();
assert!(deep.is_64bit);
assert!(deep
.conventions
.contains_key(&CallingConvention::SysV_AMD64));
assert!(deep.conventions.contains_key(&CallingConvention::Win64));
}
#[test]
fn test_deep_32_new() {
let deep = make_deep_32();
assert!(!deep.is_64bit);
assert!(deep.conventions.contains_key(&CallingConvention::Cdecl));
assert!(deep.conventions.contains_key(&CallingConvention::FastCall));
assert!(deep
.conventions
.contains_key(&CallingConvention::VectorCall));
}
#[test]
fn test_deep_get_reg_assignments() {
let deep = make_deep_64();
let a = deep.get_reg_assignments(CallingConvention::SysV_AMD64);
assert!(a.is_some());
let a = deep.get_reg_assignments(CallingConvention::Cdecl);
assert!(a.is_some());
}
#[test]
fn test_deep_register_convention() {
let mut deep = make_deep_64();
let custom = AbiRegAssignments::sysv_amd64();
deep.register_convention(CallingConvention::Cold, custom.clone());
assert!(deep.conventions.contains_key(&CallingConvention::Cold));
}
#[test]
fn test_deep_callee_saved_regs() {
let deep = make_deep_64();
let regs = deep.callee_saved_regs(CallingConvention::SysV_AMD64);
assert!(regs.contains(&X86Reg::RBX));
assert!(regs.contains(&X86Reg::R12));
}
#[test]
fn test_deep_caller_saved_regs() {
let deep = make_deep_64();
let regs = deep.caller_saved_regs(CallingConvention::Win64);
assert!(regs.contains(&X86Reg::RAX));
assert!(regs.contains(&X86Reg::RCX));
}
#[test]
fn test_deep_varargs_config() {
let deep = make_deep_64();
let v = deep.varargs_config(CallingConvention::SysV_AMD64);
assert!(v.is_some());
assert_eq!(v.unwrap().num_gp_saved, 6);
}
#[test]
fn test_deep_needs_sret() {
let deep = make_deep_64();
assert!(deep.needs_sret(CallingConvention::SysV_AMD64, 32, true, false));
assert!(!deep.needs_sret(CallingConvention::SysV_AMD64, 8, true, false));
}
#[test]
fn test_deep_stack_alignment() {
let deep = make_deep_64();
assert_eq!(deep.stack_alignment(CallingConvention::SysV_AMD64), 16);
assert_eq!(deep.stack_alignment(CallingConvention::Cdecl), 4);
}
#[test]
fn test_deep_summary() {
let deep = make_deep_64();
let s = deep.summary();
assert!(s.contains("X86CallingConvDeep Summary"));
assert!(s.contains("sysv_amd64"));
assert!(s.contains("win64"));
}
#[test]
fn test_deep_reset() {
let mut deep = make_deep_64();
let _ = deep.check_compatibility(CallingConvention::SysV_AMD64, CallingConvention::Win64);
assert!(deep.compatibility_cache.len() > 0);
deep.reset();
assert_eq!(deep.compatibility_cache.len(), 0);
}
#[test]
fn test_make_x86_64_calling_conv_deep() {
let deep = make_x86_64_calling_conv_deep();
assert!(deep.is_64bit);
}
#[test]
fn test_make_x86_32_calling_conv_deep() {
let deep = make_x86_32_calling_conv_deep();
assert!(!deep.is_64bit);
}
#[test]
fn test_all_calling_convention_names() {
let names = all_calling_convention_names();
assert!(names.contains(&"cdecl"));
assert!(names.contains(&"sysv_amd64"));
assert!(names.contains(&"win64"));
assert!(names.contains(&"vectorcall"));
assert!(names.len() >= 20);
}
#[test]
fn test_check_abi_compatibility() {
let result =
check_abi_compatibility(CallingConvention::SysV_AMD64, CallingConvention::SysV_AMD64);
assert!(result.is_compatible);
}
#[test]
fn test_eightbyte_class_is_register() {
assert!(EightByteClass::Integer.is_register_class());
assert!(EightByteClass::SSE.is_register_class());
assert!(!EightByteClass::Memory.is_register_class());
assert!(!EightByteClass::NoClass.is_register_class());
}
#[test]
fn test_eightbyte_class_gpr_sse_needed() {
assert_eq!(EightByteClass::Integer.gpr_needed(), 1);
assert_eq!(EightByteClass::SSE.sse_needed(), 1);
assert_eq!(EightByteClass::Memory.gpr_needed(), 0);
}
#[test]
fn test_eightbyte_class_display() {
assert_eq!(format!("{}", EightByteClass::Integer), "INTEGER");
assert_eq!(format!("{}", EightByteClass::SSE), "SSE");
assert_eq!(format!("{}", EightByteClass::Memory), "MEMORY");
}
#[test]
fn test_arg_location_is_in_register() {
assert!(ArgLocation::Register(X86Reg::RAX).is_in_register());
assert!(ArgLocation::RegisterPair(X86Reg::RAX, X86Reg::RDX).is_in_register());
assert!(!ArgLocation::Stack {
offset: 0,
size: 4,
alignment: 4
}
.is_in_register());
}
#[test]
fn test_arg_location_reg_count() {
assert_eq!(ArgLocation::Register(X86Reg::RAX).reg_count(), 1);
assert_eq!(
ArgLocation::RegisterPair(X86Reg::RAX, X86Reg::RDX).reg_count(),
2
);
assert_eq!(
ArgLocation::Indirect {
ptr_reg: X86Reg::RDI,
size: 16
}
.reg_count(),
1
);
}
#[test]
fn test_arg_location_display() {
let loc = ArgLocation::Register(X86Reg::RAX);
assert!(format!("{}", loc).contains("RAX"));
}
#[test]
fn test_abi_field_info_new() {
let fi = AbiFieldInfo::new(0, 8);
assert_eq!(fi.offset, 0);
assert_eq!(fi.size, 8);
assert!(fi.is_integer);
assert!(!fi.is_float);
}
#[test]
fn test_abi_field_info_float() {
let fi = AbiFieldInfo::float_field(0, 4);
assert!(fi.is_float);
assert_eq!(fi.hfa_base_type, Some(HfaBaseType::Float));
}
#[test]
fn test_abi_field_info_vector() {
let fi = AbiFieldInfo::vector_field(0, 16);
assert!(fi.is_vector);
assert_eq!(fi.hfa_base_type, Some(HfaBaseType::Vector128));
}
#[test]
fn test_abi_field_info_pointer() {
let fi = AbiFieldInfo::pointer_field(0);
assert!(fi.is_pointer);
assert_eq!(fi.size, 8);
}
#[test]
fn stress_classify_many_params_sysv() {
let deep = make_deep_64();
for i in 0..20 {
let result = deep.classify_param(
CallingConvention::SysV_AMD64,
8,
8,
i % 2 == 0,
false,
false,
0,
);
assert!(result.size == 8);
}
}
#[test]
fn stress_classify_many_params_win64() {
let deep = make_deep_64();
for i in 0..20 {
let result =
deep.classify_param(CallingConvention::Win64, 8, 8, i % 2 == 0, false, false, 0);
assert!(result.size == 8);
}
}
#[test]
fn stress_compatibility_all_pairs() {
let mut deep = make_deep_64();
let conventions = vec![
CallingConvention::SysV_AMD64,
CallingConvention::Win64,
CallingConvention::Cdecl,
CallingConvention::FastCall,
CallingConvention::VectorCall,
CallingConvention::RegCall,
];
for &a in &conventions {
for &b in &conventions {
let _ = deep.check_compatibility(a, b);
}
}
assert!(deep.compatibility_cache.len() > 0);
}
#[test]
fn stress_return_all_conventions() {
for (cc_name, cc) in [
("sysv", CallingConvention::SysV_AMD64),
("win64", CallingConvention::Win64),
("cdecl", CallingConvention::Cdecl),
("fastcall", CallingConvention::FastCall),
("vectorcall", CallingConvention::VectorCall),
] {
let deep = if cc.is_64bit() {
make_deep_64()
} else {
make_deep_32()
};
let ret = deep.classify_return(cc, 8, false, false, false);
assert!(
!matches!(ret.location, RetLocation::Void),
"{} returned Void",
cc_name
);
}
}
#[test]
fn smoke_all_conventions_have_reg_assignments() {
let deep = make_deep_64();
let conventions = [
CallingConvention::SysV_AMD64,
CallingConvention::Win64,
CallingConvention::Cdecl,
CallingConvention::FastCall,
CallingConvention::ThisCallMSVC,
CallingConvention::ThisCallGNU,
CallingConvention::VectorCall,
CallingConvention::RegCall,
CallingConvention::Pascal,
CallingConvention::Borland,
CallingConvention::Watcom,
];
for cc in &conventions {
assert!(
deep.get_reg_assignments(*cc).is_some(),
"Missing reg assignments for {}",
cc.name()
);
}
}
#[test]
fn smoke_all_conventions_have_varargs_config() {
let deep = make_deep_64();
let conventions = [
CallingConvention::SysV_AMD64,
CallingConvention::Win64,
CallingConvention::Cdecl,
CallingConvention::FastCall,
CallingConvention::ThisCallMSVC,
CallingConvention::VectorCall,
CallingConvention::RegCall,
CallingConvention::Pascal,
];
for cc in &conventions {
assert!(
deep.varargs_config(*cc).is_some(),
"Missing varargs config for {}",
cc.name()
);
}
}
#[test]
fn smoke_cc_display() {
let names: Vec<String> = vec![
CallingConvention::SysV_AMD64,
CallingConvention::Win64,
CallingConvention::Cdecl,
CallingConvention::FastCall,
CallingConvention::VectorCall,
CallingConvention::RegCall,
]
.iter()
.map(|cc| format!("{}", cc))
.collect();
assert!(names.contains(&"sysv_amd64".to_string()));
assert!(names.contains(&"cdecl".to_string()));
}
}