Skip to main content

rucc_target/
abi.rs

1//! How an argument travels and how a return value comes back, which is the target's answer
2//! and never C's.
3//!
4//! Design: `spec/12-abi-and-runtime.md` sections 12.1 to 12.5, and
5//! `spec/cross-compile/06-abis.md` sections 6.2 and 6.7.
6//!
7//! The same declaration passes a pair of registers on one target and a hidden pointer on
8//! another, so this is the one question about a C function that cannot be answered by reading
9//! the C. It used to be answered here, by four hand written classifiers covering the four ABIs
10//! this compiler had backends for. It is answered by [`rucc_abi`] now, where the same five ABIs
11//! are data rather than code and are checked against a reference compiler, and this module is
12//! the door between the compiler and that crate.
13//!
14//! # What is asked and what is answered
15//!
16//! A caller flattens a C type into a [`Shape`], which is a size, an alignment and the scalars
17//! inside it with the offsets the layout gave them. That is everything every psABI reads: the
18//! classification rules are all written over where the scalars are and whether they are integers
19//! or floating point. Flattening is the caller's job because it is where the C type system
20//! lives, and every rule after it is the target's.
21//!
22//! The answer is a [`Pass`], which is one of five things: nothing travels, the value travels as
23//! itself, the object travels as a list of [`Slot`]s that each hold a register's worth of it,
24//! the address of a copy travels in its place, or the object's own bytes go in the argument
25//! area. A scalar is always [`Pass::Direct`]: whether it ends up in a register or on the stack
26//! is the backend's arithmetic and not a change of form, and the only reason this cares about
27//! scalars at all is that they spend the registers an aggregate after them was hoping for.
28//!
29//! # Why one call at a time
30//!
31//! Three of these ABIs put an aggregate in memory when the registers it wanted are gone, so the
32//! answer for one argument depends on every argument before it and on whether the return value
33//! took a register on its way past. That is what [`Call`] is: the registers a call has left.
34//! Ask it about the return value first, then about the arguments in order, which is the order
35//! the ABI documents themselves are written in.
36
37use rucc_abi::abis;
38
39#[doc(inline)]
40pub use rucc_abi::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot};
41
42use crate::TargetInfo;
43
44impl TargetInfo {
45    /// The start of one call, with every argument register still to spend, and [`None`] on a
46    /// target whose ABI is not described.
47    ///
48    /// The [`None`] is AArch64 on Windows and nothing else today. That ABI is AAPCS64 with a
49    /// different variadic rule and x18 reserved, per `spec/cross-compile/06-abis.md` section 6.1,
50    /// and it is not written down yet. This used to answer AAPCS64 for it, which is the almost
51    /// right answer, and an almost right ABI is the failure mode `spec/cross-compile/02-the-goal.md`
52    /// exists to rule out: everything builds, everything links, and a structure crosses a
53    /// library boundary with its members in the wrong registers. A caller that cannot compile
54    /// the call says so instead.
55    #[must_use]
56    pub fn call(&self) -> Option<Call> {
57        abis::for_target(self.tuple).map(rucc_abi::AbiDescription::call)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use rucc_abi::Format;
64
65    use super::*;
66    use crate::Triple;
67
68    /// The target with this triple.
69    fn target(triple: &str) -> TargetInfo {
70        TargetInfo::new(triple.parse::<Triple>().expect("a triple the compiler supports"))
71    }
72
73    /// The pieces of a record whose members are these, each at the next offset it fits.
74    fn packed(scalars: &[Scalar]) -> Vec<Piece> {
75        let mut pieces = Vec::new();
76        let mut at: u64 = 0;
77        for &scalar in scalars {
78            at = at.next_multiple_of(scalar.align.max(1));
79            pieces.push(Piece { offset: at, scalar });
80            at += scalar.size;
81        }
82        pieces
83    }
84
85    /// The shape of a record whose members are these, sized and aligned the way C would.
86    fn record<'a>(pieces: &'a [Piece]) -> Shape<'a> {
87        let align = pieces.iter().map(|piece| piece.scalar.align).max().unwrap_or(1);
88        let size = pieces.iter().map(Piece::end).max().unwrap_or(0).next_multiple_of(align);
89        Shape { size, align, pieces, complex: false }
90    }
91
92    #[test]
93    fn a_triple_picks_the_abi_and_not_the_architecture_alone() {
94        let mut linux = target("x86_64-unknown-linux-gnu").call().expect("a described ABI");
95        let mut windows = target("x86_64-pc-windows-msvc").call().expect("a described ABI");
96        let pieces = packed(&[Scalar::integer(8), Scalar::integer(8)]);
97        let shape = Arg::Aggregate(record(&pieces));
98        // Sixteen bytes is two registers on SysV and a hidden pointer on Windows, which is the
99        // whole reason this is data about the target rather than a rule about C.
100        assert_eq!(
101            linux.argument(&shape),
102            Pass::Pieces(vec![
103                Slot::Integer { offset: 0, size: 8 },
104                Slot::Integer { offset: 8, size: 8 },
105            ])
106        );
107        assert_eq!(windows.argument(&shape), Pass::Reference);
108    }
109
110    #[test]
111    fn a_homogeneous_floating_point_aggregate_travels_in_vector_registers() {
112        let mut call = target("aarch64-unknown-linux-gnu").call().expect("a described ABI");
113        let pieces = packed(&[Scalar::float(Format::Single, 4); 3]);
114        let shape = Arg::Aggregate(record(&pieces));
115        // Three `float`s are three vector registers on AAPCS64, and adding anything that is not
116        // a `float` makes the whole thing an eightbyte pair in general purpose registers.
117        assert_eq!(
118            call.argument(&shape),
119            Pass::Pieces(vec![
120                Slot::Float { offset: 0, format: Format::Single },
121                Slot::Float { offset: 4, format: Format::Single },
122                Slot::Float { offset: 8, format: Format::Single },
123            ])
124        );
125    }
126
127    #[test]
128    fn the_two_darwin_targets_follow_different_abis() {
129        // Apple's arm64 is its own description rather than AAPCS64 with a note, because its
130        // variadic rule and its `long double` both differ. x86-64 Darwin is plain SysV.
131        let arm = target("aarch64-apple-darwin").call().expect("a described ABI");
132        let intel = target("x86_64-apple-darwin").call().expect("a described ABI");
133        assert_eq!(arm.abi().name, "Darwin arm64");
134        assert_eq!(intel.abi().name, "SysV AMD64");
135    }
136
137    #[test]
138    fn aarch64_on_windows_has_no_answer_rather_than_an_almost_right_one() {
139        // The one target the compiler can name and cannot classify a call for. It is AAPCS64
140        // with a different variadic rule and x18 reserved, and until that is written down
141        // saying so is the only honest answer.
142        assert!(target("aarch64-pc-windows-msvc").call().is_none());
143        assert!(target("aarch64-unknown-linux-gnu").call().is_some());
144        assert!(target("x86_64-pc-windows-msvc").call().is_some());
145    }
146
147    #[test]
148    fn every_other_triple_the_compiler_accepts_can_classify_a_call() {
149        use crate::{Arch, Env, Os};
150
151        let mut described = 0;
152        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
153            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
154                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
155                    let target = TargetInfo::new(Triple { arch, os, env });
156                    // AArch64 on Windows is the only gap, and it is one gap rather than a family
157                    // of them: a target with no operating system still has a calling convention,
158                    // because a freestanding program calls functions.
159                    let expected = !(arch == Arch::Aarch64 && os == Os::Windows);
160                    assert_eq!(
161                        target.call().is_some(),
162                        expected,
163                        "{arch:?} {os:?} {env:?} disagrees about whether its ABI is described"
164                    );
165                    described += usize::from(target.call().is_some());
166                }
167            }
168        }
169        // Forty eight combinations the triple can hold, less the four spellings of AArch64 on
170        // Windows, which are one gap rather than four because the environment does not reach the
171        // choice of ABI on that pair.
172        assert_eq!(described, 44);
173    }
174}