use super::arm_register_info::*;
use crate::types::{Type, TypeId, TypeKind};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArmCallingConvention {
AAPCS64,
AAPCS,
#[allow(non_camel_case_types)]
AAPCS_VFP,
ATPCS,
AppleARM64,
WindowsARM64,
}
impl ArmCallingConvention {
pub fn name(&self) -> &'static str {
match self {
ArmCallingConvention::AAPCS64 => "AAPCS64",
ArmCallingConvention::AAPCS => "AAPCS",
ArmCallingConvention::AAPCS_VFP => "AAPCS-VFP",
ArmCallingConvention::ATPCS => "ATPCS",
ArmCallingConvention::AppleARM64 => "AppleARM64",
ArmCallingConvention::WindowsARM64 => "WindowsARM64",
}
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
ArmCallingConvention::AAPCS64
| ArmCallingConvention::AppleARM64
| ArmCallingConvention::WindowsARM64
)
}
pub fn uses_register_params(&self) -> bool {
true
}
pub fn get_num_int_param_regs(&self) -> usize {
match self {
ArmCallingConvention::AAPCS64 | ArmCallingConvention::AppleARM64 => 8,
ArmCallingConvention::WindowsARM64 => 4, ArmCallingConvention::AAPCS
| ArmCallingConvention::AAPCS_VFP
| ArmCallingConvention::ATPCS => 4,
}
}
pub fn get_num_simd_param_regs(&self) -> usize {
match self {
ArmCallingConvention::AAPCS64 => 8,
ArmCallingConvention::AAPCS_VFP => 16, _ => 0,
}
}
pub fn get_int_param_regs(&self) -> Vec<u16> {
match self {
ArmCallingConvention::AAPCS64
| ArmCallingConvention::AppleARM64
| ArmCallingConvention::WindowsARM64 => {
vec![X0, X1, X2, X3, X4, X5, X6, X7]
}
ArmCallingConvention::AAPCS
| ArmCallingConvention::AAPCS_VFP
| ArmCallingConvention::ATPCS => {
vec![R0, R1, R2, R3]
}
}
}
pub fn get_simd_param_regs(&self) -> Vec<u16> {
match self {
ArmCallingConvention::AAPCS64 => {
vec![V0, V1, V2, V3, V4, V5, V6, V7]
}
ArmCallingConvention::AAPCS_VFP => {
vec![
S0_ARM32, S1_ARM32, S2_ARM32, S3_ARM32, S4_ARM32, S5_ARM32, S6_ARM32, S7_ARM32,
S8_ARM32, S9_ARM32, S10_ARM32, S11_ARM32, S12_ARM32, S13_ARM32, S14_ARM32,
S15_ARM32,
]
}
_ => vec![],
}
}
pub fn get_stack_alignment(&self) -> u32 {
match self {
ArmCallingConvention::AAPCS64
| ArmCallingConvention::AppleARM64
| ArmCallingConvention::WindowsARM64 => 16,
ArmCallingConvention::AAPCS | ArmCallingConvention::AAPCS_VFP => 8,
ArmCallingConvention::ATPCS => 4,
}
}
pub fn has_red_zone(&self) -> bool {
false
}
pub fn red_zone_size(&self) -> i64 {
0
}
pub fn get_frame_pointer_reg(&self) -> u16 {
match self {
ArmCallingConvention::AAPCS64 => FP, _ => R11, }
}
pub fn get_link_register_reg(&self) -> u16 {
match self {
ArmCallingConvention::AAPCS64 => LR, _ => LR_ARM32, }
}
pub fn get_stack_pointer_reg(&self) -> u16 {
match self {
ArmCallingConvention::AAPCS64 => SP,
_ => SP_ARM32, }
}
pub fn needs_indirect_return(&self, ty: &Type, type_map: &HashMap<TypeId, Type>) -> bool {
if !matches!(
&ty.kind,
TypeKind::Struct { .. }
| TypeKind::Array { .. }
| TypeKind::FixedVector { .. }
| TypeKind::ScalableVector { .. }
) {
return false;
}
let size = type_size(ty, type_map);
match self {
ArmCallingConvention::AAPCS64 => size > 16,
_ => size > 4,
}
}
pub fn get_return_regs(&self, ty: &Type, type_map: &HashMap<TypeId, Type>) -> Vec<u16> {
let class = self.classify_arg(ty, type_map);
match self {
ArmCallingConvention::AAPCS64 => {
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
let size = type_size(ty, type_map);
if size <= 8 {
vec![X0]
} else {
vec![X0, X1]
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
vec![V0]
}
ArmArgClass::Composite => {
if self.needs_indirect_return(ty, type_map) {
vec![X8] } else {
let size = type_size(ty, type_map);
if size <= 8 {
vec![X0]
} else {
vec![X0, X1]
}
}
}
ArmArgClass::Memory => {
vec![X8] }
}
}
_ => {
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
let size = type_size(ty, type_map);
if size <= 4 {
vec![R0]
} else {
vec![R0, R1]
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
if matches!(self, ArmCallingConvention::AAPCS_VFP) {
vec![S0_ARM32]
} else {
vec![R0]
}
}
ArmArgClass::Composite | ArmArgClass::Memory => {
vec![R0] }
}
}
}
}
pub fn assign_args(
&self,
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
match self {
ArmCallingConvention::AAPCS64 => assign_aapcs64_args(args, type_map),
ArmCallingConvention::AppleARM64 => assign_apple_arm64_args(args, type_map),
ArmCallingConvention::WindowsARM64 => assign_windows_arm64_args(args, type_map),
ArmCallingConvention::AAPCS => assign_aapcs_args(args, type_map, false),
ArmCallingConvention::AAPCS_VFP => assign_aapcs_args(args, type_map, true),
ArmCallingConvention::ATPCS => assign_atpcs_args(args, type_map),
}
}
pub fn classify_arg(&self, ty: &Type, type_map: &HashMap<TypeId, Type>) -> ArmArgClass {
classify_arm_type(ty, type_map)
}
pub fn get_indirect_result_reg(&self) -> u16 {
match self {
ArmCallingConvention::AAPCS64 => X8,
_ => R0,
}
}
pub fn get_implicit_uses_at_call(&self) -> Vec<u16> {
match self {
ArmCallingConvention::AAPCS64 => vec![SP, FP, LR],
_ => vec![SP_ARM32, LR_ARM32], }
}
pub fn get_call_clobbered_regs(&self) -> Vec<u16> {
match self {
ArmCallingConvention::AAPCS64 => {
let mut clobbered = vec![
X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17,
X18,
];
for i in 0..=7 {
clobbered.push(V0 + i);
}
for i in 16..=31 {
clobbered.push(V0 + i);
}
clobbered.push(NZCV);
clobbered
}
_ => {
let mut clobbered = vec![R0, R1, R2, R3, R12];
clobbered.push(CPSR);
if matches!(self, ArmCallingConvention::AAPCS_VFP) {
for i in 0..=15 {
clobbered.push(S0_ARM32 + i);
}
}
clobbered
}
}
}
pub fn get_callee_saved_regs(&self) -> Vec<u16> {
match self {
ArmCallingConvention::AAPCS64 => {
let mut saved = Vec::new();
for i in 19..=28 {
saved.push(X0 + i);
}
saved.push(X29); saved.push(X30); for i in 8..=15 {
saved.push(V0 + i);
}
saved
}
_ => {
let mut saved = Vec::new();
for i in 4..=11 {
saved.push(R0 + i);
}
if matches!(self, ArmCallingConvention::AAPCS_VFP) {
for i in 8..=15 {
saved.push(D0_ARM32 + i);
}
}
saved
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArmArgClass {
Integer,
Float,
ShortVector,
Composite,
Pointer,
Memory,
}
impl ArmArgClass {
pub fn is_register_class(&self) -> bool {
matches!(
self,
ArmArgClass::Integer
| ArmArgClass::Float
| ArmArgClass::ShortVector
| ArmArgClass::Pointer
)
}
pub fn is_fp_class(&self) -> bool {
matches!(self, ArmArgClass::Float | ArmArgClass::ShortVector)
}
}
#[derive(Debug, Clone)]
pub struct ArmArgInfo {
pub in_reg: bool,
pub regs: Vec<u16>,
pub stack_offset: i64,
pub size: u32,
pub alignment: u32,
pub is_byval: bool,
pub is_indirect: bool,
}
impl Default for ArmArgInfo {
fn default() -> Self {
ArmArgInfo {
in_reg: false,
regs: Vec::new(),
stack_offset: 0,
size: 0,
alignment: 1,
is_byval: false,
is_indirect: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ArmCallFrame {
pub stack_size: i64,
pub arg_offsets: Vec<i64>,
pub saved_fp_offset: i64,
pub saved_lr_offset: i64,
pub callee_saved_size: i64,
pub alignment_padding: i64,
}
impl Default for ArmCallFrame {
fn default() -> Self {
ArmCallFrame {
stack_size: 0,
arg_offsets: Vec::new(),
saved_fp_offset: -16,
saved_lr_offset: -8,
callee_saved_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 | TypeKind::BFloat => 2,
TypeKind::Float => 4,
TypeKind::Double => 8,
TypeKind::FP128 => 16,
TypeKind::X86FP80 => 16,
TypeKind::PPCFP128 => 16,
TypeKind::Label | TypeKind::Metadata | TypeKind::Token => 0,
TypeKind::X86AMX => 0,
TypeKind::X86MMX => 8,
TypeKind::Integer { bits } => ((*bits + 7) / 8) as u64,
TypeKind::Pointer { .. } => {
8
}
TypeKind::Array {
len,
element_type_id,
} => {
let elem_size = resolve_type_size(*element_type_id, type_map);
(*len) * elem_size
}
TypeKind::Struct {
is_packed,
element_type_ids,
..
} => compute_struct_size(element_type_ids, *is_packed, type_map),
TypeKind::FixedVector {
len,
element_type_id,
} => {
let elem_size = resolve_type_size(*element_type_id, type_map);
(*len as u64) * elem_size
}
TypeKind::ScalableVector {
min_elems,
element_type_id,
} => {
let elem_size = resolve_type_size(*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 size = ((*bits + 7) / 8) as u64;
size.min(16)
}
TypeKind::Pointer { .. } => 8,
TypeKind::Array {
element_type_id, ..
} => resolve_type_alignment(*element_type_id, type_map),
TypeKind::Struct {
is_packed,
element_type_ids,
..
} => {
if *is_packed {
1
} else {
compute_struct_alignment(element_type_ids, type_map)
}
}
TypeKind::FixedVector {
len,
element_type_id,
} => {
let elem_align = resolve_type_alignment(*element_type_id, type_map);
let total_size = resolve_type_size(*element_type_id, type_map) * (*len as u64);
elem_align.max(total_size.next_power_of_two().min(16))
}
TypeKind::ScalableVector {
min_elems,
element_type_id,
} => {
let elem_align = resolve_type_alignment(*element_type_id, type_map);
elem_align.max(16)
}
TypeKind::Function { .. } => 8,
}
}
fn resolve_type(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> Option<&Type> {
type_map.get(&tid)
}
fn resolve_type_size(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> u64 {
resolve_type(tid, type_map)
.map(|t| type_size(t, type_map))
.unwrap_or(0)
}
fn resolve_type_alignment(tid: TypeId, type_map: &HashMap<TypeId, Type>) -> u64 {
resolve_type(tid, type_map)
.map(|t| type_alignment(t, type_map))
.unwrap_or(1)
}
fn compute_struct_size(
element_type_ids: &[TypeId],
is_packed: bool,
type_map: &HashMap<TypeId, Type>,
) -> u64 {
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_size = resolve_type_size(*tid, type_map);
let elem_align = resolve_type_alignment(*tid, type_map);
if elem_align > max_align {
max_align = elem_align;
}
if !is_packed {
total = align_to(total, elem_align);
}
total += elem_size;
}
if !is_packed && max_align > 0 {
total = align_to(total, max_align);
}
total
}
fn compute_struct_alignment(element_type_ids: &[TypeId], type_map: &HashMap<TypeId, Type>) -> u64 {
element_type_ids
.iter()
.map(|tid| resolve_type_alignment(*tid, type_map))
.max()
.unwrap_or(1)
}
fn align_to(value: u64, alignment: u64) -> u64 {
((value + alignment - 1) / alignment) * alignment
}
fn classify_arm_type(ty: &Type, _type_map: &HashMap<TypeId, Type>) -> ArmArgClass {
match &ty.kind {
TypeKind::Void => ArmArgClass::Memory,
TypeKind::Half | TypeKind::BFloat | TypeKind::Float | TypeKind::Double => {
ArmArgClass::Float
}
TypeKind::FP128 | TypeKind::PPCFP128 => ArmArgClass::Float,
TypeKind::Integer { .. } => ArmArgClass::Integer,
TypeKind::Pointer { .. } => ArmArgClass::Pointer,
TypeKind::Label | TypeKind::Metadata | TypeKind::Token => ArmArgClass::Memory,
TypeKind::X86MMX | TypeKind::X86AMX | TypeKind::X86FP80 => ArmArgClass::Memory,
TypeKind::Array { .. } | TypeKind::Struct { .. } => ArmArgClass::Composite,
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
let elem_size = 8u64; if (*len as u64) * elem_size <= 16 {
ArmArgClass::ShortVector
} else {
ArmArgClass::Composite
}
}
TypeKind::ScalableVector { .. } => ArmArgClass::Memory,
TypeKind::Function { .. } => ArmArgClass::Pointer,
}
}
fn assign_aapcs64_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
let mut arg_infos = Vec::with_capacity(args.len());
let mut int_regs_used: u8 = 0;
let mut simd_regs_used: u8 = 0;
let max_int_regs: u8 = 8; let max_simd_regs: u8 = 8; let mut stack_offset: i64 = 0;
let mut arg_offsets = Vec::with_capacity(args.len());
for arg_ty in args {
let class = classify_arm_type(arg_ty, type_map);
let size = type_size(arg_ty, type_map) as u32;
let alignment = type_alignment(arg_ty, type_map) as u32;
let mut info = ArmArgInfo {
size,
alignment,
..Default::default()
};
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
if int_regs_used < max_int_regs && size <= 8 {
info.in_reg = true;
info.regs.push(X0 + int_regs_used as u16);
int_regs_used += 1;
if size > 8 {
if int_regs_used < max_int_regs {
info.regs.push(X0 + int_regs_used as u16);
int_regs_used += 1;
} else {
info.regs.clear();
info.in_reg = false;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
} else {
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
if simd_regs_used < max_simd_regs {
info.in_reg = true;
let regs_needed = (size.max(1) as u8 + 15) / 16; for r in 0..regs_needed.min(max_simd_regs - simd_regs_used) {
info.regs.push(V0 + (simd_regs_used + r) as u16);
}
simd_regs_used += regs_needed.min(max_simd_regs - simd_regs_used);
if regs_needed
> max_simd_regs - simd_regs_used
+ regs_needed.min(max_simd_regs - simd_regs_used)
{
info.regs.clear();
info.in_reg = false;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
} else {
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
ArmArgClass::Composite => {
if size <= 16 {
let int_regs_needed = ((size as u64 + 7) / 8) as u8;
if int_regs_used + int_regs_needed <= max_int_regs {
info.in_reg = true;
for r in 0..int_regs_needed {
info.regs.push(X0 + (int_regs_used + r) as u16);
}
int_regs_used += int_regs_needed;
} else {
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
} else {
info.is_byval = true;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
ArmArgClass::Memory => {
info.is_byval = true;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
}
let stack_align = 16u64; let total_stack = align_to(stack_offset as u64, stack_align) as i64;
let call_frame = ArmCallFrame {
stack_size: total_stack,
arg_offsets,
saved_fp_offset: -(total_stack + 16),
saved_lr_offset: -(total_stack + 8),
callee_saved_size: 0,
alignment_padding: total_stack - stack_offset,
};
(arg_infos, call_frame)
}
fn assign_aapcs_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
use_vfp: bool,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
let mut arg_infos = Vec::with_capacity(args.len());
let mut int_regs_used: u8 = 0;
let mut vfp_regs_used: u8 = 0;
let max_int_regs: u8 = 4; let max_vfp_regs: u8 = 16; let mut stack_offset: i64 = 0;
let mut arg_offsets = Vec::with_capacity(args.len());
for arg_ty in args {
let class = classify_arm_type(arg_ty, type_map);
let size = type_size(arg_ty, type_map) as u32;
let alignment = type_alignment(arg_ty, type_map) as u32;
let mut info = ArmArgInfo {
size,
alignment,
..Default::default()
};
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
if int_regs_used < max_int_regs {
info.in_reg = true;
let regs_needed = ((size as u64 + 3) / 4) as u8;
for r in 0..regs_needed.min(max_int_regs - int_regs_used) {
info.regs.push(R0 + (int_regs_used + r) as u16);
}
int_regs_used += regs_needed.min(max_int_regs - int_regs_used);
if regs_needed
> max_int_regs - int_regs_used
+ regs_needed.min(max_int_regs - int_regs_used)
{
info.regs.clear();
info.in_reg = false;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
} else {
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
if use_vfp && vfp_regs_used < max_vfp_regs {
info.in_reg = true;
if size <= 4 {
info.regs.push(S0_ARM32 + vfp_regs_used as u16);
vfp_regs_used += 1;
} else if size <= 8 {
if vfp_regs_used % 2 != 0 {
vfp_regs_used += 1;
}
if vfp_regs_used < max_vfp_regs {
info.regs.push(D0_ARM32 + (vfp_regs_used / 2) as u16);
vfp_regs_used += 2;
} else {
info.regs.clear();
info.in_reg = false;
stack_offset = align_to(stack_offset as u64, 8) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
} else {
if !use_vfp && int_regs_used < max_int_regs && size <= 4 {
info.in_reg = true;
info.regs.push(R0 + int_regs_used as u16);
int_regs_used += 1;
} else {
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
}
ArmArgClass::Composite => {
if size <= 4 {
if int_regs_used < max_int_regs {
info.in_reg = true;
info.regs.push(R0 + int_regs_used as u16);
int_regs_used += 1;
} else {
stack_offset = align_to(stack_offset as u64, 4) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
} else {
info.is_byval = true;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
ArmArgClass::Memory => {
info.is_byval = true;
stack_offset = align_to(stack_offset as u64, alignment as u64) as i64;
info.stack_offset = stack_offset;
stack_offset += size as i64;
}
}
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
}
let stack_align = 8u64;
let total_stack = align_to(stack_offset as u64, stack_align) as i64;
let call_frame = ArmCallFrame {
stack_size: total_stack,
arg_offsets,
saved_fp_offset: -(total_stack + 4),
saved_lr_offset: -(total_stack + 0),
callee_saved_size: 0,
alignment_padding: total_stack - stack_offset,
};
(arg_infos, call_frame)
}
fn assign_atpcs_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
assign_aapcs_args(args, type_map, false)
}
fn align_up(value: i64, alignment: i64) -> i64 {
if alignment <= 0 {
return value;
}
((value + alignment - 1) / alignment) * alignment
}
fn assign_apple_arm64_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
let mut infos = Vec::new();
let mut gpr_idx = 0usize;
let mut fpr_idx = 0usize;
let mut stack_offset = 0i64;
let max_gpr = 8usize;
let max_fpr = 8usize;
for arg in args {
let class = classify_arm_type(arg, type_map);
let size = type_size(arg, type_map) as i64;
let align = type_alignment(arg, type_map) as i64;
let mut info = ArmArgInfo {
size: size as u32,
alignment: align as u32,
..ArmArgInfo::default()
};
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
if gpr_idx < max_gpr {
info.in_reg = true;
info.regs.push(X0 + gpr_idx as u16);
if size > 8 {
if gpr_idx + 1 < max_gpr {
info.regs.push(X0 + (gpr_idx + 1) as u16);
gpr_idx += 1;
} else {
info.in_reg = false;
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
gpr_idx += 1;
} else {
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
if fpr_idx < max_fpr {
info.in_reg = true;
info.regs.push(V0 + fpr_idx as u16);
fpr_idx += 1;
} else if gpr_idx < max_gpr {
info.in_reg = true;
info.regs.push(X0 + gpr_idx as u16);
gpr_idx += 1;
} else {
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
_ => {
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
infos.push(info);
}
let frame = ArmCallFrame {
stack_size: align_up(stack_offset, 16),
arg_offsets: infos.iter().map(|i| i.stack_offset).collect(),
saved_fp_offset: -16,
saved_lr_offset: -8,
callee_saved_size: 0,
alignment_padding: 0,
};
(infos, frame)
}
fn assign_windows_arm64_args(
args: &[Type],
type_map: &HashMap<TypeId, Type>,
) -> (Vec<ArmArgInfo>, ArmCallFrame) {
let mut infos = Vec::new();
let mut gpr_idx = 0usize;
let mut fpr_idx = 0usize;
let max_gpr = 4usize; let max_fpr = 8usize;
let shadow_space = 32i64;
let mut stack_offset = shadow_space;
for arg in args {
let class = classify_arm_type(arg, type_map);
let size = type_size(arg, type_map) as i64;
let align = type_alignment(arg, type_map) as i64;
let mut info = ArmArgInfo {
size: size as u32,
alignment: align as u32,
..ArmArgInfo::default()
};
match class {
ArmArgClass::Integer | ArmArgClass::Pointer => {
if gpr_idx < max_gpr {
info.in_reg = true;
info.regs.push(X0 + gpr_idx as u16);
gpr_idx += 1;
} else {
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
ArmArgClass::Float | ArmArgClass::ShortVector => {
if fpr_idx < max_fpr {
info.in_reg = true;
info.regs.push(V0 + fpr_idx as u16);
gpr_idx += 1; fpr_idx += 1;
} else {
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
_ => {
info.is_byval = true;
stack_offset = align_up(stack_offset, align);
info.stack_offset = stack_offset;
stack_offset += size;
}
}
infos.push(info);
}
let frame = ArmCallFrame {
stack_size: align_up(stack_offset.max(shadow_space), 16),
arg_offsets: infos.iter().map(|i| i.stack_offset).collect(),
saved_fp_offset: -16,
saved_lr_offset: -8,
callee_saved_size: 0,
alignment_padding: 0,
};
(infos, frame)
}
pub const VARARGS_GPR_SAVE_SIZE: usize = 8;
pub const VARARGS_FPR_SAVE_SIZE: usize = 8;
pub fn varargs_save_area_size(is_64bit: bool) -> i64 {
if is_64bit {
192
} else {
16
}
}
pub fn varargs_gpr_offset(gpr_idx: usize, is_64bit: bool) -> i64 {
if is_64bit {
gpr_idx as i64 * 8
} else {
gpr_idx as i64 * 4
}
}
pub fn varargs_fpr_offset(fpr_idx: usize) -> i64 {
(VARARGS_GPR_SAVE_SIZE * 8) as i64 + fpr_idx as i64 * 16
}
pub const P0_SVE: u16 = 2150;
pub const P1_SVE: u16 = 2151;
pub const P2_SVE: u16 = 2152;
pub const P3_SVE: u16 = 2153;
pub const Z0_SVE: u16 = 2160;
pub const Z1_SVE: u16 = 2161;
pub const Z2_SVE: u16 = 2162;
pub const Z3_SVE: u16 = 2163;
pub const Z4_SVE: u16 = 2164;
pub const Z5_SVE: u16 = 2165;
pub const Z6_SVE: u16 = 2166;
pub const Z7_SVE: u16 = 2167;
pub const Z8_SVE: u16 = 2168;
pub const Z9_SVE: u16 = 2169;
pub const Z10_SVE: u16 = 2170;
pub const Z11_SVE: u16 = 2171;
pub const Z12_SVE: u16 = 2172;
pub const Z13_SVE: u16 = 2173;
pub const Z14_SVE: u16 = 2174;
pub const Z15_SVE: u16 = 2175;
pub const Z16_SVE: u16 = 2176;
pub const Z17_SVE: u16 = 2177;
pub const Z18_SVE: u16 = 2178;
pub const Z19_SVE: u16 = 2179;
pub const Z20_SVE: u16 = 2180;
pub const Z21_SVE: u16 = 2181;
pub const Z22_SVE: u16 = 2182;
pub const Z23_SVE: u16 = 2183;
pub const P4_SVE: u16 = 2154;
pub const P5_SVE: u16 = 2155;
pub const P6_SVE: u16 = 2156;
pub const P7_SVE: u16 = 2157;
pub const P8_SVE: u16 = 2158;
pub const P9_SVE: u16 = 2159;
pub const P10_SVE: u16 = 2190;
pub const P11_SVE: u16 = 2191;
pub const P12_SVE: u16 = 2192;
pub const P13_SVE: u16 = 2193;
pub const P14_SVE: u16 = 2194;
pub const P15_SVE: u16 = 2195;
pub const SVE_ARG_PREDICATE_REGS: &[u16] = &[P0_SVE, P1_SVE, P2_SVE, P3_SVE];
pub const SVE_ARG_VECTOR_REGS: &[u16] = &[
Z0_SVE, Z1_SVE, Z2_SVE, Z3_SVE, Z4_SVE, Z5_SVE, Z6_SVE, Z7_SVE,
];
pub const SVE_CALLEE_SAVED_Z_REGS: &[u16] = &[
Z8_SVE, Z9_SVE, Z10_SVE, Z11_SVE, Z12_SVE, Z13_SVE, Z14_SVE, Z15_SVE, Z16_SVE, Z17_SVE,
Z18_SVE, Z19_SVE, Z20_SVE, Z21_SVE, Z22_SVE, Z23_SVE,
];
pub const SVE_CALLEE_SAVED_P_REGS: &[u16] = &[
P4_SVE, P5_SVE, P6_SVE, P7_SVE, P8_SVE, P9_SVE, P10_SVE, P11_SVE, P12_SVE, P13_SVE, P14_SVE,
P15_SVE,
];
pub fn get_sve_arg_regs() -> Vec<u16> {
SVE_ARG_VECTOR_REGS.to_vec()
}
pub fn get_sve_callee_saved_count() -> usize {
SVE_CALLEE_SAVED_Z_REGS.len() + SVE_CALLEE_SAVED_P_REGS.len()
}
pub fn is_hfa(ty: &Type, type_map: &HashMap<TypeId, Type>) -> Option<usize> {
match &ty.kind {
TypeKind::Struct {
element_type_ids, ..
} => {
if element_type_ids.is_empty() || element_type_ids.len() > 4 {
return None;
}
let first = resolve_type(element_type_ids[0], type_map);
let base_class = first.map(|t| classify_arm_type(t, type_map));
if !matches!(base_class, Some(ArmArgClass::Float)) {
return None;
}
let first_kind = first.map(|t| &t.kind);
for tid in &element_type_ids[1..] {
let elem = resolve_type(*tid, type_map);
if elem.map(|t| &t.kind) != first_kind {
return None;
}
}
Some(element_type_ids.len())
}
TypeKind::Array {
len,
element_type_id,
} => {
if *len > 4 {
return None;
}
let elem = resolve_type(*element_type_id, type_map);
if let Some(e) = elem {
if matches!(classify_arm_type(e, type_map), ArmArgClass::Float) {
return Some(*len as usize);
}
}
None
}
_ => None,
}
}
pub fn is_hva(ty: &Type, type_map: &HashMap<TypeId, Type>) -> Option<usize> {
match &ty.kind {
TypeKind::Struct {
element_type_ids, ..
} => {
if element_type_ids.is_empty() || element_type_ids.len() > 4 {
return None;
}
let first = resolve_type(element_type_ids[0], type_map);
let base_class = first.map(|t| classify_arm_type(t, type_map));
if !matches!(base_class, Some(ArmArgClass::ShortVector)) {
return None;
}
for tid in &element_type_ids[1..] {
let elem = resolve_type(*tid, type_map);
if elem.map(|t| classify_arm_type(t, type_map)) != base_class {
return None;
}
}
Some(element_type_ids.len())
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_type(kind: TypeKind) -> Type {
Type {
id: TypeId::new(),
kind,
}
}
fn empty_type_map() -> HashMap<TypeId, Type> {
HashMap::new()
}
#[test]
fn test_convention_names() {
assert_eq!(ArmCallingConvention::AAPCS64.name(), "AAPCS64");
assert_eq!(ArmCallingConvention::AAPCS.name(), "AAPCS");
assert_eq!(ArmCallingConvention::AAPCS_VFP.name(), "AAPCS-VFP");
assert_eq!(ArmCallingConvention::ATPCS.name(), "ATPCS");
}
#[test]
fn test_is_64bit() {
assert!(ArmCallingConvention::AAPCS64.is_64bit());
assert!(!ArmCallingConvention::AAPCS.is_64bit());
assert!(!ArmCallingConvention::AAPCS_VFP.is_64bit());
assert!(!ArmCallingConvention::ATPCS.is_64bit());
}
#[test]
fn test_uses_register_params() {
assert!(ArmCallingConvention::AAPCS64.uses_register_params());
assert!(ArmCallingConvention::AAPCS.uses_register_params());
}
#[test]
fn test_num_int_param_regs() {
assert_eq!(ArmCallingConvention::AAPCS64.get_num_int_param_regs(), 8);
assert_eq!(ArmCallingConvention::AAPCS.get_num_int_param_regs(), 4);
assert_eq!(ArmCallingConvention::AAPCS_VFP.get_num_int_param_regs(), 4);
assert_eq!(ArmCallingConvention::ATPCS.get_num_int_param_regs(), 4);
}
#[test]
fn test_num_simd_param_regs() {
assert_eq!(ArmCallingConvention::AAPCS64.get_num_simd_param_regs(), 8);
assert_eq!(
ArmCallingConvention::AAPCS_VFP.get_num_simd_param_regs(),
16
);
assert_eq!(ArmCallingConvention::AAPCS.get_num_simd_param_regs(), 0);
}
#[test]
fn test_int_param_regs_aapcs64() {
let regs = ArmCallingConvention::AAPCS64.get_int_param_regs();
assert_eq!(regs.len(), 8);
assert_eq!(regs[0], X0);
assert_eq!(regs[7], X7);
}
#[test]
fn test_int_param_regs_aapcs() {
let regs = ArmCallingConvention::AAPCS.get_int_param_regs();
assert_eq!(regs.len(), 4);
assert_eq!(regs[0], R0);
assert_eq!(regs[3], R3);
}
#[test]
fn test_simd_param_regs_aapcs64() {
let regs = ArmCallingConvention::AAPCS64.get_simd_param_regs();
assert_eq!(regs.len(), 8);
assert_eq!(regs[0], V0);
assert_eq!(regs[7], V7);
}
#[test]
fn test_stack_alignment() {
assert_eq!(ArmCallingConvention::AAPCS64.get_stack_alignment(), 16);
assert_eq!(ArmCallingConvention::AAPCS.get_stack_alignment(), 8);
assert_eq!(ArmCallingConvention::ATPCS.get_stack_alignment(), 4);
}
#[test]
fn test_no_red_zone() {
assert!(!ArmCallingConvention::AAPCS64.has_red_zone());
assert_eq!(ArmCallingConvention::AAPCS64.red_zone_size(), 0);
}
#[test]
fn test_register_aliases() {
assert_eq!(ArmCallingConvention::AAPCS64.get_frame_pointer_reg(), FP);
assert_eq!(ArmCallingConvention::AAPCS64.get_link_register_reg(), LR);
assert_eq!(ArmCallingConvention::AAPCS64.get_stack_pointer_reg(), SP);
assert_eq!(ArmCallingConvention::AAPCS.get_frame_pointer_reg(), R11);
assert_eq!(
ArmCallingConvention::AAPCS.get_link_register_reg(),
LR_ARM32
);
assert_eq!(
ArmCallingConvention::AAPCS.get_stack_pointer_reg(),
SP_ARM32
);
}
#[test]
fn test_type_size_primitives() {
let m = empty_type_map();
assert_eq!(type_size(&make_type(TypeKind::Void), &m), 0);
assert_eq!(type_size(&make_type(TypeKind::Half), &m), 2);
assert_eq!(type_size(&make_type(TypeKind::Float), &m), 4);
assert_eq!(type_size(&make_type(TypeKind::Double), &m), 8);
assert_eq!(type_size(&make_type(TypeKind::Integer { bits: 32 }), &m), 4);
assert_eq!(type_size(&make_type(TypeKind::Integer { bits: 64 }), &m), 8);
assert_eq!(type_size(&make_type(TypeKind::Integer { bits: 1 }), &m), 1);
}
#[test]
fn test_type_alignment_primitives() {
let m = empty_type_map();
assert_eq!(
type_alignment(&make_type(TypeKind::Integer { bits: 32 }), &m),
4
);
assert_eq!(
type_alignment(&make_type(TypeKind::Integer { bits: 64 }), &m),
8
);
assert_eq!(type_alignment(&make_type(TypeKind::Double), &m), 8);
}
#[test]
fn test_classify_integer() {
let m = empty_type_map();
let ty = make_type(TypeKind::Integer { bits: 32 });
assert_eq!(
ArmCallingConvention::AAPCS64.classify_arg(&ty, &m),
ArmArgClass::Integer
);
}
#[test]
fn test_classify_float() {
let m = empty_type_map();
let ty = make_type(TypeKind::Float);
assert_eq!(
ArmCallingConvention::AAPCS64.classify_arg(&ty, &m),
ArmArgClass::Float
);
let ty_d = make_type(TypeKind::Double);
assert_eq!(
ArmCallingConvention::AAPCS64.classify_arg(&ty_d, &m),
ArmArgClass::Float
);
}
#[test]
fn test_classify_pointer() {
let m = empty_type_map();
let ty = make_type(TypeKind::Pointer { addr_space: 0 });
assert_eq!(
ArmCallingConvention::AAPCS64.classify_arg(&ty, &m),
ArmArgClass::Pointer
);
}
#[test]
fn test_return_regs_integer_aapcs64() {
let m = empty_type_map();
let ty = make_type(TypeKind::Integer { bits: 64 });
let regs = ArmCallingConvention::AAPCS64.get_return_regs(&ty, &m);
assert_eq!(regs.len(), 1);
assert_eq!(regs[0], X0);
}
#[test]
fn test_return_regs_float_aapcs64() {
let m = empty_type_map();
let ty = make_type(TypeKind::Float);
let regs = ArmCallingConvention::AAPCS64.get_return_regs(&ty, &m);
assert_eq!(regs.len(), 1);
assert_eq!(regs[0], V0);
}
#[test]
fn test_return_regs_integer_arm32() {
let m = empty_type_map();
let ty = make_type(TypeKind::Integer { bits: 32 });
let regs = ArmCallingConvention::AAPCS.get_return_regs(&ty, &m);
assert_eq!(regs.len(), 1);
assert_eq!(regs[0], R0);
}
#[test]
fn test_assign_args_aapcs64_simple() {
let m = empty_type_map();
let args = vec![
make_type(TypeKind::Integer { bits: 64 }),
make_type(TypeKind::Integer { bits: 32 }),
];
let (infos, _frame) = ArmCallingConvention::AAPCS64.assign_args(&args, &m);
assert_eq!(infos.len(), 2);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs[0], X0);
}
#[test]
fn test_assign_args_aapcs64_many() {
let m = empty_type_map();
let args: Vec<Type> = (0..12)
.map(|_| make_type(TypeKind::Integer { bits: 64 }))
.collect();
let (infos, frame) = ArmCallingConvention::AAPCS64.assign_args(&args, &m);
assert_eq!(infos.len(), 12);
assert!(infos[0].in_reg);
assert!(!infos[8].in_reg); assert!(frame.stack_size > 0);
}
#[test]
fn test_assign_args_aapcs_many() {
let m = empty_type_map();
let args: Vec<Type> = (0..8)
.map(|_| make_type(TypeKind::Integer { bits: 32 }))
.collect();
let (infos, _frame) = ArmCallingConvention::AAPCS.assign_args(&args, &m);
assert_eq!(infos.len(), 8);
assert!(infos[0].in_reg);
assert!(!infos[4].in_reg);
}
#[test]
fn test_call_clobbered_aapcs64() {
let regs = ArmCallingConvention::AAPCS64.get_call_clobbered_regs();
assert!(regs.contains(&X0));
assert!(regs.contains(&X15));
assert!(regs.contains(&V0));
assert!(regs.contains(&NZCV));
assert!(!regs.contains(&X19)); }
#[test]
fn test_call_clobbered_aapcs() {
let regs = ArmCallingConvention::AAPCS.get_call_clobbered_regs();
assert!(regs.contains(&R0));
assert!(regs.contains(&R3));
assert!(regs.contains(&R12));
assert!(regs.contains(&CPSR));
assert!(!regs.contains(&R4)); }
#[test]
fn test_callee_saved_aapcs64() {
let regs = ArmCallingConvention::AAPCS64.get_callee_saved_regs();
assert!(regs.contains(&X19));
assert!(regs.contains(&X28));
assert!(regs.contains(&X29)); assert!(regs.contains(&X30)); assert!(regs.contains(&V8));
}
#[test]
fn test_callee_saved_aapcs() {
let regs = ArmCallingConvention::AAPCS.get_callee_saved_regs();
assert!(regs.contains(&R4));
assert!(regs.contains(&R11));
}
#[test]
fn test_indirect_result_reg() {
assert_eq!(ArmCallingConvention::AAPCS64.get_indirect_result_reg(), X8);
assert_eq!(ArmCallingConvention::AAPCS.get_indirect_result_reg(), R0);
}
#[test]
fn test_implicit_uses_at_call() {
let regs = ArmCallingConvention::AAPCS64.get_implicit_uses_at_call();
assert!(regs.contains(&SP));
assert!(regs.contains(&FP));
assert!(regs.contains(&LR));
}
#[test]
fn test_is_hfa() {
let m = empty_type_map();
let ty = make_type(TypeKind::Float);
assert_eq!(is_hfa(&ty, &m), None);
}
#[test]
fn test_apple_arm64_name() {
assert_eq!(ArmCallingConvention::AppleARM64.name(), "AppleARM64");
}
#[test]
fn test_apple_arm64_is_64bit() {
assert!(ArmCallingConvention::AppleARM64.is_64bit());
}
#[test]
fn test_apple_arm64_num_int_param_regs() {
assert_eq!(ArmCallingConvention::AppleARM64.get_num_int_param_regs(), 8);
}
#[test]
fn test_apple_arm64_assign_args() {
let m = empty_type_map();
let args = vec![
make_type(TypeKind::Integer { bits: 64 }),
make_type(TypeKind::Float),
];
let (infos, frame) = ArmCallingConvention::AppleARM64.assign_args(&args, &m);
assert_eq!(infos.len(), 2);
assert!(infos[0].in_reg);
assert!(infos[1].in_reg);
assert_eq!(frame.stack_size % 16, 0); }
#[test]
fn test_apple_arm64_many_args_spill_to_stack() {
let m = empty_type_map();
let args: Vec<Type> = (0..12)
.map(|_| make_type(TypeKind::Integer { bits: 64 }))
.collect();
let (infos, frame) = ArmCallingConvention::AppleARM64.assign_args(&args, &m);
assert_eq!(infos.len(), 12);
assert!(infos[0].in_reg);
assert!(infos[8].stack_offset > 0);
assert_ne!(frame.stack_size, 0);
}
#[test]
fn test_windows_arm64_name() {
assert_eq!(ArmCallingConvention::WindowsARM64.name(), "WindowsARM64");
}
#[test]
fn test_windows_arm64_is_64bit() {
assert!(ArmCallingConvention::WindowsARM64.is_64bit());
}
#[test]
fn test_windows_arm64_only_4_gprs() {
assert_eq!(
ArmCallingConvention::WindowsARM64.get_num_int_param_regs(),
4
);
}
#[test]
fn test_windows_arm64_shadow_space() {
let m = empty_type_map();
let args = vec![make_type(TypeKind::Integer { bits: 64 })];
let (infos, frame) = ArmCallingConvention::WindowsARM64.assign_args(&args, &m);
assert_eq!(infos.len(), 1);
assert!(infos[0].in_reg);
assert!(frame.stack_size >= 32);
assert_eq!(frame.stack_size % 16, 0);
}
#[test]
fn test_windows_arm64_spill_after_4_gprs() {
let m = empty_type_map();
let args: Vec<Type> = (0..6)
.map(|_| make_type(TypeKind::Integer { bits: 64 }))
.collect();
let (infos, _frame) = ArmCallingConvention::WindowsARM64.assign_args(&args, &m);
assert!(infos[0].in_reg);
assert!(infos[3].in_reg); assert!(!infos[4].in_reg); }
#[test]
fn test_varargs_save_area_size_aarch64() {
assert_eq!(varargs_save_area_size(true), 192); }
#[test]
fn test_varargs_save_area_size_arm32() {
assert_eq!(varargs_save_area_size(false), 16); }
#[test]
fn test_varargs_gpr_offset_aarch64() {
assert_eq!(varargs_gpr_offset(0, true), 0);
assert_eq!(varargs_gpr_offset(1, true), 8);
assert_eq!(varargs_gpr_offset(7, true), 56);
}
#[test]
fn test_varargs_gpr_offset_arm32() {
assert_eq!(varargs_gpr_offset(0, false), 0);
assert_eq!(varargs_gpr_offset(1, false), 4);
assert_eq!(varargs_gpr_offset(3, false), 12);
}
#[test]
fn test_varargs_fpr_offset() {
assert_eq!(varargs_fpr_offset(0), 64);
assert_eq!(varargs_fpr_offset(1), 80);
assert_eq!(varargs_fpr_offset(7), 176);
}
#[test]
fn test_sve_arg_regs_count() {
let regs = get_sve_arg_regs();
assert_eq!(regs.len(), 8); }
#[test]
fn test_sve_callee_saved_count() {
assert_eq!(get_sve_callee_saved_count(), 28);
}
#[test]
fn test_sve_arg_predicate_regs() {
assert_eq!(SVE_ARG_PREDICATE_REGS.len(), 4);
}
#[test]
fn test_sve_arg_vector_regs() {
assert_eq!(SVE_ARG_VECTOR_REGS.len(), 8);
}
#[test]
fn test_sve_callee_saved_z_regs() {
assert_eq!(SVE_CALLEE_SAVED_Z_REGS.len(), 16);
}
#[test]
fn test_sve_callee_saved_p_regs() {
assert_eq!(SVE_CALLEE_SAVED_P_REGS.len(), 12);
}
#[test]
fn test_indirect_result_reg_apple_arm64() {
assert_eq!(
ArmCallingConvention::AppleARM64.get_indirect_result_reg(),
X8
);
}
#[test]
fn test_indirect_result_reg_windows_arm64() {
assert_eq!(
ArmCallingConvention::WindowsARM64.get_indirect_result_reg(),
X8
);
}
#[test]
fn test_implicit_uses_apple_arm64() {
let regs = ArmCallingConvention::AppleARM64.get_implicit_uses_at_call();
assert!(regs.contains(&SP));
assert!(regs.contains(&FP));
assert!(regs.contains(&LR));
}
#[test]
fn test_needs_indirect_return_apple_arm64() {
let m = empty_type_map();
let large_struct = Type::struct_type(&[], false); let ty = make_type(TypeKind::Integer { bits: 128 });
assert!(!ArmCallingConvention::AppleARM64.needs_indirect_return(&ty, &m));
}
#[test]
fn test_atpcs_name() {
assert_eq!(ArmCallingConvention::ATPCS.name(), "ATPCS");
}
#[test]
fn test_atpcs_not_64bit() {
assert!(!ArmCallingConvention::ATPCS.is_64bit());
}
#[test]
fn test_atpcs_uses_register_params() {
assert!(ArmCallingConvention::ATPCS.uses_register_params());
}
#[test]
fn test_atpcs_param_regs() {
assert_eq!(ArmCallingConvention::ATPCS.get_num_int_param_regs(), 4);
}
#[test]
fn test_all_convention_names() {
let conventions = [
ArmCallingConvention::AAPCS64,
ArmCallingConvention::AAPCS,
ArmCallingConvention::AAPCS_VFP,
ArmCallingConvention::ATPCS,
ArmCallingConvention::AppleARM64,
ArmCallingConvention::WindowsARM64,
];
for conv in &conventions {
let name = conv.name();
assert!(!name.is_empty());
}
}
#[test]
fn test_get_num_int_param_regs_all() {
assert_eq!(ArmCallingConvention::AAPCS64.get_num_int_param_regs(), 8);
assert_eq!(ArmCallingConvention::AppleARM64.get_num_int_param_regs(), 8);
assert_eq!(
ArmCallingConvention::WindowsARM64.get_num_int_param_regs(),
4
);
assert_eq!(ArmCallingConvention::AAPCS.get_num_int_param_regs(), 4);
assert_eq!(ArmCallingConvention::AAPCS_VFP.get_num_int_param_regs(), 4);
assert_eq!(ArmCallingConvention::ATPCS.get_num_int_param_regs(), 4);
}
#[test]
fn test_arm_arg_class_is_register() {
assert!(ArmArgClass::Integer.is_register_class());
assert!(ArmArgClass::Float.is_register_class());
assert!(ArmArgClass::ShortVector.is_register_class());
assert!(ArmArgClass::Pointer.is_register_class());
assert!(!ArmArgClass::Composite.is_register_class());
assert!(!ArmArgClass::Memory.is_register_class());
}
#[test]
fn test_arm_arg_class_is_fp() {
assert!(ArmArgClass::Float.is_fp_class());
assert!(ArmArgClass::ShortVector.is_fp_class());
assert!(!ArmArgClass::Integer.is_fp_class());
assert!(!ArmArgClass::Pointer.is_fp_class());
}
#[test]
fn test_arm_arg_info_default() {
let info = ArmArgInfo::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_indirect);
}
}