Skip to main content

rucc_abi/
describe.rs

1//! The language an ABI is described in.
2//!
3//! Design: `spec/cross-compile/06-abis.md` section 6.7.
4//!
5//! # The argument, restated
6//!
7//! `spec/cross-compile/06-abis.md` section 6.1 lists fifteen psABIs and the compiler has four of them today,
8//! hand written, at about a thousand lines. Fifteen at that rate is six to ten thousand lines of
9//! the most bug prone code in a compiler, and `spec/cross-compile/02-the-goal.md` claim 3 says the per target
10//! line count outside the target crate and the rule set has to be zero.
11//!
12//! # Where the line is drawn, and why here
13//!
14//! The tempting version of this idea is to make everything data, and it does not work. The SysV
15//! eightbyte merge is a real algorithm with a real fixed point, the homogeneous aggregate scan
16//! walks a list and compares members, and writing either as a table produces an interpreter that
17//! is longer than the four functions it replaced and slower than all of them.
18//!
19//! So the split is between mechanism and policy. The mechanisms are code, in [`crate::classify`],
20//! and there are four of them across the five ABIs described here: cut into eightbytes and merge,
21//! look for a homogeneous run of floating point members, look for a one or two member aggregate
22//! with a floating point member in it, and check the size against a list. The policies are data,
23//! and a policy is which mechanisms an ABI applies, in what order, with what limits, and what
24//! happens when the registers a mechanism wanted are not there.
25//!
26//! That split is what makes the count work. The fifth ABI reuses a mechanism and costs a
27//! description. The eleventh probably does too. A new mechanism is a real cost and it is paid
28//! once per idea rather than once per target, and there are far fewer ideas than targets.
29//!
30//! # The performance objection
31//!
32//! Section 6.7 raises it against itself: a compile time decision becoming a run time table walk,
33//! on the hot path. Two answers. The classifier runs once per call site and once per function
34//! signature rather than once per instruction, so the exposure is bounded, and the descriptions
35//! are `const` data reached through a `&'static`, so the branch predictor sees the same rule list
36//! for every call in a translation unit.
37//!
38//! Bounded is a prediction rather than a measurement, and the measurement is `spec/cross-compile/02-the-goal.md`
39//! claim 2's benchmark at the migration point. The fallback if it fails is written down in
40//! section 6.7: the descriptions stay as the source of truth for the tests and the documentation
41//! and the classifiers go back to being hand written, which loses claim 3 and keeps claim 2.
42//! Claim 2 outranks claim 3.
43
44use crate::shape::Format;
45
46/// One psABI, completely.
47///
48/// Everything an ABI decides about how a value travels is in here. What is deliberately not in
49/// here is in [`AbiDescription::stack_args`]'s note: prologue emission, register allocation
50/// constraints and unwind emission are per architecture code with per ABI parameters, and
51/// section 6.7 is explicit that turning those into tables costs more than the duplication.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct AbiDescription {
54    /// What the ABI is called, which is the name that goes in a diagnostic and in the report.
55    pub name: &'static str,
56    /// The registers a call starts with, and how a scalar spends them.
57    pub banks: Banks,
58    /// How a scalar spends registers, which differs between ABIs more than it looks like it
59    /// should.
60    pub scalars: Scalars,
61    /// The rules for a return value, tried in order.
62    pub returns: &'static [Rule],
63    /// The rules for an argument, tried in order.
64    pub arguments: &'static [Rule],
65    /// Where the address of a return value that comes back in memory travels.
66    pub return_pointer: ReturnPointer,
67    /// What a variadic argument does differently.
68    pub variadic: Variadic,
69    /// How arguments that did not get a register sit in the argument area.
70    ///
71    /// Nothing in this crate reads it. It is here because it is a fact about the ABI and section
72    /// 6.7 wants the description to be the source of truth for the whole ABI rather than for the
73    /// half of it that happens to be classification, and because the backend that does read it
74    /// should be reading it from the same place the tests are generated from.
75    pub stack_args: StackArgs,
76    /// What an integer narrower than an `int` has above it in the register it travels in.
77    pub narrow: Narrow,
78}
79
80impl AbiDescription {
81    /// Whether a scalar of this size travels as the address of a copy the caller made.
82    ///
83    /// The size rule of the one ABI that does this is written over the size of the object and says
84    /// nothing about what is in it, which is why this takes a number rather than a [`Scalar`]: a
85    /// pass writing a call to a runtime routine has a width in hand and no C type behind it, and the
86    /// answer is the same for both askers because there is only the one rule.
87    ///
88    /// [`Scalar`]: crate::shape::Scalar
89    #[must_use]
90    pub const fn scalar_is_by_reference(&self, size: u64) -> bool {
91        self.scalars.wide_is_by_reference && !matches!(size, 1 | 2 | 4 | 8)
92    }
93
94    /// The format an integer of this size comes back in, where the ABI brings one back whole in a
95    /// vector register rather than through the address the caller passed.
96    ///
97    /// The companion to [`AbiDescription::scalar_is_by_reference`] and asked by the same kind of
98    /// caller for the same reason, a pass writing a call to a runtime routine with a width in hand
99    /// and no C type behind it. It is a separate question rather than the same one answered the
100    /// other way because the two disagree on the one ABI that says yes to either: Windows x64
101    /// passes a sixteen byte integer as an address and brings one back in xmm0, so `__fixtfti`
102    /// there takes an address and answers in a vector register.
103    ///
104    /// [`None`] where the size is one a register holds, since then nothing about it is wide, and
105    /// [`None`] on every ABI that does not do this.
106    #[must_use]
107    pub const fn wide_integer_returns_in(&self, size: u64) -> Option<Format> {
108        match self.scalars.wide_integer_returns_in {
109            Some(format) if self.scalar_is_by_reference(size) => Some(format),
110            _ => None,
111        }
112    }
113}
114
115/// The registers a call starts with.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct Banks {
118    /// General purpose argument registers.
119    pub integer: u32,
120    /// Floating point argument registers.
121    pub float: u32,
122    /// Whether the two banks share argument positions.
123    ///
124    /// True on Windows x64, where rcx, rdx, r8 and r9 and xmm0 to xmm3 are the same four
125    /// positions, so a call taking an `int` and then a `double` uses rcx and xmm1 and never
126    /// xmm0. When this is set the floating point bank is not counted separately and every spend
127    /// comes out of the integer one, which is why [`Banks::float`] is zero on such a target.
128    pub shared: bool,
129    /// The width of a general purpose register in bytes, which is how wide one integer slot is.
130    pub integer_width: u64,
131    /// The widest floating point value a vector register holds, in bytes.
132    ///
133    /// Eight on RISC-V LP64D, where a sixteen byte `long double` therefore travels in integer
134    /// registers, and sixteen on AAPCS64, where it does not. This is the field that makes the
135    /// difference between those two ABIs' otherwise identical treatment of a wide float.
136    pub float_width: u64,
137}
138
139/// How a scalar spends registers.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct Scalars {
142    /// A floating point value in this format travels in the argument area and spends nothing.
143    ///
144    /// `Some(Format::X87Extended)` on SysV AMD64, where a `long double` argument is on the stack
145    /// and there is no register file it could have gone in. `None` everywhere else.
146    pub in_memory: Option<Format>,
147    /// Whether an integer wider than one register takes every register it needs or none of them.
148    ///
149    /// True on SysV AMD64, where an `__int128` takes two consecutive general purpose registers,
150    /// and taking one of them would spend a register on half a value and deny it to an argument
151    /// after it that could have used the whole thing.
152    pub wide_integer_is_all_or_nothing: bool,
153    /// Whether an integer wider than one register that goes in registers starts at an even one.
154    ///
155    /// True on AAPCS64, whose rule C.9 rounds the next general purpose register up to an even
156    /// number for a value aligned to sixteen, so an `__int128` after one `int` is in x2 and x3 and
157    /// x1 is left empty. False on Darwin arm64, which gives it x1 and x2, and everywhere else.
158    pub wide_integer_starts_even: bool,
159    /// Whether an integer wider than one register that finds too few left spends the rest of them.
160    ///
161    /// True on AAPCS64, Darwin's included, where such a value goes in the argument area and the
162    /// registers it could not use are not given to the arguments after it. False on SysV AMD64,
163    /// where they are.
164    pub wide_integer_drains: bool,
165    /// Whether a scalar of a size no register holds travels as the address of a copy the caller
166    /// made, the way an aggregate of that size does.
167    ///
168    /// True on Windows x64, whose one rule is about the size of the object and not about what is
169    /// inside it: anything that is not one, two, four or eight bytes is an address, and a
170    /// `long double`, a `_Float128` and an `__int128` are all sixteen bytes there. False on the
171    /// other four, where a wide scalar has registers to travel in or a place in the argument area
172    /// of its own, which is what [`Scalars::in_memory`] says for the one that puts it there.
173    pub wide_is_by_reference: bool,
174    /// The format a wide integer comes back in, where the ABI brings one back in a vector
175    /// register rather than through the address the caller passed.
176    ///
177    /// `Some(Format::Quad)` on Windows x64, and for an integer only: gcc returns an `__int128`
178    /// in xmm0 there, which is its own answer to a convention that has no 128-bit integer in it,
179    /// and brings the two floating point types of the same size back through the address like
180    /// everything else that size. `None` everywhere else, including on the ABIs where a wide
181    /// integer is not by reference to begin with.
182    pub wide_integer_returns_in: Option<Format>,
183}
184
185/// Where the address of a return value that comes back in memory travels.
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum ReturnPointer {
188    /// A hidden first argument, which spends an argument register.
189    ///
190    /// SysV AMD64, Windows x64 and RISC-V. This is why the return value is classified before the
191    /// arguments: on these three, a function returning a large structure has one argument
192    /// register fewer than the same function returning `int`, and classifying the arguments
193    /// first gives the wrong answer for the last one of them.
194    FirstArgument,
195    /// A register outside the argument bank, which spends nothing.
196    ///
197    /// AAPCS64's x8. A function returning a large structure still has all eight argument
198    /// registers for what it was called with.
199    Dedicated,
200}
201
202/// What a variadic argument does differently.
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum Variadic {
205    /// Nothing. A variadic argument is classified the same way a fixed one is.
206    SameAsFixed,
207    /// Every variadic argument is in the argument area, whatever registers are left.
208    ///
209    /// Darwin arm64, and the divergence that makes it a separate ABI rather than AAPCS64 with
210    /// notes, per `spec/cross-compile/06-abis.md` section 6.3. It is also the reason a variadic call there is
211    /// ABI-incompatible with a non-variadic one, so calling an unprototyped function works until
212    /// the day it does not.
213    AlwaysMemory,
214    /// A floating point argument travels in both its vector register and the corresponding
215    /// general purpose one.
216    ///
217    /// Windows x64, because the callee of a variadic function does not know which bank to read.
218    BothBanks,
219    /// Every argument of a variadic function travels in general purpose registers and the argument
220    /// area, the named ones as well as the rest, and nothing is looked inside for floating point
221    /// members.
222    ///
223    /// Windows on AArch64. The callee homes x0 to x7 directly below the arguments the caller left
224    /// in memory and walks the lot with a `char *`, so there is only one bank it could read. A
225    /// `double` travels as its bits in the next x register, a structure of four `float`s is the
226    /// sixteen bytes of two x registers rather than four s registers, and one of four `double`s is
227    /// over sixteen bytes and so travels as the address of a copy. It reaches the named arguments
228    /// too, which is the difference from [`Variadic::BothBanks`]: `void f(double, ...)` takes its
229    /// `double` in x0, and a call through a prototype without the `...` puts it in d0. The value
230    /// that comes back is not an argument and comes back where it always does.
231    IntegersOnly,
232}
233
234/// What the bits above an integer narrower than an `int` are when it travels in a register.
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum Narrow {
237    /// Anything. The side that receives the value reads only its own bits, which is every ELF ABI
238    /// here as their documents are written, whatever a particular compiler happens to leave.
239    Unspecified,
240    /// Extended to 32 bits by the type's own sign, by the caller for an argument and by the
241    /// callee for a return value.
242    ///
243    /// Darwin arm64, where clang's callee of `f(unsigned char)` returns its argument with a bare
244    /// `ret`, trusting the caller to have cleared bits 8 to 31, and its caller of a function
245    /// returning `unsigned char` compares all of `w0` without clearing them itself.
246    ToInt,
247}
248
249/// How arguments that did not get a register sit in the argument area.
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum StackArgs {
252    /// Each argument occupies a whole number of registers' worth of the argument area, so a
253    /// `char` takes eight bytes. Every ELF ABI here.
254    RegisterSized,
255    /// Each argument occupies its natural size and alignment, so a `char` takes one byte.
256    ///
257    /// Darwin arm64. Getting this wrong produces functions whose ninth argument onward is
258    /// garbage, on Darwin only, which is `spec/cross-compile/06-abis.md` section 6.3's first row.
259    Packed,
260}
261
262/// One rule: what an aggregate has to look like, how it travels if it does, and what happens
263/// when the registers it wanted are not there.
264///
265/// The rules are tried in order and the first one whose test matches wins, so a rule list reads
266/// the way the psABI document it came from is written: the special cases first, the general size
267/// rule after them, and the catch-all last.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub struct Rule {
270    /// What the aggregate has to look like.
271    pub when: Test,
272    /// How it travels if it does.
273    pub then: Travel,
274    /// What happens if the registers it wanted are not there.
275    pub short: Short,
276}
277
278impl Rule {
279    /// A rule that cannot run short of registers, which is every rule whose result does not
280    /// depend on how many are left.
281    #[must_use]
282    pub const fn new(when: Test, then: Travel) -> Self {
283        Self { when, then, short: Short::Unchanged }
284    }
285
286    /// The same rule, with what happens when the registers are gone.
287    #[must_use]
288    pub const fn short(self, short: Short) -> Self {
289        Self { short, ..self }
290    }
291}
292
293/// What an aggregate has to look like for a rule to apply.
294///
295/// Four of these look inside the aggregate and the rest read its size. The four are the
296/// mechanisms of this crate, and the claim in section 6.7 is that the number of them grows much
297/// more slowly than the number of ABIs.
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum Test {
300    /// Anything, which is what the last rule in a list is.
301    Anything,
302    /// An aggregate of no size, which is a GNU empty struct and travels nowhere.
303    Empty,
304    /// A size that is exactly one of these.
305    ///
306    /// Windows x64's rule, and the sharpest one on the list: anything not exactly one, two, four
307    /// or eight bytes travels as an address, so a three byte structure and a three hundred byte
308    /// structure are passed the same way. Also s390x's, with the same list.
309    SizeOneOf(&'static [u64]),
310    /// A size at most this many bytes.
311    SizeAtMost(u64),
312    /// A homogeneous floating point aggregate of at most this many members.
313    ///
314    /// AAPCS64's HFA, and the same idea with a different limit on AAPCS32 hard float and on
315    /// ELFv2. Homogeneous means every scalar in it is the same floating point type once arrays
316    /// and nested records are flattened, and that they fill the aggregate with no padding left
317    /// over. The second half is what rules out `struct { float a; char pad[8]; }` and anything a
318    /// zero width bit-field has stretched.
319    Homogeneous {
320        /// The most members it can have and still travel in vector registers.
321        limit: usize,
322    },
323    /// One or two members with at least one floating point member between them, each fitting one
324    /// register.
325    ///
326    /// The RISC-V rule, and LoongArch's. `struct { double re, im; }` is two floating point
327    /// registers and `struct { double value; int tag; }` is one of each, which no other ABI on
328    /// the list does. A member wider than a floating point register is not a floating point
329    /// member for this purpose, which is what makes a `long double` here behave like an integer
330    /// pair.
331    FloatPair,
332    /// Every scalar is an x87 `long double`, and there is one of them, or two if it is a
333    /// `_Complex`.
334    ///
335    /// The SysV return path, where a `long double` comes back in st(0) and a `_Complex long
336    /// double` in st(0) and st(1). A record holding two of them is the same thirty two bytes and
337    /// comes back in memory, which is the only thing [`crate::Shape::complex`] is for.
338    X87Stack,
339    /// The SysV eightbyte classification succeeds, and no eightbyte came out x87.
340    ///
341    /// The intricate one. The aggregate is cut into eight byte chunks, each chunk gets a class
342    /// from merging the classes of every scalar reaching into it, and any chunk that comes out
343    /// MEMORY takes the whole argument to memory with it. The cases that catch people are all in
344    /// the merge: an eightbyte holding an `int` and a `float` together is INTEGER, so the float
345    /// travels in a general purpose register, and a member away from its natural alignment sends
346    /// the whole thing to memory.
347    Eightbytes {
348        /// The largest aggregate that can be classified at all, sixteen bytes on SysV.
349        ///
350        /// It is a consequence of the eight eightbyte limit rather than an independent rule: an
351        /// aggregate over two eightbytes travels in registers only when every eightbyte after
352        /// the first is SSEUP. A vector produces a run of those, and a `_Float128` produces one,
353        /// and sixteen bytes of `_Float128` is inside this limit rather than over it.
354        limit: u64,
355    },
356}
357
358/// How a value travels when a rule's test matched.
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum Travel {
361    /// Nothing travels.
362    Ignore,
363    /// In the slots the test found, which is only meaningful after a test that finds some.
364    AsFound,
365    /// As a run of integer registers covering the object, one per register width, the last one
366    /// holding only what is left.
367    AsIntegers,
368    /// As one integer register of the object's exact size, whatever is in it.
369    ///
370    /// Windows x64, where a `struct { float x, y; }` arrives in rcx rather than in xmm0.
371    AsOneInteger,
372    /// As the address of a copy.
373    ByReference,
374    /// As the object's own bytes in the argument area.
375    InMemory,
376}
377
378/// What happens when the registers a rule wanted are not there.
379///
380/// This is the part of a psABI that is easiest to get wrong and hardest to notice, because every
381/// test anybody writes by hand passes few enough arguments that it never comes up. The ninth
382/// argument of a call is not classified the way the first one is on three of the five ABIs here.
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384pub enum Short {
385    /// Running out changes nothing. The value goes in the argument area in the same form it
386    /// would have had in a register, and the spend saturates.
387    ///
388    /// Every scalar, and every aggregate on Windows x64, where an argument past the fourth
389    /// travels the way the first one does.
390    Unchanged,
391    /// The argument goes in the argument area, and the registers that are left stay available
392    /// for the arguments after it.
393    ///
394    /// SysV AMD64. An aggregate that did not fit does not stop a later scalar from getting a
395    /// register, which is the opposite of what AAPCS64 does with the same situation.
396    Memory,
397    /// The argument goes in the argument area, and every remaining register of that bank goes
398    /// with it.
399    ///
400    /// AAPCS64 and RISC-V. The draining is the surprising half: once one aggregate has been put
401    /// on the stack for want of registers, a later argument that would have fitted goes on the
402    /// stack too, because the ABI will not leave a hole in the register sequence.
403    MemoryAndDrain,
404    /// The rule does not apply after all, and the rules after it are tried.
405    ///
406    /// The RISC-V floating point pair, which is a bonus rather than a requirement: an aggregate
407    /// the rule reached but the registers did not is classified by the ordinary size rules and
408    /// still travels in registers if those find any.
409    TryNextRule,
410}
411
412#[cfg(test)]
413mod tests {
414    use crate::abis::{AAPCS64, SYSV_AMD64, WIN64};
415    use crate::shape::Format;
416
417    /// The two questions about a wide scalar are asked separately because the one ABI that says
418    /// yes to either gives different answers to them.
419    #[test]
420    fn windows_passes_a_wide_scalar_as_an_address_and_brings_an_integer_back_in_a_register() {
421        assert!(WIN64.scalar_is_by_reference(16));
422        assert_eq!(WIN64.wide_integer_returns_in(16), Some(Format::Quad));
423    }
424
425    /// A size a register holds is not wide, whatever the ABI says about the ones that are.
426    #[test]
427    fn a_size_a_register_holds_is_neither() {
428        for size in [1, 2, 4, 8] {
429            assert!(!WIN64.scalar_is_by_reference(size), "{size} bytes fits a register");
430            assert_eq!(WIN64.wide_integer_returns_in(size), None, "{size} bytes fits a register");
431        }
432    }
433
434    /// Everywhere else a wide scalar has registers to travel in, so neither question applies.
435    #[test]
436    fn the_conventions_with_registers_for_one_say_no_to_both() {
437        for abi in [&SYSV_AMD64, &AAPCS64] {
438            assert!(!abi.scalar_is_by_reference(16), "{}", abi.name);
439            assert_eq!(abi.wide_integer_returns_in(16), None, "{}", abi.name);
440        }
441    }
442}