use crate::types::{Type, TypeKind};
use crate::x86::x86_register_info::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ArgClass {
NoClass,
Integer,
SSE,
SSEUp,
X87,
X87Up,
ComplexX87,
Memory,
}
impl X86ArgClass {
pub fn is_register_class(&self) -> bool {
matches!(
self,
X86ArgClass::Integer | X86ArgClass::SSE | X86ArgClass::SSEUp | X86ArgClass::X87
)
}
pub fn needs_xmm(&self) -> bool {
matches!(self, X86ArgClass::SSE | X86ArgClass::SSEUp)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CallingConvention {
C,
Fast,
StdCall,
ThisCall,
VectorCall,
#[allow(non_camel_case_types)]
X86_64_SysV,
Win64,
#[allow(non_camel_case_types)]
X86_RegCall,
PreserveAll,
PreserveMost,
GHC,
AnyReg,
}
impl Default for X86CallingConvention {
fn default() -> Self {
X86CallingConvention::X86_64_SysV
}
}
impl X86CallingConvention {
pub fn new(cc: crate::x86::x86_frame_lowering::CallConv) -> Self {
use crate::x86::x86_frame_lowering::CallConv;
match cc {
CallConv::SystemV => X86CallingConvention::X86_64_SysV,
CallConv::Win64 => X86CallingConvention::Win64,
CallConv::CDecl32 => X86CallingConvention::C,
CallConv::StdCall32 => X86CallingConvention::StdCall,
CallConv::FastCall32 => X86CallingConvention::Fast,
}
}
pub fn name(&self) -> &'static str {
match self {
X86CallingConvention::C => "cdecl",
X86CallingConvention::Fast => "fastcall",
X86CallingConvention::StdCall => "stdcall",
X86CallingConvention::ThisCall => "thiscall",
X86CallingConvention::VectorCall => "vectorcall",
X86CallingConvention::X86_64_SysV => "System V AMD64",
X86CallingConvention::Win64 => "Microsoft x64",
X86CallingConvention::X86_RegCall => "regcall",
X86CallingConvention::PreserveAll => "preserve_all",
X86CallingConvention::PreserveMost => "preserve_most",
X86CallingConvention::GHC => "GHC",
X86CallingConvention::AnyReg => "anyreg",
}
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
X86CallingConvention::X86_64_SysV
| X86CallingConvention::Win64
| X86CallingConvention::X86_RegCall
)
}
pub fn callee_cleans_stack(&self) -> bool {
matches!(
self,
X86CallingConvention::StdCall
| X86CallingConvention::ThisCall
| X86CallingConvention::Fast
)
}
pub fn uses_register_params(&self) -> bool {
match self {
X86CallingConvention::C => false,
X86CallingConvention::Fast => true,
X86CallingConvention::StdCall => false,
X86CallingConvention::ThisCall => true,
X86CallingConvention::VectorCall => true,
X86CallingConvention::X86_64_SysV => true,
X86CallingConvention::Win64 => true,
X86CallingConvention::X86_RegCall => true,
X86CallingConvention::PreserveAll => false,
X86CallingConvention::PreserveMost => false,
X86CallingConvention::GHC => true,
X86CallingConvention::AnyReg => true,
}
}
pub fn get_num_int_param_regs(&self) -> usize {
match self {
X86CallingConvention::C | X86CallingConvention::StdCall => 0,
X86CallingConvention::Fast => 2,
X86CallingConvention::ThisCall => 1,
X86CallingConvention::VectorCall => 2,
X86CallingConvention::X86_64_SysV => 6,
X86CallingConvention::Win64 => 4,
X86CallingConvention::X86_RegCall => 5, X86CallingConvention::PreserveAll | X86CallingConvention::PreserveMost => 0,
X86CallingConvention::GHC => 0,
X86CallingConvention::AnyReg => 1,
}
}
pub fn get_num_sse_param_regs(&self) -> usize {
match self {
X86CallingConvention::VectorCall => 6,
X86CallingConvention::X86_64_SysV => 8,
X86CallingConvention::Win64 => 4,
_ => 0,
}
}
pub fn get_int_param_regs(&self) -> Vec<u16> {
match self {
X86CallingConvention::Fast => vec![ECX, EDX],
X86CallingConvention::ThisCall => vec![ECX],
X86CallingConvention::X86_64_SysV => vec![RDI, RSI, RDX, RCX, R8, R9],
X86CallingConvention::Win64 => vec![RCX, RDX, R8, R9],
X86CallingConvention::X86_RegCall => vec![EAX, ECX, EDX, EDI, ESI],
_ => vec![],
}
}
pub fn get_sse_param_regs(&self) -> Vec<u16> {
match self {
X86CallingConvention::VectorCall => vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5],
X86CallingConvention::X86_64_SysV => {
vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]
}
X86CallingConvention::Win64 => vec![XMM0, XMM1, XMM2, XMM3],
_ => vec![],
}
}
pub fn get_stack_alignment(&self) -> u32 {
match self {
X86CallingConvention::X86_64_SysV
| X86CallingConvention::Win64
| X86CallingConvention::X86_RegCall
| X86CallingConvention::PreserveAll
| X86CallingConvention::PreserveMost
| X86CallingConvention::GHC
| X86CallingConvention::AnyReg => 16,
_ => 4,
}
}
pub fn get_shadow_store_size(&self) -> i64 {
match self {
X86CallingConvention::Win64 => 32,
_ => 0,
}
}
pub fn needs_hidden_sret(&self, ty: &Type, type_map: &HashMap<TypeId, Type>) -> bool {
if !ty.is_struct() {
return false;
}
let size = type_size(ty, type_map);
match self {
X86CallingConvention::X86_64_SysV => {
let classes = classify_aggregate_type(ty, type_map);
size > 16 || classes.iter().any(|c| *c == X86ArgClass::Memory)
}
X86CallingConvention::Win64 => size > 8,
_ => false,
}
}
pub fn get_return_regs(&self, ty: &Type, type_map: &HashMap<TypeId, Type>) -> Vec<u16> {
match self {
X86CallingConvention::X86_64_SysV => {
let classes = classify_arg_type(ty, 0, type_map);
sysv_return_regs_from_classes(&classes)
}
X86CallingConvention::Win64 => {
let size = type_size(ty, type_map);
win64_return_regs(ty, size)
}
X86CallingConvention::C
| X86CallingConvention::StdCall
| X86CallingConvention::Fast
| X86CallingConvention::ThisCall
| X86CallingConvention::VectorCall => {
cdecl_return_regs(ty, type_map)
}
_ => vec![RAX],
}
}
pub fn assign_args(
&self,
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
match self {
X86CallingConvention::X86_64_SysV => assign_sysv_args(args, type_map),
X86CallingConvention::Win64 => assign_win64_args(args, type_map),
X86CallingConvention::C => assign_cdecl_args(args, type_map),
X86CallingConvention::StdCall => assign_stdcall_args(args, type_map),
X86CallingConvention::Fast => assign_fastcall_args(args, type_map),
X86CallingConvention::ThisCall => assign_thiscall_args(args, type_map),
X86CallingConvention::VectorCall => assign_vectorcall_args(args, type_map),
_ => assign_stack_only_args(args, type_map),
}
}
pub fn classify_arg(
&self,
ty: &Type,
offset: i64,
type_map: &HashMap<TypeId, Type>,
) -> Vec<X86ArgClass> {
classify_arg_type(ty, offset, type_map)
}
}
use crate::types::TypeId;
#[derive(Debug, Clone)]
pub struct X86ArgInfo {
pub in_reg: bool,
pub regs: Vec<u16>,
pub stack_offset: i64,
pub size: u32,
pub alignment: u32,
pub is_byval: bool,
pub is_sret: bool,
pub padding: u32,
}
impl Default for X86ArgInfo {
fn default() -> Self {
X86ArgInfo {
in_reg: false,
regs: Vec::new(),
stack_offset: 0,
size: 0,
alignment: 1,
is_byval: false,
is_sret: false,
padding: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CallFrame {
pub stack_size: i64,
pub arg_offsets: Vec<i64>,
pub return_save_area: Option<i64>,
pub shadow_store_size: i64,
pub alignment_padding: i64,
}
impl Default for X86CallFrame {
fn default() -> Self {
X86CallFrame {
stack_size: 0,
arg_offsets: Vec::new(),
return_save_area: None,
shadow_store_size: 0,
alignment_padding: 0,
}
}
}
pub fn type_size(ty: &Type, type_map: &HashMap<TypeId, Type>) -> u64 {
match &ty.kind {
TypeKind::Void => 0,
TypeKind::Half => 2,
TypeKind::BFloat => 2,
TypeKind::Float => 4,
TypeKind::Double => 8,
TypeKind::FP128 => 16,
TypeKind::X86FP80 => 16, TypeKind::PPCFP128 => 16,
TypeKind::Label => 0,
TypeKind::Metadata => 0,
TypeKind::X86AMX => 0, TypeKind::X86MMX => 8,
TypeKind::Token => 0,
TypeKind::Integer { bits } => ((*bits + 7) / 8) as u64,
TypeKind::Pointer { .. } => 8, TypeKind::Array {
len,
element_type_id,
} => {
let elem_size = type_size_from_id(*element_type_id, type_map);
(*len) * elem_size
}
TypeKind::Struct {
is_packed,
element_type_ids,
..
} => {
if element_type_ids.is_empty() {
return 0;
}
let mut total = 0u64;
let mut max_align = 1u64;
for tid in element_type_ids {
let elem = type_size_from_id(*tid, type_map);
let align = type_alignment_from_id(*tid, type_map);
if align > max_align {
max_align = align;
}
if !(*is_packed) {
total = align_to(total, align);
}
total += elem;
}
if !(*is_packed) && max_align > 0 {
total = align_to(total, max_align);
}
total
}
TypeKind::FixedVector {
len,
element_type_id,
} => {
let elem_size = type_size_from_id(*element_type_id, type_map);
(*len as u64) * elem_size
}
TypeKind::ScalableVector {
min_elems,
element_type_id,
} => {
let elem_size = type_size_from_id(*element_type_id, type_map);
(*min_elems as u64) * elem_size
}
TypeKind::Function { .. } => 8, }
}
pub fn type_alignment(ty: &Type, type_map: &HashMap<TypeId, Type>) -> u64 {
match &ty.kind {
TypeKind::Void | TypeKind::Label | TypeKind::Metadata | TypeKind::Token => 1,
TypeKind::Half | TypeKind::BFloat => 2,
TypeKind::Float => 4,
TypeKind::Double => 8,
TypeKind::FP128 | TypeKind::PPCFP128 => 16,
TypeKind::X86FP80 => 16,
TypeKind::X86AMX => 64,
TypeKind::X86MMX => 8,
TypeKind::Integer { bits } => {
let bytes = (*bits + 7) / 8;
if bytes >= 16 {
16
} else if bytes >= 8 {
8
} else if bytes >= 4 {
4
} else if bytes >= 2 {
2
} else {
1
}
}
TypeKind::Pointer { .. } => 8,
TypeKind::Array {
element_type_id, ..
} => type_alignment_from_id(*element_type_id, type_map),
TypeKind::Struct {
is_packed,
element_type_ids,
..
} => {
if *is_packed {
1
} else {
let mut max_align = 1u64;
for tid in element_type_ids {
let a = type_alignment_from_id(*tid, type_map);
if a > max_align {
max_align = a;
}
}
max_align
}
}
TypeKind::FixedVector {
len,
element_type_id,
} => {
let total = (*len as u64) * type_size_from_id(*element_type_id, type_map);
vector_alignment(total)
}
TypeKind::ScalableVector {
min_elems,
element_type_id,
} => {
let total = (*min_elems as u64) * type_size_from_id(*element_type_id, type_map);
vector_alignment(total)
}
TypeKind::Function { .. } => 8,
}
}
fn vector_alignment(total_bytes: u64) -> u64 {
if total_bytes >= 64 {
64
} else if total_bytes >= 32 {
32
} else if total_bytes >= 16 {
16
} else if total_bytes >= 8 {
8
} else if total_bytes >= 4 {
4
} else if total_bytes >= 2 {
2
} else {
1
}
}
fn resolve_type(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> Type {
type_map
.get(&tid)
.cloned()
.unwrap_or_else(|| panic!("TypeId {} not found in type map", tid.as_u64()))
}
fn type_size_from_id(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> u64 {
let ty = resolve_type(tid, type_map);
type_size(&ty, type_map)
}
fn type_alignment_from_id(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> u64 {
let ty = resolve_type(tid, type_map);
type_alignment(&ty, type_map)
}
fn align_to(value: u64, alignment: u64) -> u64 {
if alignment == 0 {
return value;
}
((value + alignment - 1) / alignment) * alignment
}
#[allow(dead_code)]
fn is_sse_type(ty: &Type) -> bool {
matches!(
ty.kind,
TypeKind::Float | TypeKind::Double | TypeKind::Half | TypeKind::BFloat
)
}
#[allow(dead_code)]
fn is_x87_type(ty: &Type) -> bool {
matches!(ty.kind, TypeKind::X86FP80)
}
#[allow(dead_code)]
fn is_integer_like(ty: &Type) -> bool {
matches!(ty.kind, TypeKind::Integer { .. } | TypeKind::Pointer { .. })
}
pub fn classify_arg_type(
ty: &Type,
offset: i64,
type_map: &HashMap<TypeId, Type>,
) -> Vec<X86ArgClass> {
let size = type_size(ty, type_map);
match &ty.kind {
TypeKind::Integer { bits } => {
let bytes = (*bits + 7) / 8;
let num_eightbytes = ((bytes as i64 + 7) / 8).max(1);
let classes = vec![X86ArgClass::Integer; num_eightbytes as usize];
classes
}
TypeKind::Pointer { .. } => {
vec![X86ArgClass::Integer]
}
TypeKind::Half | TypeKind::BFloat | TypeKind::Float | TypeKind::Double => {
vec![X86ArgClass::SSE]
}
TypeKind::FP128 | TypeKind::PPCFP128 => {
vec![X86ArgClass::SSE, X86ArgClass::SSEUp]
}
TypeKind::X86FP80 => {
if offset % 16 == 0 {
vec![X86ArgClass::X87]
} else {
vec![X86ArgClass::Memory]
}
}
TypeKind::X86MMX => {
vec![X86ArgClass::SSE]
}
TypeKind::Void
| TypeKind::Label
| TypeKind::Metadata
| TypeKind::Token
| TypeKind::X86AMX => {
vec![]
}
TypeKind::Array { .. }
| TypeKind::Struct { .. }
| TypeKind::FixedVector { .. }
| TypeKind::ScalableVector { .. }
| TypeKind::Function { .. } => {
let mut classes = Vec::new();
classify_aggregate_impl(ty, 0, offset, type_map, &mut classes);
post_merger_cleanup(&mut classes, size);
classes
}
}
}
pub fn classify_aggregate_type(ty: &Type, type_map: &HashMap<TypeId, Type>) -> Vec<X86ArgClass> {
let size = type_size(ty, type_map);
let mut classes = Vec::new();
classify_aggregate_impl(ty, 0, 0, type_map, &mut classes);
post_merger_cleanup(&mut classes, size);
classes
}
fn classify_aggregate_impl(
ty: &Type,
start_offset: i64,
base_offset: i64,
type_map: &HashMap<TypeId, Type>,
classes: &mut Vec<X86ArgClass>,
) {
let size = type_size(ty, type_map) as i64;
let num_eightbytes = ((size + 7) / 8).max(1) as usize;
while classes.len() < num_eightbytes {
classes.push(X86ArgClass::NoClass);
}
match &ty.kind {
TypeKind::Struct {
is_packed,
element_type_ids,
..
} => {
let mut field_offset = 0i64;
for tid in element_type_ids {
let field_ty = resolve_type(*tid, type_map);
let field_size = type_size(&field_ty, type_map) as i64;
let field_align = type_alignment(&field_ty, type_map) as i64;
if !(*is_packed) && field_align > 1 {
field_offset = align_to_i64(field_offset, field_align);
}
let abs_offset = base_offset + start_offset + field_offset;
classify_field_in_eightbytes(
&field_ty,
field_offset,
abs_offset,
type_map,
classes,
);
field_offset += field_size;
}
}
TypeKind::Array {
len,
element_type_id,
} => {
let elem_ty = resolve_type(*element_type_id, type_map);
let elem_size = type_size(&elem_ty, type_map) as i64;
for i in 0..(*len as i64) {
let field_offset = i * elem_size;
let abs_offset = base_offset + start_offset + field_offset;
classify_field_in_eightbytes(&elem_ty, field_offset, abs_offset, type_map, classes);
}
}
TypeKind::FixedVector {
len,
element_type_id,
} => {
let elem_ty = resolve_type(*element_type_id, type_map);
let elem_size = type_size(&elem_ty, type_map) as i64;
for i in 0..(*len as i64) {
let field_offset = i * elem_size;
let abs_offset = base_offset + start_offset + field_offset;
let elem_classes = classify_arg_type(&elem_ty, abs_offset, type_map);
merge_classes_into(classes, &elem_classes, start_offset + field_offset);
}
}
TypeKind::ScalableVector { .. } => {
for c in classes.iter_mut() {
*c = X86ArgClass::Memory;
}
}
_ => {
let sub_classes = classify_arg_type(ty, base_offset + start_offset, type_map);
for (i, cls) in sub_classes.iter().enumerate() {
let idx = i + (start_offset as usize / 8);
if idx < classes.len() {
classes[idx] = merge_classes(classes[idx], *cls);
}
}
}
}
}
fn classify_field_in_eightbytes(
field_ty: &Type,
field_offset: i64, abs_offset: i64, type_map: &HashMap<TypeId, Type>,
classes: &mut Vec<X86ArgClass>,
) {
let _field_size = type_size(field_ty, type_map) as i64;
let field_classes = classify_arg_type(field_ty, abs_offset, type_map);
merge_classes_into(classes, &field_classes, field_offset);
}
fn merge_classes_into(
classes: &mut Vec<X86ArgClass>,
field_classes: &[X86ArgClass],
field_offset: i64,
) {
let start_eightbyte = (field_offset / 8) as usize;
for (i, cls) in field_classes.iter().enumerate() {
let idx = start_eightbyte + i;
if idx < classes.len() {
classes[idx] = merge_classes(classes[idx], *cls);
}
}
}
fn merge_classes(a: X86ArgClass, b: X86ArgClass) -> X86ArgClass {
use X86ArgClass::*;
match (a, b) {
(Memory, _) | (_, Memory) => Memory,
(NoClass, other) | (other, NoClass) => other,
(Integer, Integer) => Integer,
(SSE, SSE) | (SSE, SSEUp) | (SSEUp, SSE) | (SSEUp, SSEUp) => SSE,
(Integer, SSE) | (SSE, Integer) => SSE,
(SSEUp, Integer) | (Integer, SSEUp) => SSE,
(X87, X87) => X87,
(X87, X87Up) | (X87Up, X87) => X87,
(X87, Integer) | (Integer, X87) => Memory,
(X87, SSE) | (SSE, X87) => Memory,
(X87Up, X87Up) => X87Up,
(X87Up, Integer) | (Integer, X87Up) => Memory,
(X87Up, SSE) | (SSE, X87Up) => Memory,
(SSEUp, X87) | (X87, SSEUp) => Memory,
(SSEUp, X87Up) | (X87Up, SSEUp) => Memory,
(ComplexX87, _) | (_, ComplexX87) => ComplexX87,
}
}
fn post_merger_cleanup(classes: &mut Vec<X86ArgClass>, _total_size: u64) {
if classes.is_empty() {
return;
}
let num_eightbytes = classes.len();
if num_eightbytes > 2 {
let all_sse = classes.iter().all(|c| {
matches!(
c,
X86ArgClass::SSE | X86ArgClass::SSEUp | X86ArgClass::NoClass
)
});
let first_is_sse = matches!(classes[0], X86ArgClass::SSE);
if !(all_sse && first_is_sse) {
for c in classes.iter_mut() {
*c = X86ArgClass::Memory;
}
return;
}
}
for i in 0..num_eightbytes {
if classes[i] == X86ArgClass::SSEUp {
if i == 0 || classes[i - 1] != X86ArgClass::SSE {
classes[i] = X86ArgClass::SSE;
}
}
}
if num_eightbytes == 2 {
if classes[0] == X86ArgClass::SSE && classes[1] == X86ArgClass::SSEUp {
} else if classes[0] == X86ArgClass::Integer && classes[1] == X86ArgClass::Integer {
} else if classes[0] == X86ArgClass::NoClass && classes[1] == X86ArgClass::Integer {
} else if classes[0] == X86ArgClass::Integer && classes[1] == X86ArgClass::NoClass {
} else if classes[0] == X86ArgClass::SSE && classes[1] == X86ArgClass::NoClass {
} else {
for c in classes.iter_mut() {
*c = X86ArgClass::Memory;
}
}
}
}
pub fn assign_sysv_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let int_regs = vec![RDI, RSI, RDX, RCX, R8, R9];
let sse_regs = vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7];
let mut arg_infos = Vec::new();
let mut int_used = 0usize;
let mut sse_used = 0usize;
let mut stack_offset = 8i64; let mut arg_offsets = Vec::new();
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map);
let classes = classify_arg_type(ty, 0, type_map);
let mut info = X86ArgInfo {
size: size as u32,
alignment: align as u32,
..Default::default()
};
if classes.iter().any(|c| *c == X86ArgClass::Memory) {
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
info.stack_offset = stack_offset;
info.padding = padding as u32;
info.is_byval = ty.is_struct();
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
} else {
let int_needed = classes
.iter()
.filter(|c| **c == X86ArgClass::Integer)
.count();
let sse_needed = classes
.iter()
.filter(|c| matches!(c, X86ArgClass::SSE | X86ArgClass::SSEUp))
.count();
if int_used + int_needed <= int_regs.len() && sse_used + sse_needed <= sse_regs.len() {
info.in_reg = true;
let mut ridx = int_used;
for cls in &classes {
match cls {
X86ArgClass::Integer => {
if ridx < int_regs.len() {
info.regs.push(int_regs[ridx]);
ridx += 1;
}
}
X86ArgClass::SSE | X86ArgClass::SSEUp => {
if sse_used < sse_regs.len() {
info.regs.push(sse_regs[sse_used]);
sse_used += 1;
}
}
X86ArgClass::NoClass => {}
_ => {}
}
}
int_used += int_needed;
info.stack_offset = 0; arg_offsets.push(-1); } else {
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
info.stack_offset = stack_offset;
info.padding = padding as u32;
info.is_byval = ty.is_struct();
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
}
}
arg_infos.push(info);
}
let alignment = 16i64;
let padding = if stack_offset % alignment != 0 {
alignment - (stack_offset % alignment)
} else {
0
};
let frame = X86CallFrame {
stack_size: stack_offset + padding,
arg_offsets,
return_save_area: None,
shadow_store_size: 0,
alignment_padding: padding,
};
(arg_infos, frame)
}
fn sysv_return_regs_from_classes(classes: &[X86ArgClass]) -> Vec<u16> {
let mut regs = Vec::new();
for cls in classes {
match cls {
X86ArgClass::Integer => {
if regs.is_empty() {
regs.push(RAX);
} else if regs.len() == 1 {
regs.push(RDX);
}
}
X86ArgClass::SSE | X86ArgClass::SSEUp => {
if regs.is_empty() {
regs.push(XMM0);
} else if regs.len() == 1 {
regs.push(XMM1);
}
}
X86ArgClass::X87 => {
regs.push(ST0);
}
X86ArgClass::Memory => {
regs.push(RAX);
}
_ => {}
}
}
if regs.is_empty() {
regs.push(RAX); }
regs
}
fn assign_win64_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let int_regs = vec![RCX, RDX, R8, R9];
let sse_regs = vec![XMM0, XMM1, XMM2, XMM3];
let shadow_size: i64 = 32;
let mut arg_infos = Vec::new();
let mut slot = 0usize; let mut stack_offset = shadow_size + 8; let mut arg_offsets = Vec::new();
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map).max(8);
let mut info = X86ArgInfo {
size: size as u32,
alignment: align as u32,
..Default::default()
};
if ty.is_floating_point() && size <= 8 {
if slot < sse_regs.len() {
info.in_reg = true;
info.regs.push(sse_regs[slot]);
slot += 1;
} else {
stack_offset = align_to_i64(stack_offset, align as i64);
info.stack_offset = stack_offset;
stack_offset += 8;
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
continue;
}
} else if size <= 8 {
if slot < int_regs.len() {
info.in_reg = true;
info.regs.push(int_regs[slot]);
slot += 1;
} else {
stack_offset = align_to_i64(stack_offset, align as i64);
info.stack_offset = stack_offset;
stack_offset += 8;
}
} else if size <= 16 && (ty.is_struct() || ty.is_vector()) {
if slot < int_regs.len() {
info.in_reg = true;
info.regs.push(int_regs[slot]);
slot += 1;
if size > 8 && slot < int_regs.len() {
info.regs.push(int_regs[slot]);
slot += 1;
}
} else {
stack_offset = align_to_i64(stack_offset, align as i64);
info.stack_offset = stack_offset;
info.is_byval = true;
stack_offset += size as i64;
}
} else {
if slot < int_regs.len() {
info.in_reg = true;
info.regs.push(int_regs[slot]);
info.is_byval = true;
slot += 1;
} else {
stack_offset = align_to_i64(stack_offset, 8);
info.stack_offset = stack_offset;
info.is_byval = true;
stack_offset += 8;
}
}
if !info.in_reg {
arg_offsets.push(info.stack_offset);
} else {
arg_offsets.push(-1);
}
arg_infos.push(info);
}
let alignment = 16i64;
let padding = if stack_offset % alignment != 0 {
alignment - (stack_offset % alignment)
} else {
0
};
let frame = X86CallFrame {
stack_size: stack_offset + padding,
arg_offsets,
return_save_area: None,
shadow_store_size: shadow_size,
alignment_padding: padding,
};
(arg_infos, frame)
}
fn win64_return_regs(ty: &Type, size: u64) -> Vec<u16> {
if ty.is_floating_point() && size <= 8 {
vec![XMM0]
} else if size <= 8 {
vec![RAX]
} else if size <= 16 && (ty.is_struct() || ty.is_vector()) {
vec![RAX, RDX]
} else {
vec![RAX]
}
}
fn assign_cdecl_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
assign_stack_only_args(args, type_map)
}
fn assign_stdcall_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
assign_stack_only_args(args, type_map)
}
fn assign_fastcall_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let int_regs = vec![ECX, EDX];
let mut arg_infos = Vec::new();
let mut int_used = 0usize;
let mut stack_offset = 4i64; let mut arg_offsets = Vec::new();
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map).max(4);
let mut info = X86ArgInfo {
size: size as u32,
alignment: align as u32,
..Default::default()
};
if size <= 4 && int_used < int_regs.len() {
info.in_reg = true;
info.regs.push(int_regs[int_used]);
int_used += 1;
arg_offsets.push(-1);
} else {
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
info.stack_offset = stack_offset;
info.padding = padding as u32;
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
}
arg_infos.push(info);
}
let frame = X86CallFrame {
stack_size: stack_offset,
arg_offsets,
return_save_area: None,
shadow_store_size: 0,
alignment_padding: 0,
};
(arg_infos, frame)
}
fn assign_thiscall_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let mut arg_infos = Vec::new();
let mut this_assigned = false;
let mut stack_offset = 4i64;
let mut arg_offsets = Vec::new();
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map).max(4);
let mut info = X86ArgInfo {
size: size as u32,
alignment: align as u32,
..Default::default()
};
if !this_assigned {
info.in_reg = true;
info.regs.push(ECX);
this_assigned = true;
arg_offsets.push(-1);
} else {
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
info.stack_offset = stack_offset;
info.padding = padding as u32;
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
}
arg_infos.push(info);
}
let frame = X86CallFrame {
stack_size: stack_offset,
arg_offsets,
return_save_area: None,
shadow_store_size: 0,
alignment_padding: 0,
};
(arg_infos, frame)
}
fn assign_vectorcall_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let int_regs = vec![ECX, EDX];
let xmm_regs = vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5];
let mut arg_infos = Vec::new();
let mut int_used = 0usize;
let mut xmm_used = 0usize;
let mut stack_offset = 4i64;
let mut arg_offsets = Vec::new();
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map).max(4);
let mut info = X86ArgInfo {
size: size as u32,
alignment: align as u32,
..Default::default()
};
if ty.is_vector() && xmm_used < xmm_regs.len() {
info.in_reg = true;
info.regs.push(xmm_regs[xmm_used]);
xmm_used += 1;
arg_offsets.push(-1);
} else if size <= 4 && int_used < int_regs.len() {
info.in_reg = true;
info.regs.push(int_regs[int_used]);
int_used += 1;
arg_offsets.push(-1);
} else {
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
info.stack_offset = stack_offset;
info.padding = padding as u32;
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
}
arg_infos.push(info);
}
let frame = X86CallFrame {
stack_size: stack_offset,
arg_offsets,
return_save_area: None,
shadow_store_size: 0,
alignment_padding: 0,
};
(arg_infos, frame)
}
fn assign_stack_only_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<X86ArgInfo>, X86CallFrame) {
let mut arg_infos = Vec::new();
let mut stack_offset = 4i64; let mut arg_offsets = Vec::new();
let ptr_size: i64 = 8;
for ty in args {
let size = type_size(ty, type_map);
let align = type_alignment(ty, type_map);
let padding = if stack_offset % (align as i64) != 0 {
(align as i64) - (stack_offset % (align as i64))
} else {
0
};
stack_offset += padding;
let info = X86ArgInfo {
in_reg: false,
stack_offset,
size: size as u32,
alignment: align as u32,
is_byval: ty.is_struct() && size > ptr_size as u64,
is_sret: false,
padding: padding as u32,
..Default::default()
};
stack_offset += size as i64;
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
}
let frame = X86CallFrame {
stack_size: stack_offset,
arg_offsets,
return_save_area: None,
shadow_store_size: 0,
alignment_padding: 0,
};
(arg_infos, frame)
}
fn cdecl_return_regs(ty: &Type, type_map: &HashMap<TypeId, Type>) -> Vec<u16> {
match &ty.kind {
TypeKind::Void => vec![],
TypeKind::X86FP80 => vec![ST0],
TypeKind::Float | TypeKind::Double => {
vec![XMM0]
}
TypeKind::Integer { bits } if *bits <= 32 => vec![EAX],
TypeKind::Integer { bits } if *bits <= 64 => vec![EDX, EAX],
TypeKind::Pointer { .. } => vec![EAX],
_ => {
let size = type_size(ty, type_map);
if size <= 4 {
vec![EAX]
} else if size <= 8 {
vec![EDX, EAX]
} else {
vec![EAX]
}
}
}
}
fn align_to_i64(value: i64, alignment: i64) -> i64 {
if alignment == 0 {
return value;
}
((value + alignment - 1) / alignment) * alignment
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EightByteClass {
NO_CLASS,
INTEGER,
SSE,
SSEUP,
X87,
X87UP,
COMPLEX_X87,
MEMORY,
}
impl EightByteClass {
pub fn is_register_class(&self) -> bool {
matches!(
self,
Self::INTEGER | Self::SSE | Self::SSEUP | Self::X87 | Self::X87UP
)
}
pub fn can_merge_with(&self, other: &Self) -> bool {
match (self, other) {
(Self::INTEGER, Self::SSE) | (Self::SSE, Self::INTEGER) => false,
(Self::INTEGER, Self::INTEGER) => true,
(Self::SSE, Self::SSEUP) | (Self::SSEUP, Self::SSE) => true,
(Self::X87, Self::X87UP) | (Self::X87UP, Self::X87) => true,
(Self::SSE, Self::SSE) => true,
(Self::INTEGER, Self::NO_CLASS) | (Self::NO_CLASS, Self::INTEGER) => true,
(Self::SSE, Self::NO_CLASS) | (Self::NO_CLASS, Self::SSE) => true,
(a, b) if a == b => true,
_ => false,
}
}
pub fn post_merge(a: Self, b: Self) -> (Self, Self) {
match (a, b) {
(Self::MEMORY, _) | (_, Self::MEMORY) => (Self::MEMORY, Self::MEMORY),
(Self::INTEGER, Self::SSE) | (Self::SSE, Self::INTEGER) => (Self::MEMORY, Self::MEMORY),
(Self::SSEUP, Self::SSE) => (Self::SSE, Self::SSEUP),
(Self::X87UP, Self::X87) => (Self::X87, Self::X87UP),
(Self::SSEUP, Self::SSEUP) => (Self::SSE, Self::SSEUP),
(Self::X87UP, Self::X87UP) => (Self::X87, Self::X87UP),
other => other,
}
}
}
#[derive(Debug, Clone)]
pub struct EightByteClassification {
pub classes: [EightByteClass; 4],
pub num_eightbytes: u8,
pub is_indirect: bool,
pub is_hfa: bool,
pub hfa_count: u8,
}
impl EightByteClassification {
pub fn new() -> Self {
Self {
classes: [EightByteClass::NO_CLASS; 4],
num_eightbytes: 0,
is_indirect: false,
is_hfa: false,
hfa_count: 0,
}
}
pub fn memory() -> Self {
Self {
classes: [EightByteClass::MEMORY; 4],
num_eightbytes: 0,
is_indirect: true,
is_hfa: false,
hfa_count: 0,
}
}
pub fn needs_sret(&self) -> bool {
self.is_indirect || self.classes[0] == EightByteClass::MEMORY
}
pub fn gpr_count(&self) -> u8 {
self.classes
.iter()
.take(self.num_eightbytes as usize)
.filter(|c| **c == EightByteClass::INTEGER)
.count() as u8
}
pub fn sse_count(&self) -> u8 {
self.classes
.iter()
.take(self.num_eightbytes as usize)
.filter(|c| matches!(c, EightByteClass::SSE | EightByteClass::SSEUP))
.count() as u8
}
}
impl Default for EightByteClassification {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct HFADetector;
impl HFADetector {
pub fn detect_hfa(fields: &[HfaFieldInfo]) -> Option<(HfaBaseType, u8)> {
if fields.is_empty() {
return None;
}
if fields.len() > 4 {
return None; }
let base = fields[0].base_type;
if base == HfaBaseType::None {
return None;
}
for field in fields.iter().skip(1) {
if field.base_type != base {
return None;
}
}
let count = fields.len() as u8;
Some((base, count))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HfaBaseType {
None,
Float,
Double,
Float128,
Float16,
BFloat16,
Vector64,
Vector128,
}
#[derive(Debug, Clone, Copy)]
pub struct HfaFieldInfo {
pub base_type: HfaBaseType,
pub offset: u32,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct MicrosoftX64CC {
pub shadow_space: u32,
pub is_vectorcall: bool,
pub is_fastcall: bool,
pub stack_alignment: u32,
}
impl MicrosoftX64CC {
pub fn standard() -> Self {
Self {
shadow_space: 32,
is_vectorcall: false,
is_fastcall: false,
stack_alignment: 16,
}
}
pub fn vectorcall() -> Self {
Self {
shadow_space: 32,
is_vectorcall: true,
is_fastcall: false,
stack_alignment: 16,
}
}
pub fn fastcall() -> Self {
Self {
shadow_space: 32,
is_vectorcall: false,
is_fastcall: true,
stack_alignment: 16,
}
}
pub fn int_arg_regs(&self) -> &[u16] {
if self.is_fastcall {
&[RCX, RDX]
} else {
&[RCX, RDX, R8, R9]
}
}
pub fn xmm_arg_regs(&self) -> &[u16] {
if self.is_vectorcall {
&[XMM0, XMM1, XMM2, XMM3, XMM4, XMM5]
} else {
&[XMM0, XMM1, XMM2, XMM3]
}
}
pub fn caller_saved_regs(&self) -> Vec<u16> {
vec![RAX, RCX, RDX, R8, R9, R10, R11]
}
pub fn callee_saved_regs(&self) -> Vec<u16> {
vec![RBX, RBP, RDI, RSI, R12, R13, R14, R15]
}
pub fn shadow_space_size(&self) -> u32 {
self.shadow_space
}
pub fn is_indirect(&self, size: u32) -> bool {
size > 8 && !size.is_power_of_two()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86_32CallingConvention {
CDecl,
StdCall,
FastCall,
ThisCallMSVC,
ThisCallGNU,
VectorCall,
RegCall,
Pascal,
}
impl X86_32CallingConvention {
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",
}
}
pub fn callee_cleans_stack(&self) -> bool {
matches!(
self,
Self::StdCall | Self::FastCall | Self::ThisCallMSVC | Self::Pascal
)
}
pub fn int_arg_regs(&self) -> &[u16] {
match self {
Self::FastCall | Self::ThisCallMSVC => &[ECX, EDX],
Self::ThisCallGNU => &[ECX],
Self::VectorCall => &[ECX, EDX],
Self::RegCall => &[EAX, ECX, EDX, EDI, ESI],
_ => &[],
}
}
pub fn xmm_arg_regs(&self) -> &[u16] {
match self {
Self::VectorCall => &[XMM0, XMM1, XMM2, XMM3],
_ => &[],
}
}
pub fn stack_alignment(&self) -> u32 {
match self {
Self::VectorCall => 16,
_ => 4,
}
}
pub fn has_this_in_ecx(&self) -> bool {
matches!(self, Self::ThisCallMSVC | Self::ThisCallGNU)
}
}
#[derive(Debug, Clone)]
pub struct RegCallConvention {
pub int_regs: Vec<u16>,
pub xmm_regs: Vec<u16>,
pub ret_regs: Vec<u16>,
}
impl RegCallConvention {
pub fn new_32() -> Self {
Self {
int_regs: vec![EAX, ECX, EDX, EDI, ESI],
xmm_regs: vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7],
ret_regs: vec![EAX, EDX],
}
}
pub fn assign_param(
&self,
used_int: &mut usize,
used_xmm: &mut usize,
is_fp: bool,
) -> Option<u16> {
if is_fp && *used_xmm < self.xmm_regs.len() {
let reg = self.xmm_regs[*used_xmm];
*used_xmm += 1;
Some(reg)
} else if !is_fp && *used_int < self.int_regs.len() {
let reg = self.int_regs[*used_int];
*used_int += 1;
Some(reg)
} else {
None
}
}
pub fn remaining_int(&self, used: usize) -> usize {
self.int_regs.len().saturating_sub(used)
}
pub fn remaining_xmm(&self, used: usize) -> usize {
self.xmm_regs.len().saturating_sub(used)
}
}
#[derive(Debug, Clone)]
pub struct X32ABI {
pub pointer_size: u8,
pub long_size: u8,
pub size_t_size: u8,
pub stack_alignment: u32,
}
impl X32ABI {
pub fn new() -> Self {
Self {
pointer_size: 4,
long_size: 4,
size_t_size: 4,
stack_alignment: 16,
}
}
pub fn is_x32(&self) -> bool {
self.pointer_size == 4 && self.size_t_size == 4
}
pub fn pointer_register_class(&self) -> &'static str {
"r32"
}
pub fn zero_extend_args(&self) -> bool {
true
}
pub fn available_gprs(&self) -> u8 {
16 }
}
impl Default for X32ABI {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ReturnValueLowering {
pub sret_pointer: Option<u16>,
pub ret_regs: Vec<ReturnRegister>,
pub is_multi_reg: bool,
pub size: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct ReturnRegister {
pub reg: u16,
pub offset: u32,
pub size: u8,
pub is_xmm: bool,
}
impl ReturnValueLowering {
pub fn new() -> Self {
Self {
sret_pointer: None,
ret_regs: Vec::new(),
is_multi_reg: false,
size: 0,
}
}
pub fn sret() -> Self {
Self {
sret_pointer: Some(RDI), ret_regs: Vec::new(),
is_multi_reg: false,
size: 0,
}
}
pub fn int_ret_rax(size: u8) -> Self {
Self {
sret_pointer: None,
ret_regs: vec![ReturnRegister {
reg: RAX,
offset: 0,
size,
is_xmm: false,
}],
is_multi_reg: false,
size: size as u32,
}
}
pub fn pair_ret_rax_rdx(size: u32) -> Self {
let half = (size / 2) as u8;
Self {
sret_pointer: None,
ret_regs: vec![
ReturnRegister {
reg: RAX,
offset: 0,
size: half,
is_xmm: false,
},
ReturnRegister {
reg: RDX,
offset: half as u32,
size: half,
is_xmm: false,
},
],
is_multi_reg: true,
size,
}
}
pub fn fp_ret_xmm0() -> Self {
Self {
sret_pointer: None,
ret_regs: vec![ReturnRegister {
reg: XMM0,
offset: 0,
size: 8,
is_xmm: true,
}],
is_multi_reg: false,
size: 8,
}
}
pub fn hfa_ret(count: u8) -> Self {
let xmm_regs = [XMM0, XMM1, XMM2, XMM3];
let mut ret_regs = Vec::new();
for i in 0..count.min(4) {
ret_regs.push(ReturnRegister {
reg: xmm_regs[i as usize],
offset: (i as u32) * 8,
size: 8,
is_xmm: true,
});
}
Self {
sret_pointer: None,
ret_regs,
is_multi_reg: count > 1,
size: count as u32 * 8,
}
}
}
impl Default for ReturnValueLowering {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct VarArgsInfo {
pub gp_offset_offset: u32,
pub fp_offset_offset: u32,
pub overflow_arg_area_offset: u32,
pub reg_save_area_offset: u32,
pub va_list_size: u32,
pub num_gpr_save: u8,
pub num_xmm_save: u8,
pub reg_save_area_size: u32,
}
impl VarArgsInfo {
pub fn sysv_64() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 4,
overflow_arg_area_offset: 8,
reg_save_area_offset: 16,
va_list_size: 24,
num_gpr_save: 6,
num_xmm_save: 8,
reg_save_area_size: 6 * 8 + 8 * 16, }
}
pub fn ms_64() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 4,
overflow_arg_area_offset: 8,
reg_save_area_offset: 16,
va_list_size: 24,
num_gpr_save: 4,
num_xmm_save: 4,
reg_save_area_size: 4 * 8 + 4 * 16,
}
}
pub fn x86_32() -> Self {
Self {
gp_offset_offset: 0,
fp_offset_offset: 0,
overflow_arg_area_offset: 4,
reg_save_area_offset: 0,
va_list_size: 8,
num_gpr_save: 0,
num_xmm_save: 0,
reg_save_area_size: 0,
}
}
pub fn compute_reg_save_area(&self) -> Vec<(u16, u32, u32)> {
let mut area = Vec::new();
let mut offset: u32 = 0;
let gprs = [RDI, RSI, RDX, RCX, R8, R9];
for i in 0..self.num_gpr_save.min(6) {
area.push((gprs[i as usize], offset, 8));
offset += 8;
}
let xmms = [XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7];
for i in 0..self.num_xmm_save.min(8) {
area.push((xmms[i as usize], offset, 16));
offset += 16;
}
area
}
}
#[derive(Debug, Clone)]
pub struct StackFrameLayout {
pub frame_size: u32,
pub return_address_offset: i32,
pub saved_rbp_offset: i32,
pub callee_saved_start: i32,
pub callee_saved_size: u32,
pub locals_offset: i32,
pub locals_size: u32,
pub outgoing_args_offset: i32,
pub outgoing_args_size: u32,
pub has_red_zone: bool,
pub red_zone_size: u32,
pub stack_alignment: u32,
}
impl StackFrameLayout {
pub fn new() -> Self {
Self {
frame_size: 0,
return_address_offset: 8,
saved_rbp_offset: 0,
callee_saved_start: 0,
callee_saved_size: 0,
locals_offset: 0,
locals_size: 0,
outgoing_args_offset: 0,
outgoing_args_size: 0,
has_red_zone: true,
red_zone_size: 128,
stack_alignment: 16,
}
}
pub fn compute_frame_size(&mut self) {
let mut size = self.callee_saved_size + self.locals_size + self.outgoing_args_size;
if size % self.stack_alignment != 0 {
size = (size + self.stack_alignment - 1) / self.stack_alignment * self.stack_alignment;
}
self.frame_size = size;
}
pub fn local_offset_from_rbp(&self, index: usize, size: u32) -> i32 {
let base = -((index + 1) as i32) * size as i32;
base - self.callee_saved_size as i32
}
pub fn arg_offset_from_rsp(&self, arg_index: usize) -> i32 {
8 + self.callee_saved_size as i32 + (arg_index * 8) as i32
}
pub fn can_omit_frame_pointer(&self) -> bool {
self.locals_size == 0 && self.callee_saved_size == 0 && self.outgoing_args_size == 0
}
pub fn disable_red_zone(&mut self) {
self.has_red_zone = false;
self.red_zone_size = 0;
}
}
impl Default for StackFrameLayout {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ParameterSplitter {
pub chunks: Vec<ParamChunk>,
pub total_size: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct ParamChunk {
pub reg_class: ParamRegClass,
pub offset: u32,
pub size: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamRegClass {
GPR,
SSE,
Memory,
}
impl ParameterSplitter {
pub fn new() -> Self {
Self {
chunks: Vec::new(),
total_size: 0,
}
}
pub fn split_into_eightbytes(size: u32, classification: &EightByteClassification) -> Self {
let mut splitter = Self::new();
splitter.total_size = size;
let num_eightbytes = (size + 7) / 8;
for i in 0..num_eightbytes {
let class = if (i as usize) < classification.classes.len() {
classification.classes[i as usize]
} else {
EightByteClass::NO_CLASS
};
let reg_class = match class {
EightByteClass::INTEGER => ParamRegClass::GPR,
EightByteClass::SSE | EightByteClass::SSEUP => ParamRegClass::SSE,
EightByteClass::MEMORY => ParamRegClass::Memory,
_ => ParamRegClass::Memory,
};
let chunk_size = if i == num_eightbytes - 1 && size % 8 != 0 {
(size % 8) as u8
} else {
8
};
splitter.chunks.push(ParamChunk {
reg_class,
offset: i * 8,
size: chunk_size,
});
}
splitter
}
pub fn gpr_needed(&self) -> u8 {
self.chunks
.iter()
.filter(|c| c.reg_class == ParamRegClass::GPR)
.count() as u8
}
pub fn sse_needed(&self) -> u8 {
self.chunks
.iter()
.filter(|c| c.reg_class == ParamRegClass::SSE)
.count() as u8
}
pub fn must_use_memory(&self, available_gprs: u8, available_sse: u8) -> bool {
self.gpr_needed() > available_gprs || self.sse_needed() > available_sse
}
}
impl Default for ParameterSplitter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Type, TypeId, TypeKind, TypeStore};
fn make_type_map() -> HashMap<TypeId, Type> {
let mut store = TypeStore::new();
let mut map = HashMap::new();
let types: Vec<Type> = vec![
Type::void(),
Type::i1(),
Type::i8(),
Type::i16(),
Type::i32(),
Type::i64(),
Type::i128(),
Type::float(),
Type::double(),
Type::half(),
Type::bfloat(),
Type::fp128(),
Type::x86_fp80(),
Type::ppc_fp128(),
Type::x86_mmx(),
Type::pointer(0),
];
for ty in types {
let uniqued = store.insert_or_get(ty.clone());
map.insert(uniqued.id, uniqued);
}
map
}
fn register_type(ty: Type, store: &mut TypeStore, map: &mut HashMap<TypeId, Type>) -> TypeId {
let uniqued = store.insert_or_get(ty);
let id = uniqued.id;
map.insert(id, uniqued);
id
}
#[test]
fn test_arg_class_is_register_class() {
assert!(X86ArgClass::Integer.is_register_class());
assert!(X86ArgClass::SSE.is_register_class());
assert!(X86ArgClass::SSEUp.is_register_class());
assert!(X86ArgClass::X87.is_register_class());
assert!(!X86ArgClass::NoClass.is_register_class());
assert!(!X86ArgClass::Memory.is_register_class());
assert!(!X86ArgClass::X87Up.is_register_class());
assert!(!X86ArgClass::ComplexX87.is_register_class());
}
#[test]
fn test_arg_class_needs_xmm() {
assert!(X86ArgClass::SSE.needs_xmm());
assert!(X86ArgClass::SSEUp.needs_xmm());
assert!(!X86ArgClass::Integer.needs_xmm());
assert!(!X86ArgClass::NoClass.needs_xmm());
}
#[test]
fn test_cc_name() {
assert_eq!(X86CallingConvention::C.name(), "cdecl");
assert_eq!(X86CallingConvention::Fast.name(), "fastcall");
assert_eq!(X86CallingConvention::StdCall.name(), "stdcall");
assert_eq!(X86CallingConvention::ThisCall.name(), "thiscall");
assert_eq!(X86CallingConvention::VectorCall.name(), "vectorcall");
assert_eq!(X86CallingConvention::X86_64_SysV.name(), "System V AMD64");
assert_eq!(X86CallingConvention::Win64.name(), "Microsoft x64");
assert_eq!(X86CallingConvention::X86_RegCall.name(), "regcall");
assert_eq!(X86CallingConvention::PreserveAll.name(), "preserve_all");
assert_eq!(X86CallingConvention::PreserveMost.name(), "preserve_most");
assert_eq!(X86CallingConvention::GHC.name(), "GHC");
assert_eq!(X86CallingConvention::AnyReg.name(), "anyreg");
}
#[test]
fn test_cc_is_64bit() {
assert!(X86CallingConvention::X86_64_SysV.is_64bit());
assert!(X86CallingConvention::Win64.is_64bit());
assert!(X86CallingConvention::X86_RegCall.is_64bit());
assert!(!X86CallingConvention::C.is_64bit());
assert!(!X86CallingConvention::Fast.is_64bit());
assert!(!X86CallingConvention::StdCall.is_64bit());
assert!(!X86CallingConvention::ThisCall.is_64bit());
}
#[test]
fn test_cc_callee_cleans_stack() {
assert!(!X86CallingConvention::C.callee_cleans_stack());
assert!(X86CallingConvention::StdCall.callee_cleans_stack());
assert!(X86CallingConvention::ThisCall.callee_cleans_stack());
assert!(X86CallingConvention::Fast.callee_cleans_stack());
assert!(!X86CallingConvention::X86_64_SysV.callee_cleans_stack());
}
#[test]
fn test_cc_num_int_param_regs() {
assert_eq!(X86CallingConvention::C.get_num_int_param_regs(), 0);
assert_eq!(X86CallingConvention::Fast.get_num_int_param_regs(), 2);
assert_eq!(X86CallingConvention::ThisCall.get_num_int_param_regs(), 1);
assert_eq!(
X86CallingConvention::X86_64_SysV.get_num_int_param_regs(),
6
);
assert_eq!(X86CallingConvention::Win64.get_num_int_param_regs(), 4);
assert_eq!(
X86CallingConvention::X86_RegCall.get_num_int_param_regs(),
5
);
}
#[test]
fn test_cc_num_sse_param_regs() {
assert_eq!(X86CallingConvention::C.get_num_sse_param_regs(), 0);
assert_eq!(X86CallingConvention::VectorCall.get_num_sse_param_regs(), 6);
assert_eq!(
X86CallingConvention::X86_64_SysV.get_num_sse_param_regs(),
8
);
assert_eq!(X86CallingConvention::Win64.get_num_sse_param_regs(), 4);
}
#[test]
fn test_cc_get_int_param_regs_sysv() {
let regs = X86CallingConvention::X86_64_SysV.get_int_param_regs();
assert_eq!(regs, vec![RDI, RSI, RDX, RCX, R8, R9]);
}
#[test]
fn test_cc_get_int_param_regs_win64() {
let regs = X86CallingConvention::Win64.get_int_param_regs();
assert_eq!(regs, vec![RCX, RDX, R8, R9]);
}
#[test]
fn test_cc_get_sse_param_regs_sysv() {
let regs = X86CallingConvention::X86_64_SysV.get_sse_param_regs();
assert_eq!(regs, vec![XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7]);
}
#[test]
fn test_cc_get_stack_alignment() {
assert_eq!(X86CallingConvention::X86_64_SysV.get_stack_alignment(), 16);
assert_eq!(X86CallingConvention::Win64.get_stack_alignment(), 16);
assert_eq!(X86CallingConvention::C.get_stack_alignment(), 4);
}
#[test]
fn test_cc_get_shadow_store_size() {
assert_eq!(X86CallingConvention::Win64.get_shadow_store_size(), 32);
assert_eq!(X86CallingConvention::X86_64_SysV.get_shadow_store_size(), 0);
assert_eq!(X86CallingConvention::C.get_shadow_store_size(), 0);
}
#[test]
fn test_cc_uses_register_params() {
assert!(!X86CallingConvention::C.uses_register_params());
assert!(X86CallingConvention::Fast.uses_register_params());
assert!(!X86CallingConvention::StdCall.uses_register_params());
assert!(X86CallingConvention::ThisCall.uses_register_params());
assert!(X86CallingConvention::X86_64_SysV.uses_register_params());
assert!(X86CallingConvention::Win64.uses_register_params());
}
#[test]
fn test_type_size_primitives() {
let map = make_type_map();
assert_eq!(type_size(&Type::void(), &map), 0);
assert_eq!(type_size(&Type::i1(), &map), 1);
assert_eq!(type_size(&Type::i8(), &map), 1);
assert_eq!(type_size(&Type::i16(), &map), 2);
assert_eq!(type_size(&Type::i32(), &map), 4);
assert_eq!(type_size(&Type::i64(), &map), 8);
assert_eq!(type_size(&Type::i128(), &map), 16);
assert_eq!(type_size(&Type::float(), &map), 4);
assert_eq!(type_size(&Type::double(), &map), 8);
assert_eq!(type_size(&Type::half(), &map), 2);
assert_eq!(type_size(&Type::fp128(), &map), 16);
assert_eq!(type_size(&Type::x86_fp80(), &map), 16);
assert_eq!(type_size(&Type::pointer(0), &map), 8);
assert_eq!(type_size(&Type::x86_mmx(), &map), 8);
}
#[test]
fn test_type_alignment_primitives() {
let map = make_type_map();
assert_eq!(type_alignment(&Type::i8(), &map), 1);
assert_eq!(type_alignment(&Type::i16(), &map), 2);
assert_eq!(type_alignment(&Type::i32(), &map), 4);
assert_eq!(type_alignment(&Type::i64(), &map), 8);
assert_eq!(type_alignment(&Type::i128(), &map), 16);
assert_eq!(type_alignment(&Type::float(), &map), 4);
assert_eq!(type_alignment(&Type::double(), &map), 8);
assert_eq!(type_alignment(&Type::fp128(), &map), 16);
assert_eq!(type_alignment(&Type::pointer(0), &map), 8);
}
#[test]
fn test_type_size_array() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 32 }))
.map(|(id, _)| *id)
.unwrap();
let arr_ty = Type::array_with(10, i32_id);
let arr_id = register_type(arr_ty, &mut store, &mut map);
let arr = resolve_type(arr_id, &map);
assert_eq!(type_size(&arr, &map), 40);
}
#[test]
fn test_type_size_struct() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 32 }))
.map(|(id, _)| *id)
.unwrap();
let i64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 64 }))
.map(|(id, _)| *id)
.unwrap();
let struct_ty = Type::struct_literal_with(false, vec![i32_id, i64_id]);
let struct_id = register_type(struct_ty, &mut store, &mut map);
let s = resolve_type(struct_id, &map);
assert_eq!(type_size(&s, &map), 16);
let packed_ty = Type::struct_literal_with(true, vec![i32_id, i64_id]);
let packed_id = register_type(packed_ty, &mut store, &mut map);
let ps = resolve_type(packed_id, &map);
assert_eq!(type_size(&ps, &map), 12);
}
#[test]
fn test_type_size_vector() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let f32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Float))
.map(|(id, _)| *id)
.unwrap();
let vec_ty = Type::fixed_vector_with(4, f32_id);
let vec_id = register_type(vec_ty, &mut store, &mut map);
let v = resolve_type(vec_id, &map);
assert_eq!(type_size(&v, &map), 16);
assert_eq!(type_alignment(&v, &map), 16);
}
#[test]
fn test_sysv_classify_integer_primitives() {
let map = make_type_map();
let classes = classify_arg_type(&Type::i32(), 0, &map);
assert_eq!(classes, vec![X86ArgClass::Integer]);
let classes = classify_arg_type(&Type::i64(), 0, &map);
assert_eq!(classes, vec![X86ArgClass::Integer]);
let classes = classify_arg_type(&Type::i128(), 0, &map);
assert_eq!(classes.len(), 2);
assert_eq!(classes[0], X86ArgClass::Integer);
assert_eq!(classes[1], X86ArgClass::Integer);
}
#[test]
fn test_sysv_classify_pointer() {
let map = make_type_map();
let classes = classify_arg_type(&Type::pointer(0), 0, &map);
assert_eq!(classes, vec![X86ArgClass::Integer]);
}
#[test]
fn test_sysv_classify_sse_primitives() {
let map = make_type_map();
assert_eq!(
classify_arg_type(&Type::float(), 0, &map),
vec![X86ArgClass::SSE]
);
assert_eq!(
classify_arg_type(&Type::double(), 0, &map),
vec![X86ArgClass::SSE]
);
}
#[test]
fn test_sysv_classify_fp128() {
let map = make_type_map();
let classes = classify_arg_type(&Type::fp128(), 0, &map);
assert_eq!(classes, vec![X86ArgClass::SSE, X86ArgClass::SSEUp]);
}
#[test]
fn test_sysv_classify_x86_fp80() {
let map = make_type_map();
let classes = classify_arg_type(&Type::x86_fp80(), 0, &map);
assert_eq!(classes, vec![X86ArgClass::X87]);
let classes = classify_arg_type(&Type::x86_fp80(), 8, &map);
assert_eq!(classes, vec![X86ArgClass::Memory]);
}
#[test]
fn test_sysv_classify_simple_struct() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 32 }))
.map(|(id, _)| *id)
.unwrap();
let i32_id_2 = i32_id;
let struct_ty = Type::struct_literal_with(false, vec![i32_id, i32_id_2]);
let struct_id = register_type(struct_ty, &mut store, &mut map);
let s = resolve_type(struct_id, &map);
let classes = classify_aggregate_type(&s, &map);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], X86ArgClass::Integer);
}
#[test]
fn test_sysv_classify_struct_with_float() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let f32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Float))
.map(|(id, _)| *id)
.unwrap();
let struct_ty = Type::struct_literal_with(false, vec![f32_id, f32_id]);
let struct_id = register_type(struct_ty, &mut store, &mut map);
let s = resolve_type(struct_id, &map);
let classes = classify_aggregate_type(&s, &map);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], X86ArgClass::SSE);
}
#[test]
fn test_sysv_classify_large_struct() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 64 }))
.map(|(id, _)| *id)
.unwrap();
let struct_ty = Type::struct_literal_with(false, vec![i64_id, i64_id, i64_id, i64_id]);
let struct_id = register_type(struct_ty, &mut store, &mut map);
let s = resolve_type(struct_id, &map);
let classes = classify_aggregate_type(&s, &map);
assert!(classes.iter().all(|c| *c == X86ArgClass::Memory));
}
#[test]
fn test_sysv_classify_mixed_struct_sse_int() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let f64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Double))
.map(|(id, _)| *id)
.unwrap();
let i64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 64 }))
.map(|(id, _)| *id)
.unwrap();
let struct_ty = Type::struct_literal_with(false, vec![f64_id, i64_id]);
let struct_id = register_type(struct_ty, &mut store, &mut map);
let s = resolve_type(struct_id, &map);
let classes = classify_aggregate_type(&s, &map);
assert!(classes.iter().any(|c| *c == X86ArgClass::Memory));
}
#[test]
fn test_sysv_assign_reg_args() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i64(), Type::float(), Type::double()];
let (infos, _frame) = X86CallingConvention::X86_64_SysV.assign_args(&args, &map);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![RDI]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![RSI]);
assert!(infos[2].in_reg);
assert_eq!(infos[2].regs, vec![XMM0]);
assert!(infos[3].in_reg);
assert_eq!(infos[3].regs, vec![XMM1]);
}
#[test]
fn test_sysv_assign_stack_args_when_regs_exhausted() {
let map = make_type_map();
let args = vec![
Type::i64(),
Type::i64(),
Type::i64(),
Type::i64(),
Type::i64(),
Type::i64(),
Type::i64(),
Type::i64(),
];
let (infos, _frame) = X86CallingConvention::X86_64_SysV.assign_args(&args, &map);
for i in 0..6 {
assert!(infos[i].in_reg, "arg {} should be in reg", i);
}
assert!(!infos[6].in_reg);
assert!(!infos[7].in_reg);
assert!(infos[6].stack_offset > 0);
assert!(infos[7].stack_offset > infos[6].stack_offset);
}
#[test]
fn test_sysv_return_regs_integer() {
let map = make_type_map();
let regs = X86CallingConvention::X86_64_SysV.get_return_regs(&Type::i64(), &map);
assert_eq!(regs, vec![RAX]);
let regs = X86CallingConvention::X86_64_SysV.get_return_regs(&Type::i128(), &map);
assert_eq!(regs, vec![RAX, RDX]);
}
#[test]
fn test_sysv_return_regs_sse() {
let map = make_type_map();
let regs = X86CallingConvention::X86_64_SysV.get_return_regs(&Type::float(), &map);
assert_eq!(regs, vec![XMM0]);
let regs = X86CallingConvention::X86_64_SysV.get_return_regs(&Type::double(), &map);
assert_eq!(regs, vec![XMM0]);
}
#[test]
fn test_sysv_return_regs_fp128() {
let map = make_type_map();
let regs = X86CallingConvention::X86_64_SysV.get_return_regs(&Type::fp128(), &map);
assert_eq!(regs, vec![XMM0, XMM1]);
}
#[test]
fn test_win64_assign_reg_args() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i64(), Type::float(), Type::double()];
let (infos, frame) = X86CallingConvention::Win64.assign_args(&args, &map);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![RCX]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![RDX]);
assert!(infos[2].in_reg);
assert_eq!(infos[2].regs, vec![XMM2]);
assert!(infos[3].in_reg);
assert_eq!(infos[3].regs, vec![XMM3]);
assert_eq!(frame.shadow_store_size, 32);
}
#[test]
fn test_win64_shadow_store() {
let map = make_type_map();
let args = vec![Type::i32()];
let (_infos, frame) = X86CallingConvention::Win64.assign_args(&args, &map);
assert_eq!(frame.shadow_store_size, 32);
assert!(frame.stack_size >= 32 + 8); }
#[test]
fn test_win64_return_regs() {
let map = make_type_map();
let regs = X86CallingConvention::Win64.get_return_regs(&Type::i64(), &map);
assert_eq!(regs, vec![RAX]);
let regs = X86CallingConvention::Win64.get_return_regs(&Type::float(), &map);
assert_eq!(regs, vec![XMM0]);
let regs = X86CallingConvention::Win64.get_return_regs(&Type::double(), &map);
assert_eq!(regs, vec![XMM0]);
}
#[test]
fn test_cdecl_all_stack() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i32(), Type::i32()];
let (infos, _frame) = X86CallingConvention::C.assign_args(&args, &map);
for info in &infos {
assert!(!info.in_reg);
assert!(info.stack_offset > 0);
}
}
#[test]
fn test_fastcall_reg_args() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i32(), Type::i32(), Type::i32()];
let (infos, _frame) = X86CallingConvention::Fast.assign_args(&args, &map);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![ECX]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![EDX]);
assert!(!infos[2].in_reg);
assert!(!infos[3].in_reg);
}
#[test]
fn test_thiscall_ecx_first() {
let map = make_type_map();
let args = vec![Type::pointer(0), Type::i32()];
let (infos, _frame) = X86CallingConvention::ThisCall.assign_args(&args, &map);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![ECX]);
assert!(!infos[1].in_reg);
}
#[test]
fn test_vectorcall_simd_regs() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let f32_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Float))
.map(|(id, _)| *id)
.unwrap();
let vec_ty = Type::fixed_vector_with(4, f32_id);
let vec_id = register_type(vec_ty, &mut store, &mut map);
let vec = resolve_type(vec_id, &map);
let args = vec![vec.clone(), vec.clone(), vec.clone()];
let (infos, _frame) = X86CallingConvention::VectorCall.assign_args(&args, &map);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![XMM0]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![XMM1]);
assert!(infos[2].in_reg);
assert_eq!(infos[2].regs, vec![XMM2]);
}
#[test]
fn test_32bit_return_regs() {
let map = make_type_map();
let regs = cdecl_return_regs(&Type::i32(), &map);
assert_eq!(regs, vec![EAX]);
let regs = cdecl_return_regs(&Type::i64(), &map);
assert_eq!(regs, vec![EDX, EAX]);
let regs = cdecl_return_regs(&Type::float(), &map);
assert_eq!(regs, vec![XMM0]);
let regs = cdecl_return_regs(&Type::x86_fp80(), &map);
assert_eq!(regs, vec![ST0]);
}
#[test]
fn test_arg_info_default() {
let info = X86ArgInfo::default();
assert!(!info.in_reg);
assert!(info.regs.is_empty());
assert_eq!(info.stack_offset, 0);
assert_eq!(info.size, 0);
assert_eq!(info.alignment, 1);
assert!(!info.is_byval);
assert!(!info.is_sret);
assert_eq!(info.padding, 0);
}
#[test]
fn test_call_frame_default() {
let frame = X86CallFrame::default();
assert_eq!(frame.stack_size, 0);
assert!(frame.arg_offsets.is_empty());
assert!(frame.return_save_area.is_none());
assert_eq!(frame.shadow_store_size, 0);
assert_eq!(frame.alignment_padding, 0);
}
#[test]
fn test_merge_classes_basic() {
use X86ArgClass::*;
assert_eq!(merge_classes(NoClass, Integer), Integer);
assert_eq!(merge_classes(Integer, NoClass), Integer);
assert_eq!(merge_classes(Integer, Integer), Integer);
assert_eq!(merge_classes(SSE, SSE), SSE);
assert_eq!(merge_classes(SSE, SSEUp), SSE);
assert_eq!(merge_classes(SSEUp, SSE), SSE);
assert_eq!(merge_classes(Integer, SSE), SSE);
assert_eq!(merge_classes(SSE, Integer), SSE);
assert_eq!(merge_classes(Integer, Memory), Memory);
assert_eq!(merge_classes(SSE, Memory), Memory);
assert_eq!(merge_classes(Memory, NoClass), Memory);
}
#[test]
fn test_post_merger_cleanup_two_eightbytes_valid_sse() {
let mut classes = vec![X86ArgClass::SSE, X86ArgClass::SSEUp];
post_merger_cleanup(&mut classes, 16);
assert_eq!(classes, vec![X86ArgClass::SSE, X86ArgClass::SSEUp]);
}
#[test]
fn test_post_merger_cleanup_two_eightbytes_mixed() {
let mut classes = vec![X86ArgClass::SSE, X86ArgClass::Integer];
post_merger_cleanup(&mut classes, 16);
assert!(classes.iter().all(|c| *c == X86ArgClass::Memory));
}
#[test]
fn test_post_merger_cleanup_more_than_two_not_all_sse() {
let mut classes = vec![
X86ArgClass::Integer,
X86ArgClass::Integer,
X86ArgClass::Integer,
];
post_merger_cleanup(&mut classes, 24);
assert!(classes.iter().all(|c| *c == X86ArgClass::Memory));
}
#[test]
fn test_post_merger_cleanup_empty() {
let mut classes: Vec<X86ArgClass> = vec![];
post_merger_cleanup(&mut classes, 0);
assert!(classes.is_empty());
}
#[test]
fn test_sysv_needs_hidden_sret() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 64 }))
.map(|(id, _)| *id)
.unwrap();
let small_struct = Type::struct_literal_with(false, vec![i64_id]);
let small_id = register_type(small_struct, &mut store, &mut map);
let small = resolve_type(small_id, &map);
assert!(!X86CallingConvention::X86_64_SysV.needs_hidden_sret(&small, &map));
let large_struct = Type::struct_literal_with(false, vec![i64_id, i64_id, i64_id]);
let large_id = register_type(large_struct, &mut store, &mut map);
let large = resolve_type(large_id, &map);
assert!(X86CallingConvention::X86_64_SysV.needs_hidden_sret(&large, &map));
}
#[test]
fn test_win64_needs_hidden_sret() {
let mut store = TypeStore::new();
let mut map = make_type_map();
let i64_id = map
.iter()
.find(|(_, ty)| matches!(ty.kind, TypeKind::Integer { bits: 64 }))
.map(|(id, _)| *id)
.unwrap();
let small_struct = Type::struct_literal_with(false, vec![i64_id]);
let small_id = register_type(small_struct, &mut store, &mut map);
let small = resolve_type(small_id, &map);
assert!(!X86CallingConvention::Win64.needs_hidden_sret(&small, &map));
let medium_struct = Type::struct_literal_with(false, vec![i64_id, i64_id]);
let medium_id = register_type(medium_struct, &mut store, &mut map);
let medium = resolve_type(medium_id, &map);
assert!(X86CallingConvention::Win64.needs_hidden_sret(&medium, &map));
}
#[test]
fn test_stdcall_assign_all_stack() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i32(), Type::i32()];
let (infos, _frame) = X86CallingConvention::StdCall.assign_args(&args, &map);
for info in &infos {
assert!(!info.in_reg);
assert!(info.stack_offset > 0);
}
}
#[test]
fn test_preserve_all_assign() {
let map = make_type_map();
let args = vec![Type::i32(), Type::i64()];
let (_infos, _frame) = X86CallingConvention::PreserveAll.assign_args(&args, &map);
}
}