Skip to main content

rucc_codegen/abi/
aarch64.rs

1//! The instructions the calling convention is written with on AArch64.
2//!
3//! The same four questions [`super::head_of`] and its neighbours answer for x86-64, answered with
4//! the names `rucc_target::aarch64` describes. Two things make the answers shorter here. A value
5//! narrower than 32 bits lives in a 32 bit register, so a byte and a short arrive and go back as
6//! the 32 bit pseudo, the same as an `int`. And there is no second name for a half: `_Float16`
7//! has no rule on this machine yet, so it has no name here either, and a function that passes one
8//! is refused at the argument rather than halfway through.
9
10use rucc_ir::Type;
11
12use super::{Insts, place};
13
14/// The AArch64 ones.
15pub static INSTS: Insts = Insts {
16    arg: head_of,
17    load: load_of,
18    store: store_of,
19    ret: ret_of,
20    call: "a64.bl",
21    call_reg: "a64.blr",
22    lea: "a64.lea_64",
23    small: "a64.mov_ri_32",
24};
25
26/// What the pseudo for an argument of that type is called.
27fn head_of(ty: Type) -> Option<&'static str> {
28    if crate::term::is_quad(ty) {
29        return Some("a64.arg_val_f128");
30    }
31    if crate::term::is_half(ty) {
32        return None;
33    }
34    if let Some(at) = crate::term::float_slot(ty) {
35        return Some(["a64.arg_val_f32", "a64.arg_val_f64"][at]);
36    }
37    let names = ["a64.arg_val_32", "a64.arg_val_32", "a64.arg_val_32", "a64.arg_val_64"];
38    Some(names[place(ty)?])
39}
40
41/// What the instruction that reads an argument of that type out of memory is called.
42///
43/// A narrow one is read at its own width, for the reason [`super::load_of`] gives: the caller
44/// wrote a whole slot and the convention says nothing about what is above the value in it.
45fn load_of(ty: Type) -> Option<&'static str> {
46    if crate::term::is_quad(ty) {
47        return Some("a64.ldr_f128");
48    }
49    if crate::term::is_half(ty) {
50        return None;
51    }
52    if let Some(at) = crate::term::float_slot(ty) {
53        return Some(["a64.ldr_f32", "a64.ldr_f64"][at]);
54    }
55    let names = ["a64.ldr_8", "a64.ldr_16", "a64.ldr_32", "a64.ldr_64"];
56    Some(names[place(ty)?])
57}
58
59/// What the instruction that writes an argument of that type into memory is called.
60fn store_of(ty: Type) -> Option<&'static str> {
61    if crate::term::is_quad(ty) {
62        return Some("a64.str_f128");
63    }
64    if crate::term::is_half(ty) {
65        return None;
66    }
67    if let Some(at) = crate::term::float_slot(ty) {
68        return Some(["a64.str_f32", "a64.str_f64"][at]);
69    }
70    let names = ["a64.str_8", "a64.str_16", "a64.str_32", "a64.str_64"];
71    Some(names[place(ty)?])
72}
73
74/// What the pseudo that leaves a returned value in its register is called, for the value at that
75/// place in its own register file.
76///
77/// Two places in each file, the same as x86-64. AAPCS64 gives back up to eight registers of either
78/// kind, but the front end splits nothing into more than two pieces yet, so a third name would be
79/// a name nothing asks for.
80fn ret_of(ty: Type, at: usize) -> Option<&'static str> {
81    if crate::term::is_quad(ty) {
82        let names =
83            ["a64.ret_val_f128", "a64.ret_val2_f128", "a64.ret_val3_f128", "a64.ret_val4_f128"];
84        return Some(*names.get(at)?);
85    }
86    if crate::term::is_half(ty) {
87        return None;
88    }
89    if let Some(width) = crate::term::float_slot(ty) {
90        // Four, since a homogeneous aggregate of up to four floating point members comes back
91        // one member to a register.
92        let names = [
93            ["a64.ret_val_f32", "a64.ret_val_f64"],
94            ["a64.ret_val2_f32", "a64.ret_val2_f64"],
95            ["a64.ret_val3_f32", "a64.ret_val3_f64"],
96            ["a64.ret_val4_f32", "a64.ret_val4_f64"],
97        ];
98        return Some(names.get(at)?[width]);
99    }
100    let names = [
101        ["a64.ret_val_32", "a64.ret_val_32", "a64.ret_val_32", "a64.ret_val_64"],
102        ["a64.ret_val2_32", "a64.ret_val2_32", "a64.ret_val2_32", "a64.ret_val2_64"],
103    ];
104    Some(names.get(at)?[place(ty)?])
105}
106
107#[cfg(test)]
108mod tests {
109    use rucc_ir::{Float, Type};
110    use rucc_target::aarch64;
111
112    use super::*;
113
114    /// Every type the four functions answer for, and a few they do not.
115    fn types() -> Vec<Type> {
116        let mut types: Vec<Type> = [1, 8, 16, 32, 64, 128].into_iter().map(Type::int).collect();
117        types
118            .extend([Float::F16, Float::F32, Float::F64, Float::F80, Float::F128].map(Type::float));
119        types.push(Type::PTR);
120        types
121    }
122
123    #[test]
124    fn every_name_is_an_instruction_the_machine_describes() {
125        let described = |name: &str| {
126            let bare = name.strip_prefix("a64.").expect("an AArch64 name");
127            assert!(aarch64::form(bare).is_some(), "{name}");
128        };
129        for ty in types() {
130            for name in [head_of(ty), load_of(ty), store_of(ty)]
131                .into_iter()
132                .chain((0..4).map(|at| ret_of(ty, at)))
133            {
134                name.map(described);
135            }
136        }
137        for name in [INSTS.call, INSTS.call_reg, INSTS.lea, INSTS.small] {
138            described(name);
139        }
140    }
141
142    #[test]
143    fn the_four_answer_for_the_same_types() {
144        for ty in types() {
145            let arrives = head_of(ty).is_some();
146            assert_eq!(arrives, load_of(ty).is_some(), "{ty:?}");
147            assert_eq!(arrives, store_of(ty).is_some(), "{ty:?}");
148            assert_eq!(arrives, ret_of(ty, 0).is_some(), "{ty:?}");
149        }
150    }
151
152    #[test]
153    fn a_narrow_integer_travels_in_a_32_bit_register_and_is_read_at_its_own_width() {
154        assert_eq!(head_of(Type::int(8)), Some("a64.arg_val_32"));
155        assert_eq!(head_of(Type::int(16)), Some("a64.arg_val_32"));
156        assert_eq!(head_of(Type::PTR), Some("a64.arg_val_64"));
157        assert_eq!(load_of(Type::int(8)), Some("a64.ldr_8"));
158        assert_eq!(store_of(Type::int(16)), Some("a64.str_16"));
159        assert_eq!(ret_of(Type::int(1), 0), Some("a64.ret_val_32"));
160        assert_eq!(ret_of(Type::int(64), 1), Some("a64.ret_val2_64"));
161        assert_eq!(ret_of(Type::int(64), 2), None);
162    }
163
164    /// A structure of four `float`, `double` or `long double` members comes back one member to a
165    /// vector register, so the vector file has four places where the integer one has two.
166    #[test]
167    fn a_floating_point_aggregate_comes_back_in_up_to_four_registers() {
168        assert_eq!(ret_of(Type::float(Float::F32), 2), Some("a64.ret_val3_f32"));
169        assert_eq!(ret_of(Type::float(Float::F64), 3), Some("a64.ret_val4_f64"));
170        assert_eq!(ret_of(Type::float(Float::F128), 3), Some("a64.ret_val4_f128"));
171        assert_eq!(ret_of(Type::float(Float::F64), 4), None);
172    }
173
174    #[test]
175    fn a_long_double_is_a_quad_and_a_half_is_not_passed_yet() {
176        assert_eq!(head_of(Type::float(Float::F128)), Some("a64.arg_val_f128"));
177        assert_eq!(head_of(Type::float(Float::F16)), None);
178        assert_eq!(head_of(Type::int(128)), None);
179    }
180}