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.
5//!
6//! The same declaration passes a pair of registers on one target and a hidden pointer on
7//! another, so this is the one question about a C function that cannot be answered by reading
8//! the C. It is answered here, as data and an algorithm over data, because
9//! `spec/18-package-layout.md` section 18.2 says there is no target-specific code outside this
10//! crate.
11//!
12//! # What is asked and what is answered
13//!
14//! A caller flattens a C type into a [`Shape`], which is a size, an alignment and the scalars
15//! inside it with the offsets the layout gave them. That is everything every psABI here reads:
16//! the classification rules are all written over where the scalars are and whether they are
17//! integers or floating point. Flattening is the caller's job because it is where the C type
18//! system lives, and every rule after it is the target's.
19//!
20//! The answer is a [`Pass`], which is one of five things: nothing travels, the value travels as
21//! itself, the object travels as a list of [`Slot`]s that each hold a register's worth of it,
22//! the address of a copy travels in its place, or the object's own bytes go in the argument
23//! area. A scalar is always [`Pass::Direct`]: whether it ends up in a register or on the stack
24//! is the backend's arithmetic and not a change of form, and the only reason this cares about
25//! scalars at all is that they spend the registers an aggregate after them was hoping for.
26//!
27//! # Why one call at a time
28//!
29//! Three of these ABIs put an aggregate in memory when the registers it wanted are gone, so the
30//! answer for one argument depends on every argument before it and on whether the return value
31//! took a register on its way past. That is what [`Call`] is: the registers a call has left.
32//! Ask it about the return value first, then about the arguments in order, which is the order
33//! the ABI documents themselves are written in.
34
35use rucc_base::float::Format;
36
37use crate::{Arch, Os, TargetInfo};
38
39mod aapcs;
40mod riscv;
41mod sysv;
42mod win64;
43
44/// What a scalar is, once the ABI is the one asking.
45///
46/// Signedness is not here. Every ABI on this list passes a value of a given width the same way
47/// whichever end of the range it is at, and the widening a small argument gets is a property of
48/// the call and not of the type.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Kind {
51 /// An integer, an enumeration, a `bool` or a pointer.
52 Integer,
53 /// A floating point value in this format.
54 Float(Format),
55}
56
57/// One scalar, with the two facts about it a psABI reads.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct Scalar {
60 /// Whether it is an integer or a floating point value.
61 pub kind: Kind,
62 /// How many bytes it takes in memory, which is the target's answer rather than the
63 /// format's: an x87 `long double` is eighty bits of value in sixteen bytes of storage.
64 pub size: u64,
65 /// What it is aligned to, in bytes.
66 ///
67 /// One for a bit-field, which is allowed to start anywhere and which is an integer wherever
68 /// it starts. That matters because an ordinary member that is not aligned puts the whole
69 /// aggregate in memory on SysV, and a bit-field that straddles an eightbyte does not.
70 pub align: u64,
71}
72
73/// One scalar inside an aggregate, at the offset the layout put it.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct Piece {
76 /// Where it starts, in bytes from the start of the aggregate.
77 pub offset: u64,
78 /// What it is.
79 pub scalar: Scalar,
80}
81
82impl Piece {
83 /// One past the last byte it covers.
84 fn end(&self) -> u64 {
85 self.offset + self.scalar.size.max(1)
86 }
87}
88
89/// An aggregate, as much of it as an ABI cares about.
90///
91/// The pieces are every scalar in it, arrays and nested records flattened out, in offset order.
92/// Padding is not a piece: a hole is described by the offsets around it, which is what the
93/// classification rules are written over.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct Shape<'a> {
96 /// The size of the whole thing in bytes, padding included.
97 pub size: u64,
98 /// What it is aligned to, in bytes.
99 pub align: u64,
100 /// The scalars in it.
101 pub pieces: &'a [Piece],
102 /// Whether it is a `_Complex` rather than a `struct` or a `union` of the same shape.
103 ///
104 /// One rule reads this and it is on SysV AMD64, where `_Complex long double` comes back on
105 /// the x87 stack and `struct { long double a, b; }`, which is the same thirty two bytes
106 /// with the same two members in the same places, comes back in memory.
107 pub complex: bool,
108}
109
110/// One argument, or one return value, as much of it as an ABI cares about.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum Arg<'a> {
113 /// `void`, which is a return type and never an argument.
114 Void,
115 /// A scalar.
116 Scalar(Scalar),
117 /// A `struct`, a `union`, an array or a `_Complex`.
118 Aggregate(Shape<'a>),
119}
120
121/// One register's worth of an aggregate that travels in registers, and which of the object's
122/// bytes go in it.
123///
124/// A slot is what the object's bytes are read as rather than what the program wrote. An
125/// eightbyte holding two `float`s is [`Slot::Float`] of [`Format::Double`], because eight bytes
126/// of floating point data go in one vector register whichever way they are divided up and the
127/// bits that arrive are the same either way.
128///
129/// The offset is here because it cannot be worked out from the run of slots. Two eightbytes are
130/// at zero and eight and four `float`s of a homogeneous aggregate are four bytes apart, but
131/// `struct { int a; double b; }` on RISC-V travels as an integer and a floating point register
132/// whose bytes are at zero and eight, and the second is not where the first one ended.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum Slot {
135 /// An integer this many bytes wide, which is one general purpose register.
136 ///
137 /// The last slot of an aggregate is as wide as what is left of it, so a twelve byte
138 /// structure is eight bytes and then four, and nothing reads a byte past the object.
139 Integer {
140 /// Where its bytes start in the object.
141 offset: u64,
142 /// How many of them there are.
143 size: u32,
144 },
145 /// A floating point value in this format, which is one vector register.
146 Float {
147 /// Where its bytes start in the object.
148 offset: u64,
149 /// What is read out of them.
150 format: Format,
151 },
152}
153
154impl Slot {
155 /// Where its bytes start in the object.
156 #[must_use]
157 pub const fn offset(self) -> u64 {
158 match self {
159 Self::Integer { offset, .. } | Self::Float { offset, .. } => offset,
160 }
161 }
162}
163
164/// How one value travels.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub enum Pass {
167 /// Nothing travels, which is `void` and an aggregate of no size.
168 Ignore,
169 /// The value itself. Every scalar is this.
170 Direct,
171 /// The object's bytes, in these slots, which is what passing an aggregate in registers
172 /// means once the object has been taken apart.
173 Pieces(Vec<Slot>),
174 /// The address of a copy, in the place the value would have gone.
175 ///
176 /// For an argument the caller makes the copy, and for a return value the caller passes the
177 /// address of somewhere to put it, which is what the hidden first argument is.
178 Reference,
179 /// The object's own bytes, in the argument area, with no address anywhere.
180 ///
181 /// This is SysV's MEMORY class and AAPCS's aggregate that ran out of registers. It is never
182 /// a return value: a return value that does not fit in registers is [`Pass::Reference`].
183 Memory,
184}
185
186/// Which psABI a target follows.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188enum Convention {
189 /// SysV AMD64, which is x86-64 everywhere but Windows.
190 Sysv,
191 /// AAPCS64, and Apple's variant of it.
192 Aapcs,
193 /// Windows x64.
194 Win64,
195 /// The RISC-V LP64D psABI.
196 Riscv,
197}
198
199/// The registers one call has left.
200///
201/// Made by [`TargetInfo::call`], asked about the return value first and then about each
202/// argument in order. Asking out of order gives an answer for a different program.
203#[derive(Debug)]
204pub struct Call {
205 /// Which psABI to follow.
206 convention: Convention,
207 /// General purpose argument registers left. On Windows x64 this is the argument positions
208 /// left, since there the two kinds of register share them.
209 gp: u32,
210 /// Floating point argument registers left.
211 fp: u32,
212}
213
214impl TargetInfo {
215 /// The start of one call, with every argument register still to spend.
216 #[must_use]
217 pub fn call(&self) -> Call {
218 let convention = match (self.triple.arch, self.triple.os) {
219 (Arch::X86_64, Os::Windows) => Convention::Win64,
220 (Arch::X86_64, _) => Convention::Sysv,
221 // AArch64 on Windows is not one of the five ABIs `spec/12-abi-and-runtime.md`
222 // implements for 1.0. It follows AAPCS64 here, which is most of what it does.
223 (Arch::Aarch64, _) => Convention::Aapcs,
224 (Arch::Riscv64, _) => Convention::Riscv,
225 };
226 let (gp, fp) = match convention {
227 Convention::Sysv => (6, 8),
228 Convention::Aapcs | Convention::Riscv => (8, 8),
229 // rcx, rdx, r8 and r9, which an integer and a floating point argument share: the
230 // fourth argument is in r9 or in xmm3 and never in both.
231 Convention::Win64 => (4, 0),
232 };
233 Call { convention, gp, fp }
234 }
235}
236
237impl Call {
238 /// How the return value comes back, which is asked before anything else.
239 ///
240 /// A return value that comes back in memory takes an argument register with it on three of
241 /// these four ABIs, so asking about the arguments first gives the wrong answer for the last
242 /// one of them.
243 #[must_use]
244 pub fn returns(&mut self, arg: &Arg<'_>) -> Pass {
245 match self.convention {
246 Convention::Sysv => sysv::returns(self, arg),
247 Convention::Aapcs => aapcs::returns(self, arg),
248 Convention::Win64 => win64::returns(self, arg),
249 Convention::Riscv => riscv::returns(self, arg),
250 }
251 }
252
253 /// How the next argument travels, which spends whatever registers it takes.
254 #[must_use]
255 pub fn argument(&mut self, arg: &Arg<'_>) -> Pass {
256 match self.convention {
257 Convention::Sysv => sysv::argument(self, arg),
258 Convention::Aapcs => aapcs::argument(self, arg),
259 Convention::Win64 => win64::argument(self, arg),
260 Convention::Riscv => riscv::argument(self, arg),
261 }
262 }
263}
264
265/// An aggregate of this size as a run of integer registers, the last one holding what is left.
266fn integer_slots(size: u64) -> Vec<Slot> {
267 (0..size.div_ceil(8))
268 .map(|index| Slot::Integer {
269 offset: index * 8,
270 size: u32::try_from((size - index * 8).min(8)).unwrap_or(8),
271 })
272 .collect()
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use crate::Triple;
279
280 /// The target with this triple.
281 pub(super) fn target(triple: &str) -> TargetInfo {
282 TargetInfo::new(triple.parse::<Triple>().expect("a triple the compiler supports"))
283 }
284
285 /// An integer scalar of this size, aligned to itself.
286 pub(super) fn int(size: u64) -> Scalar {
287 Scalar { kind: Kind::Integer, size, align: size }
288 }
289
290 /// A floating point scalar in this format, aligned to itself.
291 pub(super) fn float(format: Format, size: u64) -> Scalar {
292 Scalar { kind: Kind::Float(format), size, align: size }
293 }
294
295 /// An integer register holding this many bytes from this offset.
296 pub(super) const fn gpr(offset: u64, size: u32) -> Slot {
297 Slot::Integer { offset, size }
298 }
299
300 /// A vector register holding this format from this offset.
301 pub(super) const fn fpr(offset: u64, format: Format) -> Slot {
302 Slot::Float { offset, format }
303 }
304
305 /// The pieces of a record whose members are these, each at the next offset it fits.
306 pub(super) fn packed(scalars: &[Scalar]) -> Vec<Piece> {
307 let mut pieces = Vec::new();
308 let mut at: u64 = 0;
309 for &scalar in scalars {
310 at = at.next_multiple_of(scalar.align.max(1));
311 pieces.push(Piece { offset: at, scalar });
312 at += scalar.size;
313 }
314 pieces
315 }
316
317 /// The shape of a record whose members are these, sized and aligned the way C would.
318 pub(super) fn record<'a>(pieces: &'a [Piece]) -> Shape<'a> {
319 let align = pieces.iter().map(|piece| piece.scalar.align).max().unwrap_or(1);
320 let size = pieces.iter().map(Piece::end).max().unwrap_or(0).next_multiple_of(align);
321 Shape { size, align, pieces, complex: false }
322 }
323
324 #[test]
325 fn the_last_register_of_an_aggregate_holds_only_what_is_left_of_it() {
326 assert_eq!(integer_slots(4), vec![gpr(0, 4)]);
327 assert_eq!(integer_slots(8), vec![gpr(0, 8)]);
328 assert_eq!(integer_slots(12), vec![gpr(0, 8), gpr(8, 4)]);
329 assert_eq!(integer_slots(16), vec![gpr(0, 8), gpr(8, 8)]);
330 }
331
332 #[test]
333 fn a_triple_picks_the_abi_and_not_the_architecture_alone() {
334 let mut linux = target("x86_64-unknown-linux-gnu").call();
335 let mut windows = target("x86_64-pc-windows-msvc").call();
336 let pieces = packed(&[int(8), int(8)]);
337 let shape = Arg::Aggregate(record(&pieces));
338 // Sixteen bytes is two registers on SysV and a hidden pointer on Windows, which is the
339 // whole reason this is data about the target rather than a rule about C.
340 assert_eq!(linux.argument(&shape), Pass::Pieces(vec![gpr(0, 8), gpr(8, 8)]));
341 assert_eq!(windows.argument(&shape), Pass::Reference);
342 }
343}