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::{Abi, 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    extend: extend_of,
25};
26
27/// What the instruction that extends a narrow integer to 32 bits is called, for a value the
28/// signature says travels extended.
29///
30/// Apple's arm64 is the one that says so, and it is the lowering's business to mark only what it
31/// asks for, so this answers for any mark it finds. A `bool` is one bit and is widened by keeping
32/// that bit, which is what the other side reads it as.
33fn extend_of(ty: Type, abi: Abi) -> Option<&'static str> {
34    match (abi, ty.bits()) {
35        (Abi::Zext, 1) => Some("a64.bit_to_32"),
36        (Abi::Sext, 8) => Some("a64.sxtb_32"),
37        (Abi::Sext, 16) => Some("a64.sxth_32"),
38        (Abi::Zext, 8) => Some("a64.uxtb_32"),
39        (Abi::Zext, 16) => Some("a64.uxth_32"),
40        _ => None,
41    }
42}
43
44/// What the pseudo for an argument of that type is called.
45fn head_of(ty: Type) -> Option<&'static str> {
46    if crate::term::is_quad(ty) {
47        return Some("a64.arg_val_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.arg_val_f32", "a64.arg_val_f64"][at]);
54    }
55    let names = ["a64.arg_val_32", "a64.arg_val_32", "a64.arg_val_32", "a64.arg_val_64"];
56    Some(names[place(ty)?])
57}
58
59/// What the instruction that reads an argument of that type out of memory is called.
60///
61/// A narrow one is read at its own width, for the reason [`super::load_of`] gives: the caller
62/// wrote a whole slot and the convention says nothing about what is above the value in it.
63fn load_of(ty: Type) -> Option<&'static str> {
64    if crate::term::is_quad(ty) {
65        return Some("a64.ldr_f128");
66    }
67    if crate::term::is_half(ty) {
68        return None;
69    }
70    if let Some(at) = crate::term::float_slot(ty) {
71        return Some(["a64.ldr_f32", "a64.ldr_f64"][at]);
72    }
73    let names = ["a64.ldr_8", "a64.ldr_16", "a64.ldr_32", "a64.ldr_64"];
74    Some(names[place(ty)?])
75}
76
77/// What the instruction that writes an argument of that type into memory is called.
78fn store_of(ty: Type) -> Option<&'static str> {
79    if crate::term::is_quad(ty) {
80        return Some("a64.str_f128");
81    }
82    if crate::term::is_half(ty) {
83        return None;
84    }
85    if let Some(at) = crate::term::float_slot(ty) {
86        return Some(["a64.str_f32", "a64.str_f64"][at]);
87    }
88    let names = ["a64.str_8", "a64.str_16", "a64.str_32", "a64.str_64"];
89    Some(names[place(ty)?])
90}
91
92/// What the pseudo that leaves a returned value in its register is called, for the value at that
93/// place in its own register file.
94///
95/// Two places in each file, the same as x86-64. AAPCS64 gives back up to eight registers of either
96/// kind, but the front end splits nothing into more than two pieces yet, so a third name would be
97/// a name nothing asks for.
98fn ret_of(ty: Type, at: usize) -> Option<&'static str> {
99    if crate::term::is_quad(ty) {
100        let names =
101            ["a64.ret_val_f128", "a64.ret_val2_f128", "a64.ret_val3_f128", "a64.ret_val4_f128"];
102        return Some(*names.get(at)?);
103    }
104    if crate::term::is_half(ty) {
105        return None;
106    }
107    if let Some(width) = crate::term::float_slot(ty) {
108        // Four, since a homogeneous aggregate of up to four floating point members comes back
109        // one member to a register.
110        let names = [
111            ["a64.ret_val_f32", "a64.ret_val_f64"],
112            ["a64.ret_val2_f32", "a64.ret_val2_f64"],
113            ["a64.ret_val3_f32", "a64.ret_val3_f64"],
114            ["a64.ret_val4_f32", "a64.ret_val4_f64"],
115        ];
116        return Some(names.get(at)?[width]);
117    }
118    let names = [
119        ["a64.ret_val_32", "a64.ret_val_32", "a64.ret_val_32", "a64.ret_val_64"],
120        ["a64.ret_val2_32", "a64.ret_val2_32", "a64.ret_val2_32", "a64.ret_val2_64"],
121    ];
122    Some(names.get(at)?[place(ty)?])
123}
124
125#[cfg(test)]
126mod tests {
127    use rucc_ir::{Float, Type};
128    use rucc_target::aarch64;
129
130    use super::*;
131
132    /// Every type the four functions answer for, and a few they do not.
133    fn types() -> Vec<Type> {
134        let mut types: Vec<Type> = [1, 8, 16, 32, 64, 128].into_iter().map(Type::int).collect();
135        types
136            .extend([Float::F16, Float::F32, Float::F64, Float::F80, Float::F128].map(Type::float));
137        types.push(Type::PTR);
138        types
139    }
140
141    #[test]
142    fn every_name_is_an_instruction_the_machine_describes() {
143        let described = |name: &str| {
144            let bare = name.strip_prefix("a64.").expect("an AArch64 name");
145            assert!(aarch64::form(bare).is_some(), "{name}");
146        };
147        for ty in types() {
148            for name in [head_of(ty), load_of(ty), store_of(ty)]
149                .into_iter()
150                .chain((0..4).map(|at| ret_of(ty, at)))
151            {
152                name.map(described);
153            }
154        }
155        for name in [INSTS.call, INSTS.call_reg, INSTS.lea, INSTS.small] {
156            described(name);
157        }
158    }
159
160    #[test]
161    fn the_four_answer_for_the_same_types() {
162        for ty in types() {
163            let arrives = head_of(ty).is_some();
164            assert_eq!(arrives, load_of(ty).is_some(), "{ty:?}");
165            assert_eq!(arrives, store_of(ty).is_some(), "{ty:?}");
166            assert_eq!(arrives, ret_of(ty, 0).is_some(), "{ty:?}");
167        }
168    }
169
170    #[test]
171    fn a_narrow_integer_travels_in_a_32_bit_register_and_is_read_at_its_own_width() {
172        assert_eq!(head_of(Type::int(8)), Some("a64.arg_val_32"));
173        assert_eq!(head_of(Type::int(16)), Some("a64.arg_val_32"));
174        assert_eq!(head_of(Type::PTR), Some("a64.arg_val_64"));
175        assert_eq!(load_of(Type::int(8)), Some("a64.ldr_8"));
176        assert_eq!(store_of(Type::int(16)), Some("a64.str_16"));
177        assert_eq!(ret_of(Type::int(1), 0), Some("a64.ret_val_32"));
178        assert_eq!(ret_of(Type::int(64), 1), Some("a64.ret_val2_64"));
179        assert_eq!(ret_of(Type::int(64), 2), None);
180    }
181
182    /// A structure of four `float`, `double` or `long double` members comes back one member to a
183    /// vector register, so the vector file has four places where the integer one has two.
184    #[test]
185    fn a_floating_point_aggregate_comes_back_in_up_to_four_registers() {
186        assert_eq!(ret_of(Type::float(Float::F32), 2), Some("a64.ret_val3_f32"));
187        assert_eq!(ret_of(Type::float(Float::F64), 3), Some("a64.ret_val4_f64"));
188        assert_eq!(ret_of(Type::float(Float::F128), 3), Some("a64.ret_val4_f128"));
189        assert_eq!(ret_of(Type::float(Float::F64), 4), None);
190    }
191
192    #[test]
193    fn a_long_double_is_a_quad_and_a_half_is_not_passed_yet() {
194        assert_eq!(head_of(Type::float(Float::F128)), Some("a64.arg_val_f128"));
195        assert_eq!(head_of(Type::float(Float::F16)), None);
196        assert_eq!(head_of(Type::int(128)), None);
197    }
198}