Skip to main content

rucc_abi/
layout.rs

1//! Type sizes, alignments and signedness, which is the half of a psABI that decides whether a
2//! header is read correctly.
3//!
4//! Design: `spec/cross-compile/06-abis.md` section 6.2 items 1 to 3.
5//!
6//! Section 6.2 splits a psABI into twelve independent decisions and then splits those into two
7//! groups. Items 4 to 9 decide whether a call works, and the differential harness of
8//! `spec/cross-compile/14-testing.md` is what tests them. Items 1 to 3 plus item 11 decide whether a *header*
9//! is interpreted correctly, and those are this file.
10//!
11//! The second group is cheaper to test and catches more, which is why `spec/cross-compile/06-abis.md` section
12//! 6.8 puts the `_Static_assert` corpus first for every new ABI: it costs nothing to run, needs
13//! no target machine and no execution, and it catches every layout disagreement there is. The
14//! corpus is generated from this description, which is why the description comes first.
15//!
16//! # The two traps
17//!
18//! A `long double`'s width and its format are separate facts. Sixteen bytes on x86-64 Linux of
19//! which eighty bits are the value, eight bytes and a plain `double` on Darwin and under MSVC,
20//! IEEE binary128 on AArch64 Linux and s390x and RISC-V, and IBM double-double on legacy
21//! 64-bit PowerPC. A description that carried only the width would call three of those the same.
22//!
23//! Windows is two answers and not one. mingw-w64 keeps GCC's eighty bit `long double` and MSVC
24//! makes it a `double`, so the rule reads the environment and not the operating system.
25//!
26//! `char`'s signedness is a target fact and not a C fact. Unsigned on AArch64 Linux, on ARM, on
27//! PowerPC and on s390x, signed on x86 and on Darwin. A program that indexes an array with a
28//! `char` holding a byte over 127 works on one and not the other, and nothing in it is wrong.
29
30use rucc_tuple::{Arch, DataModel, Env, Os, TargetTuple};
31
32use crate::shape::Format;
33
34/// A floating point type, as the two separate facts it is.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct FloatType {
37    /// How the bits mean a number.
38    pub format: Format,
39    /// How many bytes it takes in memory, which is not the format's width.
40    pub size: u64,
41    /// What it is aligned to, in bytes, which is not always its size: an x87 `long double` is
42    /// twelve bytes aligned to four on i386 and sixteen aligned to sixteen on x86-64, and it is
43    /// the same eighty bits of value both times.
44    pub align: u64,
45}
46
47/// How a bit-field is allocated within its storage unit.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum BitfieldOrder {
50    /// The first field declared gets the low order bits, which is every little-endian target
51    /// here.
52    LowestFirst,
53    /// The first field declared gets the high order bits, which is s390x and every other
54    /// big-endian ELF target.
55    HighestFirst,
56}
57
58/// The type sizes and alignments a target's headers were written against.
59///
60/// Everything here is a fact the psABI states and the compiler has to agree with. None of it is
61/// something C decides, which is the point: two compilers can both implement C correctly and
62/// disagree about every field in this struct, and the one that disagrees with the target's
63/// headers is the one that is wrong.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct DataLayout {
66    /// Whether a plain `char` is signed.
67    pub char_is_signed: bool,
68    /// The width of a `short` in bytes, which is two everywhere on the target list.
69    pub short_size: u64,
70    /// The width of an `int` in bytes.
71    pub int_size: u64,
72    /// The width of a `long` in bytes, which is the field the old three field triple got wrong
73    /// for every 64-bit Windows target.
74    pub long_size: u64,
75    /// The width of a `long long` in bytes.
76    pub long_long_size: u64,
77    /// What a `long long` is aligned to.
78    ///
79    /// Four on System V i386, where it is eight bytes aligned to four, which is
80    /// `spec/cross-compile/06-abis.md` section 6.2 item 2's example of a layout rule that is not
81    /// derivable from the member alignments. Eight under mingw on the same architecture.
82    pub long_long_align: u64,
83    /// The width of a pointer in bytes.
84    pub pointer_size: u64,
85    /// What a pointer is aligned to.
86    pub pointer_align: u64,
87    /// `float`.
88    pub float: FloatType,
89    /// `double`.
90    pub double: FloatType,
91    /// `long double`, which is the one that differs across almost every target.
92    pub long_double: FloatType,
93    /// The width of a `wchar_t` in bytes, which decides what a wide string literal is encoded in.
94    ///
95    /// Two on Windows, so a wide string there is UTF-16 and a character outside the basic plane
96    /// takes two elements, and four everywhere else, where it is UTF-32 and no character takes
97    /// more than one. It is the operating system's answer and not the architecture's, which
98    /// `facts/aarch64-windows-msvc.facts` and `facts/aarch64-linux-gnu.facts` show as a pair.
99    pub wchar_size: u64,
100    /// Whether a `wchar_t` is signed.
101    ///
102    /// A separate fact from [`DataLayout::char_is_signed`] and not derivable from it. AArch64
103    /// FreeBSD makes both unsigned, AArch64 NetBSD makes `char` unsigned and `wchar_t` signed, and
104    /// Windows makes `char` signed and `wchar_t` unsigned, so no rule over one of them answers the
105    /// other. `L'\xffffffff'` is minus one where this is true and four billion where it is false.
106    pub wchar_is_signed: bool,
107    /// Whether a symbol gets a leading underscore, which is section 6.2 item 11.
108    pub leading_underscore: bool,
109    /// Which end of a storage unit a bit-field starts at.
110    pub bitfield_order: BitfieldOrder,
111    /// The largest alignment the ABI will give a struct member on its own, in bytes, and [`None`]
112    /// where there is no cap.
113    ///
114    /// Four on System V i386 and eight on s390x. A cap is invisible until a program uses a
115    /// sixteen byte type inside a struct, and then it is a layout difference rather than an
116    /// error.
117    pub max_field_align: Option<u64>,
118    /// Whether the target has `__int128`.
119    ///
120    /// It is a fact about the architecture and not about the pointer width, which is the trap:
121    /// `x86_64-linux-gnux32` has four byte pointers and the type, and `i686-linux-gnu` has four
122    /// byte pointers and not the type. Both references provide it wherever the machine has sixty
123    /// four bit registers to hold half of it in, and refuse it everywhere else.
124    pub has_int128: bool,
125}
126
127impl DataLayout {
128    /// The layout for this target.
129    ///
130    /// Every field is derived from the tuple rather than from the host, which is
131    /// `spec/cross-compile/08-sysroots.md` section 8.5's rule applied to types instead of to directories, and
132    /// it is what makes `spec/cross-compile/02-the-goal.md` claim 5 checkable: two hosts asking about the same
133    /// target get the same answer because there is nothing in here for the host to influence.
134    #[must_use]
135    pub fn for_target(target: TargetTuple) -> Self {
136        let model = target.data_model();
137        let pointer = u64::from(target.pointer_width()) / 8;
138        Self {
139            char_is_signed: target.char_is_signed(),
140            short_size: 2,
141            int_size: u64::from(model.int_width()) / 8,
142            long_size: u64::from(model.long_width()) / 8,
143            long_long_size: u64::from(model.long_long_width()) / 8,
144            long_long_align: long_long_align(target),
145            pointer_size: pointer,
146            pointer_align: pointer,
147            float: FloatType { format: Format::Single, size: 4, align: 4 },
148            double: FloatType { format: Format::Double, size: 8, align: double_align(target) },
149            long_double: long_double(target),
150            wchar_size: match target.os() {
151                Os::Windows => 2,
152                _ => 4,
153            },
154            wchar_is_signed: wchar_is_signed(target),
155            leading_underscore: target.leading_underscore(),
156            bitfield_order: match target.is_little_endian() {
157                true => BitfieldOrder::LowestFirst,
158                false => BitfieldOrder::HighestFirst,
159            },
160            max_field_align: max_field_align(target),
161            has_int128: has_int128(target),
162        }
163    }
164
165    /// Whether a `long double` is really a `double`, which is true on Darwin, under MSVC and on
166    /// every 32-bit ARM target, and which decides whether `%Lf` and `LDBL_MAX` and the `l`
167    /// suffixed math functions mean anything different from their unsuffixed forms.
168    #[must_use]
169    pub const fn long_double_is_double(&self) -> bool {
170        matches!(self.long_double.format, Format::Double)
171    }
172}
173
174/// What a `long long` is aligned to.
175fn long_long_align(target: TargetTuple) -> u64 {
176    // System V i386 is the only target on the list where an eight byte type is aligned to four,
177    // and it is the reason a struct holding one lays out differently there than the member sizes
178    // suggest. mingw is not that target. It follows Microsoft here and aligns to eight, so the
179    // arm has to name the operating system as well as the architecture.
180    match (target.arch(), target.data_model()) {
181        (Arch::X86, DataModel::Ilp32) if target.os() != Os::Windows => 4,
182        _ => 8,
183    }
184}
185
186/// What a `double` is aligned to.
187fn double_align(target: TargetTuple) -> u64 {
188    match (target.arch(), target.data_model()) {
189        // Same rule as `long long`, the same reason, and the same mingw exception. A `double`
190        // inside a struct on System V i386 sits at a four byte boundary, which is why an i386
191        // struct is often smaller than the same declaration on any other target.
192        (Arch::X86, DataModel::Ilp32) if target.os() != Os::Windows => 4,
193        _ => 8,
194    }
195}
196
197/// The `long double` of this target, as a format and a width, which are separate facts.
198fn long_double(target: TargetTuple) -> FloatType {
199    let double = FloatType { format: Format::Double, size: 8, align: 8 };
200    let quad = FloatType { format: Format::Quad, size: 16, align: 16 };
201    match target.arch() {
202        // Eighty bits of value in twelve bytes on i386 and sixteen on x86-64, aligned to its
203        // storage size both times, and the same format underneath.
204        //
205        // MSVC is the exception and it is handled first, because there a `long double` is a
206        // `double` and the x87 format never appears in an interface. It is the environment and
207        // not the operating system that decides this. mingw-w64 keeps the GCC answer, so
208        // `x86_64-windows-gnu` has an eighty bit `long double` in sixteen bytes and
209        // `x86_64-windows-msvc` has a `double` in eight, which is the same OS with two answers
210        // and the reason this arm cannot be written as `os() == Windows`.
211        Arch::X86_64 | Arch::X86 if target.env() == Env::Msvc => double,
212        Arch::X86_64 => FloatType { format: Format::X87Extended, size: 16, align: 16 },
213        // Four on i386, under mingw as well as under System V, which is worth stating because
214        // mingw does follow Microsoft on the alignment of `double` and `long long` and the guess
215        // that it does the same here is wrong.
216        Arch::X86 => FloatType { format: Format::X87Extended, size: 12, align: 4 },
217        // Darwin's third divergence, `spec/cross-compile/06-abis.md` section 6.3. `%Lf` disagrees, `LDBL_MAX`
218        // is wrong, and a math library call resolves to a differently named symbol, all from one
219        // field.
220        Arch::Aarch64 if target.os().is_darwin() => double,
221        Arch::Aarch64 if target.os() == Os::Windows => double,
222        // Sixteen bytes of IEEE quad aligned to eight, because the s390x ELF ABI caps scalar
223        // alignment at eight and a `long double` is the widest scalar it has. Same format as the
224        // arm below and a different alignment, which is why it is a row of its own.
225        Arch::S390x => FloatType { format: Format::Quad, size: 16, align: 8 },
226        // wasm32 is in here and not in with the other thirty two bit machines, which is the
227        // surprise. It has four byte pointers and a sixteen byte IEEE quad `long double` aligned
228        // to sixteen, the same way it has four byte pointers and an `__int128`: the width of an
229        // address on that machine says nothing about the widest type, because there is no register
230        // file for either of them to be a fact about. `facts/wasm32-none.facts` recorded quad and
231        // sixteen from the start and this arm said `double` anyway, which the generated record
232        // corpus caught the first time it was written for the row.
233        // RISC-V 32 is in here for the same reason and it is not in the table, so it was measured
234        // by running `zig cc -target riscv32-linux-musl` rather than assumed from the width: the
235        // RISC-V psABI gives both ILP32 and LP64 a binary128 `long double`, and that machine has
236        // a sixteen byte float and no `__int128` at the same time.
237        Arch::Aarch64 | Arch::Riscv64 | Arch::LoongArch64 | Arch::Wasm32 | Arch::Riscv32 => quad,
238        // Thirty two bit ARM has never had anything wider than a `double` for it, which the four
239        // ARM rows of the table agree about.
240        Arch::Arm => double,
241        // ELFv2 keeps IBM double-double, a pair of `double`s whose sum is the value, which is
242        // not an IEEE format at all and is the reason a `long double` there cannot be treated as
243        // a wide binary float.
244        Arch::PowerPc64 => FloatType { format: Format::DoubleDouble, size: 16, align: 16 },
245        Arch::Arm64Ec => double,
246    }
247}
248
249/// Whether a `wchar_t` is signed.
250///
251/// Three rules over the facts in tamnd/rucc-cross rather than a guess from the architecture. The
252/// ARM family is the only one that makes it unsigned, and two operating systems override that:
253/// Windows makes it an `unsigned short` on every architecture, and Darwin and NetBSD both make it
254/// a plain `int`. `facts/aarch64-freebsd.facts` and `facts/aarch64-netbsd.facts` are the same
255/// architecture with opposite answers, which is why the operating system has to be read here.
256fn wchar_is_signed(target: TargetTuple) -> bool {
257    match target.os() {
258        // A `wchar_t` on Windows is an `unsigned short`, which is also why it is two bytes.
259        Os::Windows => false,
260        // Apple kept the Intel answer on AArch64, the same way it kept plain `char` signed, and
261        // NetBSD makes it an `int` everywhere on purpose.
262        Os::MacOs | Os::IOs | Os::NetBsd => true,
263        _ => !matches!(target.arch(), Arch::Arm | Arch::Aarch64 | Arch::Arm64Ec),
264    }
265}
266
267/// The largest alignment the ABI gives a member on its own.
268fn max_field_align(target: TargetTuple) -> Option<u64> {
269    match (target.arch(), target.data_model()) {
270        // i386 Linux caps member alignment at four, so a sixteen byte aligned type inside a
271        // struct is aligned to four there and to sixteen everywhere else.
272        (Arch::X86, DataModel::Ilp32) if target.os() != Os::Windows => Some(4),
273        // s390x caps at eight, which is why its `long double` and its `__int128` are both sixteen
274        // bytes aligned to eight. Two architectures with a cap and two different caps, which is
275        // the argument for this being a number rather than a boolean.
276        (Arch::S390x, _) => Some(8),
277        _ => None,
278    }
279}
280
281/// Whether the target has `__int128`.
282fn has_int128(target: TargetTuple) -> bool {
283    match target.arch() {
284        // Sixty four bit registers, so the type is a pair of them and both references have it.
285        // x32 is on this side of the line with its four byte pointers, which is the reason the
286        // question is asked of the architecture rather than of the data model.
287        Arch::X86_64
288        | Arch::Aarch64
289        | Arch::Arm64Ec
290        | Arch::Riscv64
291        | Arch::LoongArch64
292        | Arch::PowerPc64
293        | Arch::S390x => true,
294        // wasm32 has it too, which is the second half of the same point: the machine's values are
295        // sixty four bits wide and its addresses are thirty two.
296        Arch::Wasm32 => true,
297        Arch::X86 | Arch::Arm | Arch::Riscv32 => false,
298    }
299}