Skip to main content

rucc_abi/
shape.rs

1//! What a classifier is asked about, and what it answers.
2//!
3//! Design: `spec/cross-compile/06-abis.md` sections 6.2 and 6.7.
4//!
5//! Everything in this module is deliberately not C. A psABI does not read a C type, it reads a
6//! size, an alignment, and where the scalars inside are and whether each one is an integer or a
7//! floating point value. Flattening a C type down to that is the compiler's job, because that
8//! is where the C type system lives, and every rule after it is the target's.
9//!
10//! Keeping the boundary there is what lets the descriptions in [`crate::abis`] be data. A rule
11//! written over "the members of the struct" would have to know about unions, arrays, bit-fields
12//! and anonymous members. A rule written over a flat list of scalars at offsets does not, and
13//! every psABI on `spec/cross-compile/06-abis.md` section 6.1's list turns out to be expressible over the flat
14//! list.
15
16/// A floating point format.
17///
18/// The compiler's own [`rucc_base::float::Format`], rather than a copy of it. A format is one
19/// fact and it has to be the same fact in a data layout, in a psABI rule and in a constant the
20/// front end folded, because those three meet: the layout says a target's `long double` is x87,
21/// the ABI rule says an x87 value goes on the stack, and the constant evaluator has to produce
22/// eighty bits for it. Two enums with the same variants let those drift apart one variant at a
23/// time and the drift shows up as a wrong number rather than as a build error.
24///
25/// The width and the format are separate facts, which is the trap in `spec/cross-compile/06-abis.md` section
26/// 6.2 item 1. An x87 `long double` is eighty bits of value stored in twelve bytes on i386 and
27/// sixteen on x86-64, and a `long double` on AArch64 Linux is a different format entirely at the
28/// same sixteen bytes. A rule written over the width alone gets both wrong.
29#[doc(inline)]
30pub use rucc_base::float::Format;
31
32/// What a scalar is, once the ABI is the one asking.
33///
34/// Signedness is not here. Every ABI in `spec/cross-compile/06-abis.md` section 6.1 passes a value of a given
35/// width the same way whichever end of the range it sits at, and the widening a narrow argument
36/// gets on the way into a register is a property of the call rather than of the type.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Kind {
39    /// An integer, an enumeration, a `bool` or a pointer.
40    Integer,
41    /// A floating point value in this format.
42    Float(Format),
43}
44
45/// One scalar, with the three facts a psABI reads about it.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct Scalar {
48    /// Whether it is an integer or a floating point value.
49    pub kind: Kind,
50    /// How many bytes it takes in memory, which is the target's answer and not the format's.
51    pub size: u64,
52    /// What it is aligned to, in bytes.
53    ///
54    /// One for a bit-field, which may start anywhere and is an integer wherever it starts. That
55    /// distinction earns its keep on SysV, where an ordinary member away from its natural
56    /// alignment sends the whole aggregate to memory and a bit-field straddling an eightbyte
57    /// does not.
58    pub align: u64,
59}
60
61impl Scalar {
62    /// An integer of this size, aligned to itself.
63    #[must_use]
64    pub const fn integer(size: u64) -> Self {
65        Self { kind: Kind::Integer, size, align: size }
66    }
67
68    /// A floating point value in this format, of this size, aligned to itself.
69    #[must_use]
70    pub const fn float(format: Format, size: u64) -> Self {
71        Self { kind: Kind::Float(format), size, align: size }
72    }
73
74    /// Whether it is a floating point value.
75    #[must_use]
76    pub const fn is_float(self) -> bool {
77        matches!(self.kind, Kind::Float(_))
78    }
79}
80
81/// One scalar inside an aggregate, at the offset the layout gave it.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct Piece {
84    /// Where it starts, in bytes from the start of the aggregate.
85    pub offset: u64,
86    /// What it is.
87    pub scalar: Scalar,
88}
89
90impl Piece {
91    /// One past the last byte it covers.
92    #[must_use]
93    pub const fn end(&self) -> u64 {
94        self.offset + if self.scalar.size == 0 { 1 } else { self.scalar.size }
95    }
96}
97
98/// An aggregate, as much of it as an ABI cares about.
99///
100/// The pieces are every scalar in it with arrays and nested records flattened out, in offset
101/// order. Padding is not a piece: a hole is described by the offsets on either side of it, which
102/// is the form every classification rule is written in.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct Shape<'a> {
105    /// The size of the whole thing in bytes, padding included.
106    pub size: u64,
107    /// What it is aligned to, in bytes.
108    pub align: u64,
109    /// The scalars in it.
110    pub pieces: &'a [Piece],
111    /// Whether it is a `_Complex` rather than a `struct` or a `union` of the same shape.
112    ///
113    /// Exactly one rule reads this, and it is on SysV AMD64, where `_Complex long double` comes
114    /// back on the x87 stack and `struct { long double a, b; }`, which is the same thirty two
115    /// bytes with the same two members in the same places, comes back in memory. Without the
116    /// flag there is no way to tell those apart from the shape, and they are passed differently.
117    pub complex: bool,
118}
119
120impl Shape<'_> {
121    /// Whether it holds at least one scalar and every one of them is this format.
122    #[must_use]
123    pub fn is_all_of(&self, format: Format) -> bool {
124        !self.pieces.is_empty()
125            && self.pieces.iter().all(|piece| piece.scalar.kind == Kind::Float(format))
126    }
127}
128
129/// One argument, or one return value, as much of it as an ABI cares about.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum Arg<'a> {
132    /// `void`, which is a return type and never an argument.
133    Void,
134    /// A scalar.
135    Scalar(Scalar),
136    /// A `struct`, a `union`, an array or a `_Complex`.
137    Aggregate(Shape<'a>),
138}
139
140/// One register's worth of an aggregate that travels in registers, and which of the object's
141/// bytes go in it.
142///
143/// A slot says what the object's bytes are read as rather than what the program wrote into them.
144/// An eightbyte holding two `float`s is a [`Slot::Float`] of [`Format::Double`], because eight
145/// bytes of floating point data arrive in one vector register whichever way the program divided
146/// them up and the bits are the same either way.
147///
148/// The offset is carried rather than derived because it cannot be worked out from the run of
149/// slots. Two eightbytes are at zero and eight, four `float`s of a homogeneous aggregate are
150/// four bytes apart, and `struct { double value; int tag; }` on RISC-V travels in one floating
151/// point and one integer register whose bytes are at zero and eight, where the second is not
152/// where the first one ended.
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum Slot {
155    /// An integer this many bytes wide, which is one general purpose register.
156    ///
157    /// The last slot of an aggregate is only as wide as what is left of it, so a twelve byte
158    /// structure is eight bytes and then four and nothing reads a byte past the object.
159    Integer {
160        /// Where its bytes start in the object.
161        offset: u64,
162        /// How many of them there are.
163        size: u32,
164    },
165    /// A floating point value in this format, which is one vector register.
166    Float {
167        /// Where its bytes start in the object.
168        offset: u64,
169        /// What is read out of them.
170        format: Format,
171    },
172}
173
174impl Slot {
175    /// Where its bytes start in the object.
176    #[must_use]
177    pub const fn offset(self) -> u64 {
178        match self {
179            Self::Integer { offset, .. } | Self::Float { offset, .. } => offset,
180        }
181    }
182
183    /// Whether it is a vector register rather than a general purpose one.
184    #[must_use]
185    pub const fn is_float(self) -> bool {
186        matches!(self, Self::Float { .. })
187    }
188}
189
190/// How one value travels.
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum Pass {
193    /// Nothing travels, which is `void` and an aggregate of no size.
194    Ignore,
195    /// The value itself. Every scalar is this.
196    Direct,
197    /// The object's bytes, in these slots, which is what passing an aggregate in registers means
198    /// once the object has been taken apart.
199    Pieces(Vec<Slot>),
200    /// The address of a copy, in the place the value itself would have gone.
201    ///
202    /// For an argument the caller makes the copy. For a return value the caller passes the
203    /// address of somewhere to put it, which is the hidden first argument.
204    Reference,
205    /// The object's own bytes in the argument area, with no address anywhere.
206    ///
207    /// SysV's MEMORY class and AAPCS's aggregate that ran out of registers. Never a return
208    /// value: a return value that does not fit in registers is [`Pass::Reference`].
209    Memory,
210}