1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! The type layouts, against the facts the target documents state.
//!
//! Design: `spec/cross-compile/06-abis.md` section 6.2 items 1 to 3 and item 11.
//!
//! These are the disagreements that make a target's own headers unreadable, which is a different
//! and earlier failure than a miscompiled call. Almost every case here is a pair: the same C on
//! two targets with two answers, because a layout field is only interesting where it differs.
use rucc_abi::{BitfieldOrder, DataLayout, Format};
use rucc_base::float::Format as CompilerFormat;
use rucc_tuple::{TARGETS, TargetEntry, TargetTuple};
/// The layout of the target with this tuple.
fn layout(tuple: &str) -> DataLayout {
let target: TargetTuple = tuple.parse().expect("a row in the target table");
DataLayout::for_target(target)
}
#[test]
fn a_long_is_eight_bytes_on_linux_and_four_on_windows() {
// LLP64 against LP64, which is the field the old three field triple could not carry and the
// reason `spec/cross-compile/03-target-model.md` puts the data model in the tuple. A header that says
// `long` where it means a pointer sized integer compiles differently on these two.
assert_eq!(layout("x86_64-linux-gnu").long_size, 8);
assert_eq!(layout("x86_64-pc-windows-msvc").long_size, 4);
assert_eq!(layout("x86_64-pc-windows-gnu").long_size, 4);
// Pointers are eight bytes on all three, which is what makes the difference a trap rather
// than an obvious one.
for tuple in ["x86_64-linux-gnu", "x86_64-pc-windows-msvc", "x86_64-pc-windows-gnu"] {
assert_eq!(layout(tuple).pointer_size, 8);
}
}
#[test]
fn a_char_is_signed_on_x86_and_unsigned_on_aarch64_linux() {
// The classic one, from spec/cross-compile/06-abis.md section 6.2 item 3. A program that indexes an array
// with a `char` holding a byte over 127 works on one of these and not the other, and nothing
// in the program is wrong.
assert!(layout("x86_64-linux-gnu").char_is_signed);
assert!(!layout("aarch64-linux-gnu").char_is_signed);
// Darwin signs it on both architectures, so this is not an architecture fact either.
assert!(layout("aarch64-apple-darwin").char_is_signed);
assert!(layout("x86_64-apple-darwin").char_is_signed);
// s390x is unsigned, which the corpus in tamnd/rucc-cross caught and this file used to have
// backwards. `facts/s390x-linux-gnu.facts` records `char_signed=no`, from `__CHAR_UNSIGNED__`
// being defined by a cross compiler targeting it, and the ELF ABI supplement agrees.
assert!(!layout("s390x-linux-gnu").char_is_signed);
}
#[test]
fn mingw_and_msvc_are_the_same_operating_system_with_different_layouts() {
// Found by the corpus in tamnd/rucc-cross. The rule here used to key on the operating system,
// which handed every Windows target the MSVC answer, and mingw-w64 is not an MSVC target. It
// is GCC's answer with Microsoft's `long`, so it needs the environment field and not the OS.
//
// `facts/x86_64-windows-gnu.facts` records `long_double_format=x87_extended` with
// `sizeof_long_double=16`, and `facts/x86_64-windows-msvc.facts` records `double` with 8.
let mingw = layout("x86_64-pc-windows-gnu").long_double;
assert_eq!(mingw.format, Format::X87Extended);
assert_eq!((mingw.size, mingw.align), (16, 16));
assert!(!layout("x86_64-pc-windows-gnu").long_double_is_double());
assert!(layout("x86_64-pc-windows-msvc").long_double_is_double());
// Twelve bytes on i386 under either environment, aligned to four under both, which is worth an
// assertion because mingw does follow Microsoft on `double` and `long long` just below and the
// guess that it does the same here is wrong.
let mingw32 = layout("i686-pc-windows-gnu").long_double;
assert_eq!(mingw32.format, Format::X87Extended);
assert_eq!((mingw32.size, mingw32.align), (12, 4));
// The half mingw does take from Microsoft. `double` and `long long` are eight aligned on
// i686-windows-gnu and four aligned on i686-linux-gnu, so a struct holding either lays out
// differently on the two, and the System V i386 psABI only governs the second one.
let mingw32 = layout("i686-pc-windows-gnu");
assert_eq!(mingw32.long_long_align, 8);
assert_eq!(mingw32.double.align, 8);
assert_eq!(mingw32.max_field_align, None);
}
#[test]
fn s390x_caps_scalar_alignment_at_eight() {
// The other thing the corpus caught. The s390x ELF ABI caps alignment at eight, so a sixteen
// byte IEEE quad `long double` is eight aligned there and sixteen aligned on AArch64 Linux
// with the same format and the same width. `__int128` goes the same way, which is what the
// cap on member alignment is for.
let s390x = layout("s390x-linux-gnu").long_double;
assert_eq!(s390x.format, Format::Quad);
assert_eq!((s390x.size, s390x.align), (16, 8));
assert_eq!(layout("s390x-linux-gnu").max_field_align, Some(8));
let aarch64 = layout("aarch64-linux-gnu").long_double;
assert_eq!(aarch64.format, s390x.format);
assert_eq!(aarch64.size, s390x.size);
assert_ne!(aarch64.align, s390x.align);
}
#[test]
fn a_long_double_has_a_width_and_a_format_and_they_are_separate_facts() {
// Four targets, four answers, and two of them share a width while disagreeing about every
// bit in it. A description carrying only the width would call those two the same.
let linux = layout("x86_64-linux-gnu").long_double;
assert_eq!(linux.format, Format::X87Extended);
assert_eq!((linux.size, linux.align), (16, 16));
let windows = layout("x86_64-pc-windows-msvc").long_double;
assert_eq!(windows.format, Format::Double);
assert_eq!((windows.size, windows.align), (8, 8));
let aarch64 = layout("aarch64-linux-gnu").long_double;
assert_eq!(aarch64.format, Format::Quad);
assert_eq!((aarch64.size, aarch64.align), (16, 16));
// Same sixteen bytes as AArch64 Linux, and not one bit means the same thing, because
// double-double is a pair of `double`s whose sum is the value rather than a wide binary
// float.
let powerpc = layout("powerpc64le-linux-gnu").long_double;
assert_eq!(powerpc.format, Format::DoubleDouble);
assert_eq!((powerpc.size, powerpc.align), (16, 16));
}
/// A function over the compiler's own floating point format.
///
/// It takes a [`Format`] from this crate only while the two are one type, which is what the test
/// below is: a compile time assertion with a runtime one after it so that it reads as a test.
fn width_of(format: CompilerFormat) -> u32 {
format.width()
}
#[test]
fn the_format_here_is_the_compilers_own_and_not_a_copy_of_it() {
// These were two identical enums until the double-double went into one of them, which is the
// shape of the drift: the copies agree until a format is added to the one that describes
// targets and not to the one that holds constants, and then a `long double` on the new target
// is folded as something else without anything failing to build.
assert_eq!(width_of(Format::DoubleDouble), 128);
assert_eq!(width_of(layout("powerpc64le-linux-gnu").long_double.format), 128);
assert!(!Format::DoubleDouble.is_ieee());
assert!(Format::Quad.is_ieee());
}
#[test]
fn a_wide_character_is_two_bytes_on_windows_and_four_everywhere_else() {
// What a wide string literal is encoded in. Two bytes means UTF-16 and a character outside the
// basic plane takes two elements, four means UTF-32 and nothing takes more than one, so a
// program that indexes a wide literal counts different things on the two.
assert_eq!(layout("x86_64-pc-windows-msvc").wchar_size, 2);
assert_eq!(layout("aarch64-pc-windows-msvc").wchar_size, 2);
assert_eq!(layout("i686-pc-windows-gnu").wchar_size, 2);
for tuple in ["x86_64-linux-gnu", "aarch64-linux-gnu", "aarch64-apple-darwin"] {
assert_eq!(layout(tuple).wchar_size, 4, "{tuple}");
}
}
#[test]
fn a_wide_characters_signedness_does_not_follow_a_plain_chars() {
// Three pairs, and no rule over `char` gets all three right. Windows signs `char` and not
// `wchar_t`, AArch64 FreeBSD makes both unsigned, and AArch64 NetBSD makes `char` unsigned and
// `wchar_t` signed. The facts in tamnd/rucc-cross record all six.
let windows = layout("x86_64-pc-windows-msvc");
assert!(windows.char_is_signed);
assert!(!windows.wchar_is_signed);
let freebsd = layout("aarch64-unknown-freebsd");
assert!(!freebsd.char_is_signed);
assert!(!freebsd.wchar_is_signed);
let netbsd = layout("aarch64-unknown-netbsd");
assert!(!netbsd.char_is_signed);
assert!(netbsd.wchar_is_signed);
// The pair that catches a corpus out, the same way the plain `char` one does: AArch64 Linux
// makes it unsigned and AArch64 Darwin does not, so `L'\xffffffff'` is four billion on one and
// minus one on the other.
assert!(!layout("aarch64-linux-gnu").wchar_is_signed);
assert!(layout("aarch64-apple-darwin").wchar_is_signed);
assert!(layout("x86_64-linux-gnu").wchar_is_signed);
assert!(layout("riscv64-linux-gnu").wchar_is_signed);
}
#[test]
fn darwin_makes_a_long_double_a_double_and_linux_does_not() {
// spec/cross-compile/06-abis.md section 6.3's third divergence. `%Lf` disagrees, `LDBL_MAX` is wrong, and
// the `l` suffixed math functions resolve to differently named symbols, all from this field.
assert!(layout("aarch64-apple-darwin").long_double_is_double());
assert!(!layout("aarch64-linux-gnu").long_double_is_double());
assert!(layout("aarch64-pc-windows-msvc").long_double_is_double());
// Darwin, and not on x86-64. Apple made the change when it moved to AArch64 and left the Intel
// answer alone, so `x86_64-macos` still has an eighty bit `long double` in sixteen bytes, which
// `facts/x86_64-macos.facts` records as `long_double_format=x87_extended`. A rule keyed on the
// operating system alone gets this one wrong in the direction nobody checks.
assert!(!layout("x86_64-apple-darwin").long_double_is_double());
assert_eq!(layout("x86_64-apple-darwin").long_double.size, 16);
}
#[test]
fn system_v_i386_aligns_an_eight_byte_type_to_four() {
// Section 6.2 item 2's example of a layout rule that does not follow from the member sizes.
// `struct { char c; long long v; }` is twelve bytes here and sixteen on x86-64, and both are
// correct C. It is the psABI and not the architecture that says so, which is why mingw on the
// same architecture answers eight and has a test of its own above.
let i386 = layout("i686-linux-gnu");
assert_eq!((i386.long_long_size, i386.long_long_align), (8, 4));
assert_eq!(i386.double.align, 4);
assert_eq!(
i386.long_double,
rucc_abi::FloatType { format: Format::X87Extended, size: 12, align: 4 }
);
// The cap on member alignment goes with it, so a sixteen byte aligned type inside a struct is
// aligned to four here and to sixteen on x86-64.
assert_eq!(i386.max_field_align, Some(4));
let amd64 = layout("x86_64-linux-gnu");
assert_eq!((amd64.long_long_size, amd64.long_long_align), (8, 8));
assert_eq!(amd64.double.align, 8);
assert_eq!(amd64.max_field_align, None);
}
#[test]
fn a_symbol_gets_a_leading_underscore_on_darwin_and_not_on_elf() {
// Section 6.2 item 11. Getting it wrong produces a link error rather than a wrong answer,
// which makes it the least dangerous field here and the one most likely to be forgotten.
assert!(layout("x86_64-apple-darwin").leading_underscore);
assert!(layout("aarch64-apple-darwin").leading_underscore);
assert!(!layout("x86_64-linux-gnu").leading_underscore);
assert!(!layout("x86_64-pc-windows-msvc").leading_underscore);
}
#[test]
fn a_bitfield_starts_at_the_end_the_endianness_says() {
assert_eq!(layout("x86_64-linux-gnu").bitfield_order, BitfieldOrder::LowestFirst);
assert_eq!(layout("aarch64-linux-gnu").bitfield_order, BitfieldOrder::LowestFirst);
assert_eq!(layout("s390x-linux-gnu").bitfield_order, BitfieldOrder::HighestFirst);
}
#[test]
fn every_target_in_the_table_has_a_layout_that_makes_sense() {
for entry in TARGETS {
let target = TargetEntry::parse(entry).expect("the table parses");
let layout = DataLayout::for_target(target);
let tuple = entry.tuple;
// Nothing here is a C rule. These are the ones every psABI on the list happens to agree
// on, and a row that broke one of them would be a row worth arguing about rather than one
// to quietly accommodate.
assert_eq!(layout.short_size, 2, "{tuple} has a short that is not two bytes");
assert!(layout.int_size >= layout.short_size, "{tuple} has an int narrower than a short");
assert!(layout.long_size >= layout.int_size, "{tuple} has a long narrower than an int");
assert!(
layout.long_long_size >= layout.long_size,
"{tuple} has a long long narrower than a long"
);
assert!(
layout.long_double.size >= layout.double.size,
"{tuple} has a long double narrower than a double"
);
assert_eq!(
layout.pointer_size, layout.pointer_align,
"{tuple} has a pointer aligned to something other than its size"
);
assert!(
layout.wchar_size == 2 || layout.wchar_size == 4,
"{tuple} has a wchar_t of {} bytes, which is neither UTF-16 nor UTF-32",
layout.wchar_size
);
// An alignment that is not a power of two is a layout nobody can implement.
for align in [
layout.long_long_align,
layout.pointer_align,
layout.float.align,
layout.double.align,
layout.long_double.align,
] {
assert!(align.is_power_of_two(), "{tuple} aligns something to {align}");
}
if let Some(cap) = layout.max_field_align {
assert!(cap.is_power_of_two(), "{tuple} caps member alignment at {cap}");
}
}
}