| 1 | //! AAPCS64 argument classification and stack layout. |
| 2 | //! |
| 3 | //! Apple's AArch64 procedure-call standard, as implemented for armfortas's |
| 4 | //! lowering layer. Covers integer (general-purpose) registers `x0-x7`, |
| 5 | //! floating-point `s0-s7` / `d0-d7`, NEON vector `v0-v7`, the i128 register |
| 6 | //! pair lane (`x{N}, x{N+1}`), and stack-overflow assignment with proper |
| 7 | //! per-type alignment. |
| 8 | //! |
| 9 | //! Extracted from `isel.rs` so the ABI assumption lives in one place. Any |
| 10 | //! future ABI variant (e.g. Linux AArch64, with its different va_list shape) |
| 11 | //! goes here, not in instruction selection. |
| 12 | |
| 13 | use crate::ir::types::{FloatWidth, IntWidth, IrType}; |
| 14 | |
| 15 | /// Where a single argument lives after AAPCS64 classification. |
| 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 17 | pub enum AbiArgLoc { |
| 18 | Gp(u8), |
| 19 | Gp32(u8), |
| 20 | Fp(u8), |
| 21 | Fp32(u8), |
| 22 | GpPair(u8), |
| 23 | /// 128-bit NEON vector via `v0-v7`. Per AAPCS64, vectors share |
| 24 | /// the same physical bank as floats; the V form is the 128-bit |
| 25 | /// view of the same register. |
| 26 | V128(u8), |
| 27 | Stack(i64), |
| 28 | } |
| 29 | |
| 30 | /// Running tally of consumed argument slots, used while classifying a |
| 31 | /// signature one parameter at a time. |
| 32 | #[derive(Debug, Default, Clone, Copy)] |
| 33 | pub struct AbiArgState { |
| 34 | pub gp_idx: u8, |
| 35 | pub fp_idx: u8, |
| 36 | pub stack_offset: i64, |
| 37 | } |
| 38 | |
| 39 | /// Round `value` up to the next multiple of `align` (which must be a |
| 40 | /// power of two). |
| 41 | pub fn align_to(value: i64, align: i64) -> i64 { |
| 42 | debug_assert!(align > 0 && (align & (align - 1)) == 0); |
| 43 | (value + align - 1) & !(align - 1) |
| 44 | } |
| 45 | |
| 46 | /// `(size, align)` of a type when passed on the stack overflow area |
| 47 | /// per AAPCS64 §6.4. Mostly the natural ABI size; vectors are 16-byte |
| 48 | /// aligned, i128 takes a 16-byte slot. |
| 49 | pub fn abi_stack_layout(ty: &IrType) -> (i64, i64) { |
| 50 | match ty { |
| 51 | IrType::Int(IntWidth::I128) => (16, 16), |
| 52 | IrType::Int(IntWidth::I64) | IrType::Ptr(_) | IrType::FuncPtr(_) => (8, 8), |
| 53 | IrType::Float(FloatWidth::F64) => (8, 8), |
| 54 | // TODO: integer(c_short), value actuals still appear widened before |
| 55 | // reaching call lowering, so end-to-end 16-bit VALUE ABI parity needs |
| 56 | // a follow-up beyond this stack-packing fix. |
| 57 | IrType::Float(FloatWidth::F32) => (4, 4), |
| 58 | IrType::Int(IntWidth::I32) => (4, 4), |
| 59 | IrType::Int(IntWidth::I16) => (2, 2), |
| 60 | IrType::Int(IntWidth::I8) | IrType::Bool => (1, 1), |
| 61 | IrType::Vector { .. } => (16, 16), |
| 62 | _ => (8, 8), |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | /// Place one argument according to AAPCS64. Updates `state` in-place |
| 67 | /// to reflect the consumed slot. |
| 68 | pub fn classify_abi_arg(ty: &IrType, state: &mut AbiArgState) -> AbiArgLoc { |
| 69 | match ty { |
| 70 | IrType::Int(IntWidth::I128) => { |
| 71 | if state.gp_idx + 2 <= 8 { |
| 72 | let reg = state.gp_idx; |
| 73 | state.gp_idx += 2; |
| 74 | AbiArgLoc::GpPair(reg) |
| 75 | } else { |
| 76 | state.gp_idx = 8; |
| 77 | let (size, align) = abi_stack_layout(ty); |
| 78 | let offset = align_to(state.stack_offset, align); |
| 79 | state.stack_offset = offset + size; |
| 80 | AbiArgLoc::Stack(offset) |
| 81 | } |
| 82 | } |
| 83 | IrType::Float(FloatWidth::F64) => { |
| 84 | if state.fp_idx < 8 { |
| 85 | let reg = state.fp_idx; |
| 86 | state.fp_idx += 1; |
| 87 | AbiArgLoc::Fp(reg) |
| 88 | } else { |
| 89 | let (size, align) = abi_stack_layout(ty); |
| 90 | let offset = align_to(state.stack_offset, align); |
| 91 | state.stack_offset = offset + size; |
| 92 | AbiArgLoc::Stack(offset) |
| 93 | } |
| 94 | } |
| 95 | IrType::Float(FloatWidth::F32) => { |
| 96 | if state.fp_idx < 8 { |
| 97 | let reg = state.fp_idx; |
| 98 | state.fp_idx += 1; |
| 99 | AbiArgLoc::Fp32(reg) |
| 100 | } else { |
| 101 | let (size, align) = abi_stack_layout(ty); |
| 102 | let offset = align_to(state.stack_offset, align); |
| 103 | state.stack_offset = offset + size; |
| 104 | AbiArgLoc::Stack(offset) |
| 105 | } |
| 106 | } |
| 107 | IrType::Int(IntWidth::I8) |
| 108 | | IrType::Int(IntWidth::I16) |
| 109 | | IrType::Int(IntWidth::I32) |
| 110 | | IrType::Bool => { |
| 111 | if state.gp_idx < 8 { |
| 112 | let reg = state.gp_idx; |
| 113 | state.gp_idx += 1; |
| 114 | AbiArgLoc::Gp32(reg) |
| 115 | } else { |
| 116 | state.gp_idx = 8; |
| 117 | let (size, align) = abi_stack_layout(ty); |
| 118 | let offset = align_to(state.stack_offset, align); |
| 119 | state.stack_offset = offset + size; |
| 120 | AbiArgLoc::Stack(offset) |
| 121 | } |
| 122 | } |
| 123 | IrType::Vector { .. } => { |
| 124 | // AAPCS64 §6.4.2: vector args pass in v0-v7, sharing the |
| 125 | // same idx counter as float args (the V registers ARE the |
| 126 | // 128-bit form of the same physical regs). |
| 127 | if state.fp_idx < 8 { |
| 128 | let reg = state.fp_idx; |
| 129 | state.fp_idx += 1; |
| 130 | AbiArgLoc::V128(reg) |
| 131 | } else { |
| 132 | let (size, align) = abi_stack_layout(ty); |
| 133 | let offset = align_to(state.stack_offset, align); |
| 134 | state.stack_offset = offset + size; |
| 135 | AbiArgLoc::Stack(offset) |
| 136 | } |
| 137 | } |
| 138 | _ => { |
| 139 | if state.gp_idx < 8 { |
| 140 | let reg = state.gp_idx; |
| 141 | state.gp_idx += 1; |
| 142 | AbiArgLoc::Gp(reg) |
| 143 | } else { |
| 144 | state.gp_idx = 8; |
| 145 | let (size, align) = abi_stack_layout(ty); |
| 146 | let offset = align_to(state.stack_offset, align); |
| 147 | state.stack_offset = offset + size; |
| 148 | AbiArgLoc::Stack(offset) |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 |