Skip to main content

cinrs_core/
target.rs

1//! The target data model, and the triples it is derived from.
2//!
3//! C's arithmetic is defined in terms of the widths and the signedness of the
4//! implementation's types: whether `-1 < 1u` holds depends on how wide `int`
5//! is, `unsigned char a = 200, b = 100; a + b` is 300 rather than 44 only
6//! because `int` is wider than `char`, and `'\xff'` is `-1` exactly when plain
7//! `char` is signed. Sema therefore needs a concrete model of the machine
8//! before it can type a single expression.
9//!
10//! # Where the model comes from
11//!
12//! A procedural macro cannot ask `rustc` what it is compiling for: `--target`
13//! is not part of a macro's world, and `CARGO_CFG_TARGET_*` belongs to build
14//! scripts. So the model is chosen, in this order:
15//!
16//! 1. `#pragma cinrs target "<triple>"`, written in the unit itself, which
17//!    wins over everything;
18//! 2. the **`CINRS_TARGET`** environment variable, which a crate sets from its
19//!    own build script —
20//!    `println!("cargo:rustc-env=CINRS_TARGET={}", std::env::var("TARGET").unwrap());`
21//!    — because `cargo:rustc-env` reaches the very `rustc` process that runs
22//!    the macro;
23//! 3. otherwise the machine this crate was compiled for, the *host*.
24//!
25//! [`TargetSource`] records which of the three it was, so that a diagnostic —
26//! above all the data-model assertion, which is what a wrong choice trips —
27//! can say which knob to turn.
28//!
29//! # The check that guards it
30//!
31//! Whichever way the model was chosen it may still be the wrong one: a cross
32//! build with no `CINRS_TARGET`, or one with the wrong triple in it. So every
33//! expansion **states the model it was translated for**, as a
34//! `const _: () = { assert!(…); };` block at the top of the unit's module: one
35//! assertion per width, over the `core::ffi` aliases, which follow the real
36//! target. See `codegen`'s `data_model_check`. A mismatch is therefore a
37//! failed compile-time assertion with the caret on the C, naming both the
38//! model cinrs used and how to change it, rather than a program that quietly
39//! computes the wrong thing.
40//!
41//! # The table
42//!
43//! [`TargetModel::from_triple`] is a table of architecture families crossed
44//! with operating-system families; `doc/c-status.md` prints it. Nothing in it
45//! is guesswork: it comes from the ABI document each architecture is defined
46//! by and from `core::ffi`'s own `cfg` cascade — because the generated code
47//! uses those aliases, a model that disagreed with them would fail its own
48//! assertion. In particular **the signedness of plain `char` follows
49//! `core::ffi::c_char`**, which is not the same as following the architecture:
50//! Windows and Apple's platforms make it signed whatever the machine is.
51//!
52//! A triple the table does not have is an error rather than a guess.
53
54use std::fmt;
55
56/// The architecture family a triple names.
57///
58/// Only the ones [`TargetModel::from_triple`] accepts are here. The variant
59/// decides the `__x86_64__`-style predefined macros, whether `__int128`
60/// exists, and — for Arm — the signedness of `wchar_t`.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62pub enum Arch {
63    /// 32-bit x86: `i386`, `i486`, `i586`, `i686`.
64    X86,
65    /// x86-64, the x32 ABI included.
66    X86_64,
67    /// 64-bit Arm.
68    Aarch64,
69    /// 32-bit Arm, `thumb*` included.
70    Arm,
71    /// 32-bit RISC-V.
72    Riscv32,
73    /// 64-bit RISC-V.
74    Riscv64,
75    /// 32-bit WebAssembly.
76    Wasm32,
77    /// 32-bit PowerPC.
78    PowerPc,
79    /// 64-bit PowerPC, big and little endian.
80    PowerPc64,
81    /// IBM z/Architecture.
82    S390x,
83    /// 32-bit MIPS.
84    Mips,
85    /// 64-bit MIPS, the n64 ABI.
86    Mips64,
87    /// 32-bit SPARC.
88    Sparc,
89    /// 64-bit SPARC.
90    Sparc64,
91    /// 64-bit LoongArch.
92    LoongArch64,
93}
94
95impl Arch {
96    /// The name used in diagnostics and in the documentation table.
97    pub fn as_str(self) -> &'static str {
98        match self {
99            Arch::X86 => "x86",
100            Arch::X86_64 => "x86_64",
101            Arch::Aarch64 => "aarch64",
102            Arch::Arm => "arm",
103            Arch::Riscv32 => "riscv32",
104            Arch::Riscv64 => "riscv64",
105            Arch::Wasm32 => "wasm32",
106            Arch::PowerPc => "powerpc",
107            Arch::PowerPc64 => "powerpc64",
108            Arch::S390x => "s390x",
109            Arch::Mips => "mips",
110            Arch::Mips64 => "mips64",
111            Arch::Sparc => "sparc",
112            Arch::Sparc64 => "sparc64",
113            Arch::LoongArch64 => "loongarch64",
114        }
115    }
116
117    /// The `__x86_64__`-style macros this architecture predefines, each with
118    /// the value it is given.
119    ///
120    /// Deliberately short. A program that tests for something not here sees a
121    /// `0` in an `#if`, which is what C written for an unfamiliar compiler
122    /// expects; a macro claimed wrongly sends it down a path built on an
123    /// extension this crate does not have.
124    pub fn macros(self) -> &'static [(&'static str, &'static str)] {
125        match self {
126            Arch::X86 => &[("__i386__", "1"), ("__i386", "1")],
127            // `__SSE__` and `__SSE2__` are here because *every* x86-64 target
128            // has SSE2: it is in the System V and the Microsoft ABI both, and
129            // `rustc`'s own baseline. Nothing above them is, and a procedural
130            // macro cannot see `-C target-feature` or `-C target-cpu`, so a
131            // program that tests `#ifdef __AVX2__` takes the baseline branch
132            // here — the run-time question is `__builtin_cpu_supports("avx2")`
133            // and the way to ask for the instructions is
134            // `__attribute__((target("avx2")))` (a `#pragma GCC target`
135            // defines the macros from there on, as GCC's does; see
136            // `Pp::sync_target_macros`). `__SSE_MATH__` says what a
137            // program really wants to know from the pair: floating-point
138            // arithmetic goes through SSE rather than through x87, so a
139            // `double` is a `double` and not an 80-bit temporary.
140            //
141            // 32-bit x86 gets neither. Rust's `i686-*` targets do enable SSE2,
142            // but `i586-*` does not and [`Arch`] does not tell the two apart,
143            // and claiming SSE2 where there is none would send a program down a
144            // path its processor cannot run.
145            //
146            // `__MMX__` is deliberately absent although GCC predefines it on
147            // x86-64: `core::arch` has no MMX and no `__m64`, so the branch it
148            // would open leads to a diagnostic rather than to code.
149            Arch::X86_64 => &[
150                ("__x86_64__", "1"),
151                ("__x86_64", "1"),
152                ("__amd64__", "1"),
153                ("__amd64", "1"),
154                ("__SSE__", "1"),
155                ("__SSE2__", "1"),
156                ("__SSE_MATH__", "1"),
157                ("__SSE2_MATH__", "1"),
158            ],
159            Arch::Aarch64 => &[("__aarch64__", "1")],
160            Arch::Arm => &[("__arm__", "1")],
161            Arch::Riscv32 => &[("__riscv", "1"), ("__riscv_xlen", "32")],
162            Arch::Riscv64 => &[("__riscv", "1"), ("__riscv_xlen", "64")],
163            Arch::Wasm32 => &[
164                ("__wasm", "1"),
165                ("__wasm__", "1"),
166                ("__wasm32", "1"),
167                ("__wasm32__", "1"),
168            ],
169            Arch::PowerPc => &[("__powerpc__", "1"), ("__PPC__", "1")],
170            Arch::PowerPc64 => &[
171                ("__powerpc__", "1"),
172                ("__powerpc64__", "1"),
173                ("__PPC__", "1"),
174                ("__PPC64__", "1"),
175            ],
176            Arch::S390x => &[("__s390__", "1"), ("__s390x__", "1")],
177            Arch::Mips => &[("__mips__", "1"), ("__mips", "32")],
178            Arch::Mips64 => &[("__mips__", "1"), ("__mips", "64")],
179            Arch::Sparc => &[("__sparc__", "1"), ("__sparc", "1")],
180            Arch::Sparc64 => &[
181                ("__sparc__", "1"),
182                ("__sparc", "1"),
183                ("__sparc64__", "1"),
184                ("__arch64__", "1"),
185            ],
186            Arch::LoongArch64 => &[("__loongarch__", "1"), ("__loongarch64", "1")],
187        }
188    }
189}
190
191/// The operating-system family a triple names.
192///
193/// This is what the bundled headers branch on — `errno`, the standard streams,
194/// `mbstate_t`, `struct tm`, `time_t`, `FILE` — through the `_WIN32` and
195/// `__APPLE__` macros the model predefines. A family whose library layout
196/// differs therefore has to be a variant here rather than a guess.
197#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
198pub enum Os {
199    /// Linux, whatever the C library: `gnu`, `musl`, `uclibc`, `android`.
200    Linux,
201    /// Apple's platforms: macOS, iOS, tvOS, watchOS, visionOS.
202    Darwin,
203    /// Windows, `msvc` and `gnu` alike.
204    Windows,
205    /// FreeBSD.
206    FreeBsd,
207    /// NetBSD.
208    NetBsd,
209    /// OpenBSD.
210    OpenBsd,
211    /// WASI.
212    Wasi,
213    /// No operating system at all: a `-none` triple, and
214    /// `wasm32-unknown-unknown`.
215    None,
216}
217
218impl Os {
219    /// The name used in diagnostics and in the documentation table.
220    pub fn as_str(self) -> &'static str {
221        match self {
222            Os::Linux => "linux",
223            Os::Darwin => "darwin",
224            Os::Windows => "windows",
225            Os::FreeBsd => "freebsd",
226            Os::NetBsd => "netbsd",
227            Os::OpenBsd => "openbsd",
228            Os::Wasi => "wasi",
229            Os::None => "none",
230        }
231    }
232
233    /// The `__linux__`-style macros this operating system predefines.
234    ///
235    /// `_WIN64` is not here: it follows the pointer width rather than the
236    /// system, so [`TargetModel::macros`] adds it.
237    pub fn macros(self) -> &'static [(&'static str, &'static str)] {
238        match self {
239            Os::Linux => &[
240                ("__linux__", "1"),
241                ("__linux", "1"),
242                ("__gnu_linux__", "1"),
243                ("__unix__", "1"),
244                ("__unix", "1"),
245            ],
246            Os::Darwin => &[
247                ("__APPLE__", "1"),
248                ("__MACH__", "1"),
249                ("__unix__", "1"),
250                ("__unix", "1"),
251            ],
252            Os::Windows => &[("_WIN32", "1")],
253            Os::FreeBsd => &[("__FreeBSD__", "1"), ("__unix__", "1"), ("__unix", "1")],
254            Os::NetBsd => &[("__NetBSD__", "1"), ("__unix__", "1"), ("__unix", "1")],
255            Os::OpenBsd => &[("__OpenBSD__", "1"), ("__unix__", "1"), ("__unix", "1")],
256            Os::Wasi => &[("__wasi__", "1")],
257            Os::None => &[],
258        }
259    }
260
261    /// Whether the object format is ELF, which is what `__ELF__` says.
262    fn is_elf(self) -> bool {
263        matches!(
264            self,
265            Os::Linux | Os::FreeBsd | Os::NetBsd | Os::OpenBsd | Os::None
266        )
267    }
268}
269
270/// The C library a triple's environment component names.
271///
272/// On most systems the [operating system](Os) settles the library — Apple has
273/// libSystem, the BSDs each have their own — and this is [`Env::None`]. Linux
274/// is the exception: `-gnu`, `-musl` and `-android` are three libraries with
275/// three sets of layouts behind the same system macros, and a header that laid
276/// a `mtx_t` out for the wrong one would corrupt memory. So the environment is
277/// kept, and [`TargetModel::macros`] turns the two this crate models into
278/// `__cinrs_glibc__` and `__cinrs_musl__` for the bundled headers to branch on.
279#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
280pub enum Env {
281    /// The GNU C Library, `-gnu*`: `gnu`, `gnueabihf`, `gnux32`, `gnullvm`.
282    Gnu,
283    /// musl, `-musl*`.
284    Musl,
285    /// Android's bionic, which a triple names `-android` or `-androideabi`.
286    Bionic,
287    /// uClibc, `-uclibc*`.
288    Uclibc,
289    /// The Microsoft runtime, `-msvc`.
290    Msvc,
291    /// The triple says nothing, because the system has only one C library
292    /// (Apple, the BSDs, WASI) or because there is none at all.
293    #[default]
294    None,
295}
296
297impl Env {
298    /// The name used in diagnostics.
299    pub fn as_str(self) -> &'static str {
300        match self {
301            Env::Gnu => "gnu",
302            Env::Musl => "musl",
303            Env::Bionic => "android",
304            Env::Uclibc => "uclibc",
305            Env::Msvc => "msvc",
306            Env::None => "",
307        }
308    }
309
310    /// The environment a triple's last component names.
311    ///
312    /// A Linux triple that names none — `x86_64-unknown-linux` — is glibc,
313    /// which is what `rustc` and `gcc` both take it for.
314    fn from_component(component: &str, os: Os) -> Self {
315        if component.starts_with("gnu") {
316            Env::Gnu
317        } else if component.starts_with("musl") {
318            Env::Musl
319        } else if component.starts_with("android") {
320            Env::Bionic
321        } else if component.starts_with("uclibc") {
322            Env::Uclibc
323        } else if component == "msvc" {
324            Env::Msvc
325        } else if os == Os::Linux {
326            Env::Gnu
327        } else {
328            Env::None
329        }
330    }
331}
332
333/// Where the [`TargetModel`] of an expansion came from.
334#[derive(Clone, Debug, PartialEq, Eq, Default)]
335pub enum TargetSource {
336    /// The machine the procedural macro itself was compiled for, because
337    /// nothing said otherwise.
338    #[default]
339    Host,
340    /// The `CINRS_TARGET` environment variable, holding this triple.
341    Env(String),
342    /// `#pragma cinrs target "…"`, holding this triple.
343    Pragma(String),
344    /// A [`crate::Options::target`] the caller set — a test, or a program
345    /// driving the front end directly.
346    Explicit,
347}
348
349impl TargetSource {
350    /// The triple this source named, where it named one.
351    pub fn triple(&self) -> Option<&str> {
352        match self {
353            TargetSource::Env(t) | TargetSource::Pragma(t) => Some(t),
354            TargetSource::Host | TargetSource::Explicit => None,
355        }
356    }
357
358    /// Where the model came from, as a diagnostic names it.
359    pub fn as_str(&self) -> &'static str {
360        match self {
361            TargetSource::Host => "the host",
362            TargetSource::Env(_) => "CINRS_TARGET",
363            TargetSource::Pragma(_) => "#pragma cinrs target",
364            TargetSource::Explicit => "the options given to the front end",
365        }
366    }
367
368    /// How the data-model assertion describes where the model came from.
369    ///
370    /// Reads after "cinrs translated this unit for …".
371    pub fn describe(&self) -> String {
372        match self {
373            TargetSource::Host => "the host, CINRS_TARGET being unset".to_owned(),
374            TargetSource::Env(t) => format!("CINRS_TARGET={t}"),
375            TargetSource::Pragma(t) => format!("#pragma cinrs target \"{t}\""),
376            TargetSource::Explicit => "the options given to the front end".to_owned(),
377        }
378    }
379}
380
381/// Why a triple could not be turned into a [`TargetModel`].
382#[derive(Clone, Debug, PartialEq, Eq)]
383pub struct UnknownTarget {
384    /// The triple as it was written.
385    pub triple: String,
386    /// What about it was not recognised, as a sentence.
387    detail: String,
388}
389
390impl UnknownTarget {
391    /// The diagnostic for a triple that came from `source`.
392    ///
393    /// The knob that named the triple leads, so that the reader knows what to
394    /// change before reading why; the fix follows the reason, and the list of
395    /// families comes last.
396    pub fn message(&self, source: &TargetSource) -> String {
397        let (from, fix) = match source {
398            TargetSource::Env(_) => (
399                "CINRS_TARGET: ",
400                "; set #pragma cinrs target or unset the variable",
401            ),
402            TargetSource::Pragma(_) => ("#pragma cinrs target: ", ""),
403            TargetSource::Host | TargetSource::Explicit => ("", ""),
404        };
405        format!("{from}{}{fix}; {FAMILIES}", self.detail)
406    }
407}
408
409impl fmt::Display for UnknownTarget {
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        write!(f, "{}; {FAMILIES}", self.detail)
412    }
413}
414
415/// One row of [`ARCHITECTURES`].
416struct ArchRow {
417    /// Matched against the start of the triple's architecture component.
418    prefix: &'static str,
419    arch: Arch,
420    /// The natural pointer width, which an ABI in the environment component
421    /// (`gnux32`, `gnu_ilp32`) may narrow.
422    ptr_bits: u32,
423    big_endian: bool,
424}
425
426impl ArchRow {
427    const fn new(prefix: &'static str, arch: Arch, ptr_bits: u32) -> Self {
428        Self {
429            prefix,
430            arch,
431            ptr_bits,
432            big_endian: false,
433        }
434    }
435
436    const fn big_endian(mut self) -> Self {
437        self.big_endian = true;
438        self
439    }
440}
441
442/// The architecture families, as [`TargetModel::from_triple`] matches them.
443///
444/// The first row whose `prefix` starts the architecture component wins, so a
445/// longer spelling of the same family — `powerpc64le` before `powerpc64`
446/// before `powerpc` — has to come first.
447const ARCHITECTURES: &[ArchRow] = &[
448    ArchRow::new("x86_64", Arch::X86_64, 64),
449    ArchRow::new("i386", Arch::X86, 32),
450    ArchRow::new("i486", Arch::X86, 32),
451    ArchRow::new("i586", Arch::X86, 32),
452    ArchRow::new("i686", Arch::X86, 32),
453    ArchRow::new("aarch64_be", Arch::Aarch64, 64).big_endian(),
454    ArchRow::new("aarch64", Arch::Aarch64, 64),
455    ArchRow::new("arm64", Arch::Aarch64, 64),
456    ArchRow::new("armeb", Arch::Arm, 32).big_endian(),
457    ArchRow::new("arm", Arch::Arm, 32),
458    ArchRow::new("thumb", Arch::Arm, 32),
459    ArchRow::new("riscv32", Arch::Riscv32, 32),
460    ArchRow::new("riscv64", Arch::Riscv64, 64),
461    ArchRow::new("wasm32", Arch::Wasm32, 32),
462    ArchRow::new("powerpc64le", Arch::PowerPc64, 64),
463    ArchRow::new("powerpc64", Arch::PowerPc64, 64).big_endian(),
464    ArchRow::new("powerpc", Arch::PowerPc, 32).big_endian(),
465    ArchRow::new("s390x", Arch::S390x, 64).big_endian(),
466    ArchRow::new("loongarch64", Arch::LoongArch64, 64),
467    ArchRow::new("mipsisa64r6el", Arch::Mips64, 64),
468    ArchRow::new("mipsisa64r6", Arch::Mips64, 64).big_endian(),
469    ArchRow::new("mipsisa32r6el", Arch::Mips, 32),
470    ArchRow::new("mipsisa32r6", Arch::Mips, 32).big_endian(),
471    ArchRow::new("mips64el", Arch::Mips64, 64),
472    ArchRow::new("mips64", Arch::Mips64, 64).big_endian(),
473    ArchRow::new("mipsel", Arch::Mips, 32),
474    ArchRow::new("mips", Arch::Mips, 32).big_endian(),
475    ArchRow::new("sparc64", Arch::Sparc64, 64).big_endian(),
476    ArchRow::new("sparcv9", Arch::Sparc64, 64).big_endian(),
477    ArchRow::new("sparc", Arch::Sparc, 32).big_endian(),
478];
479
480/// The architectures that are recognised only in order to be refused by name,
481/// with the reason. Each has a data model this crate does not implement.
482const EXOTIC: &[(&str, &str)] = &[
483    ("avr", "'int' is 16 bits and 'double' is 32"),
484    ("msp430", "'int' is 16 bits"),
485    ("xtensa", "cinrs has no model for it"),
486    ("hexagon", "cinrs has no model for it"),
487    ("csky", "cinrs has no model for it"),
488    ("m68k", "cinrs has no model for it"),
489    ("nvptx64", "cinrs has no model for it"),
490    ("bpfel", "cinrs has no model for it"),
491    ("bpfeb", "cinrs has no model for it"),
492    ("wasm64", "cinrs has no model for it"),
493];
494
495/// The operating systems, compared whole against every component of the triple
496/// after the architecture.
497const OPERATING_SYSTEMS: &[(&str, Os)] = &[
498    ("linux", Os::Linux),
499    ("android", Os::Linux),
500    ("androideabi", Os::Linux),
501    ("darwin", Os::Darwin),
502    ("macos", Os::Darwin),
503    ("macosx", Os::Darwin),
504    ("ios", Os::Darwin),
505    ("tvos", Os::Darwin),
506    ("watchos", Os::Darwin),
507    ("visionos", Os::Darwin),
508    ("windows", Os::Windows),
509    ("freebsd", Os::FreeBsd),
510    ("netbsd", Os::NetBsd),
511    ("openbsd", Os::OpenBsd),
512    ("wasi", Os::Wasi),
513    ("wasip1", Os::Wasi),
514    ("wasip2", Os::Wasi),
515    ("none", Os::None),
516    ("elf", Os::None),
517];
518
519/// The list every "unknown triple" diagnostic ends with.
520const FAMILIES: &str = "the architectures cinrs models are x86, x86_64, aarch64, arm/thumb, \
521                        riscv32, riscv64, wasm32, powerpc, powerpc64, s390x, mips, mips64, \
522                        sparc, sparc64 and loongarch64, on linux (android included), darwin, \
523                        windows, freebsd, netbsd, openbsd, wasi or none";
524
525/// Widths, signedness, endianness and identity of the machine the generated
526/// code runs on.
527///
528/// Everything the front end computes at expansion time — `sizeof`, `_Alignof`,
529/// member offsets, bit-field storage, the type of an integer constant, the
530/// value of an `#if`, the predefined macros and therefore the branch each
531/// bundled header takes — comes from here.
532#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533pub struct TargetModel {
534    /// The architecture family, which decides the `__x86_64__`-style macros.
535    pub arch: Arch,
536    /// The operating system, which decides the `__linux__`-style macros and so
537    /// the branch every bundled header takes.
538    pub os: Os,
539    /// The C library the triple's environment component names, which on Linux
540    /// is the difference between glibc's layouts and musl's; see [`Env`].
541    pub env: Env,
542    /// Whether plain `char` is a signed type.
543    ///
544    /// This follows `core::ffi::c_char` exactly, because the generated code
545    /// uses that alias: unsigned on AArch64, Arm, PowerPC, RISC-V and s390x —
546    /// *except* on Windows and on Apple's platforms, which make it signed
547    /// whatever the architecture — and signed everywhere else, LoongArch and
548    /// wasm32 included.
549    pub char_signed: bool,
550    /// Width of `short`.
551    pub short_bits: u32,
552    /// Width of `int`.
553    pub int_bits: u32,
554    /// Width of `long`: 64 on a 64-bit system that is not Windows (LP64), 32
555    /// otherwise (LLP64 and ILP32).
556    pub long_bits: u32,
557    /// Width of `long long`.
558    pub long_long_bits: u32,
559    /// Width of a pointer; also the width `size_t` and `ptrdiff_t` follow.
560    pub ptr_bits: u32,
561    /// The strictest alignment any scalar but `__int128` gets, **in bytes**.
562    ///
563    /// 4 on 32-bit x86 outside Windows and 8 everywhere else: the one place
564    /// where two targets of the same *data model* lay a `struct` out
565    /// differently, since the i386 System V ABI aligns `long long` and
566    /// `double` to four bytes and the Microsoft one to eight. `rustc` splits
567    /// the same way — `align_of::<u64>()` really is 4 on
568    /// `i686-unknown-linux-gnu` and 8 on `i686-pc-windows-msvc` — so a
569    /// `#[repr(C)]` item laid out with this is the one the compiler will
570    /// build.
571    pub max_scalar_align: u64,
572    /// Alignment of `__int128`, **in bytes**.
573    ///
574    /// The width is not a knob: GCC's `__int128` is 128 bits wherever it
575    /// exists at all. The alignment is, and it is the one place where the
576    /// generated Rust could disagree with the model, `__int128` becoming
577    /// `i128`, whose ABI Rust settled in 1.77.
578    pub int128_align: u64,
579    /// Whether `__int128` exists at all.
580    ///
581    /// GCC has it on the 64-bit architectures only and refuses it on a 32-bit
582    /// one rather than emulating it; so does this.
583    pub has_int128: bool,
584    /// Whether the byte order is big-endian.
585    ///
586    /// Only bit-fields can tell, and cinrs allocates them from the
587    /// least significant end, so a big-endian target refuses a bit-field
588    /// rather than laying it out the wrong way round.
589    pub big_endian: bool,
590    /// Width of `wchar_t`: 16 on Windows, 32 everywhere else.
591    pub wchar_bits: u32,
592    /// Whether `wchar_t` is signed: `int` on most systems, `unsigned int` on
593    /// Arm and AArch64 outside Apple's platforms, `unsigned short` on Windows.
594    pub wchar_signed: bool,
595    /// Width of `wint_t`: 16 on Windows, 32 everywhere else.
596    pub wint_bits: u32,
597    /// Whether `wint_t` is signed, which it is only on Apple's platforms,
598    /// where it is `int`.
599    pub wint_signed: bool,
600}
601
602impl TargetModel {
603    /// The LP64 model 64-bit Unix uses, as `x86_64-unknown-linux-gnu` has it:
604    /// 32-bit `int`, 64-bit `long` and pointers, signed `char`.
605    pub const LP64: Self = Self {
606        arch: Arch::X86_64,
607        os: Os::Linux,
608        env: Env::Gnu,
609        char_signed: true,
610        short_bits: 16,
611        int_bits: 32,
612        long_bits: 64,
613        long_long_bits: 64,
614        ptr_bits: 64,
615        max_scalar_align: 8,
616        int128_align: 16,
617        has_int128: true,
618        big_endian: false,
619        wchar_bits: 32,
620        wchar_signed: true,
621        wint_bits: 32,
622        wint_signed: false,
623    };
624
625    /// The ILP32 model 32-bit systems use, as `i686-unknown-linux-gnu` has it:
626    /// 32-bit `int`, `long` and pointers, `long long` and `double` aligned to
627    /// four bytes, and no `__int128`.
628    pub const ILP32: Self = Self {
629        arch: Arch::X86,
630        long_bits: 32,
631        ptr_bits: 32,
632        max_scalar_align: 4,
633        has_int128: false,
634        ..Self::LP64
635    };
636
637    /// The LLP64 model 64-bit Windows uses: 32-bit `int` and `long`, 64-bit
638    /// pointers, and a 16-bit `wchar_t`.
639    pub const LLP64: Self = Self {
640        os: Os::Windows,
641        env: Env::Msvc,
642        long_bits: 32,
643        wchar_bits: 16,
644        wchar_signed: false,
645        wint_bits: 16,
646        ..Self::LP64
647    };
648
649    /// The model of the machine this crate was compiled for.
650    ///
651    /// See the [module documentation](self) for when that is the right answer
652    /// and what happens when it is not.
653    pub const fn host() -> Self {
654        // Kept in step with `core::ffi::c_char`, whose own list this is —
655        // including the rule that Windows, Apple and the Vita make plain
656        // `char` signed whatever the architecture. The generated code uses
657        // that alias, so a disagreement here would be a failed data-model
658        // assertion on the host itself.
659        let char_signed = !(cfg!(any(
660            target_arch = "aarch64",
661            target_arch = "arm",
662            target_arch = "csky",
663            target_arch = "hexagon",
664            target_arch = "msp430",
665            target_arch = "powerpc",
666            target_arch = "powerpc64",
667            target_arch = "riscv32",
668            target_arch = "riscv64",
669            target_arch = "s390x",
670            target_arch = "xtensa",
671        )) && !cfg!(any(windows, target_vendor = "apple", target_os = "vita")));
672        let ptr_bits = if cfg!(target_pointer_width = "64") {
673            64
674        } else if cfg!(target_pointer_width = "32") {
675            32
676        } else {
677            16
678        };
679        Self {
680            arch: host_arch(),
681            os: host_os(),
682            env: host_env(),
683            char_signed,
684            short_bits: 16,
685            int_bits: if cfg!(any(target_arch = "avr", target_arch = "msp430")) {
686                16
687            } else {
688                32
689            },
690            // `long` is 64 bits on 64-bit Unix (LP64) and 32 everywhere else,
691            // 64-bit Windows (LLP64) included.
692            long_bits: if ptr_bits == 64 && !cfg!(windows) {
693                64
694            } else {
695                32
696            },
697            long_long_bits: 64,
698            ptr_bits,
699            max_scalar_align: if cfg!(all(target_arch = "x86", not(windows))) {
700                4
701            } else {
702                8
703            },
704            // Read off the compiling toolchain rather than guessed: `__int128`
705            // is generated as `i128`, so the alignment the layout code works
706            // with must be the one `rustc` will really give it.
707            int128_align: core::mem::align_of::<i128>() as u64,
708            has_int128: ptr_bits == 64,
709            big_endian: cfg!(target_endian = "big"),
710            wchar_bits: if cfg!(windows) { 16 } else { 32 },
711            wchar_signed: !(cfg!(windows)
712                || cfg!(all(
713                    any(target_arch = "aarch64", target_arch = "arm"),
714                    not(target_vendor = "apple")
715                ))),
716            wint_bits: if cfg!(windows) { 16 } else { 32 },
717            wint_signed: cfg!(target_vendor = "apple"),
718        }
719    }
720
721    /// The model of the machine `triple` names.
722    ///
723    /// The triple is a Rust one — `arch-vendor-os-env`, or `arch-os-env` where
724    /// the vendor is left out — read the way `rustc` writes them: the first
725    /// component is the architecture, and the operating system is whichever of
726    /// the rest names one. The environment matters only where it changes the
727    /// ABI: `gnux32` and `gnu_ilp32` narrow the pointer to 32 bits while
728    /// leaving the architecture 64-bit.
729    ///
730    /// # Errors
731    ///
732    /// An architecture or an operating system the table does not have, and the
733    /// handful recognised only to be refused because their data model is one
734    /// this crate does not implement — `avr`'s 16-bit `int`, say.
735    pub fn from_triple(triple: &str) -> Result<Self, UnknownTarget> {
736        let unknown = |detail: String| UnknownTarget {
737            triple: triple.to_owned(),
738            detail,
739        };
740        let mut parts = triple.split('-');
741        let Some(arch_name) = parts.next().filter(|a| !a.is_empty()) else {
742            return Err(unknown(format!("unknown target triple '{triple}'")));
743        };
744        let rest: Vec<&str> = parts.collect();
745        if let Some((name, why)) = EXOTIC.iter().find(|(name, _)| arch_name == *name) {
746            return Err(unknown(format!(
747                "the target triple '{triple}' is not supported: on '{name}' {why}"
748            )));
749        }
750        let Some(row) = ARCHITECTURES
751            .iter()
752            .find(|row| arch_name.starts_with(row.prefix))
753        else {
754            return Err(unknown(format!(
755                "unknown architecture '{arch_name}' in the target triple '{triple}'"
756            )));
757        };
758        // The operating system is whichever component after the architecture
759        // names one. `unknown` is a vendor as well as the placeholder Rust
760        // uses where there is no system at all, so a triple made of nothing
761        // but placeholders — `wasm32-unknown-unknown` — is freestanding.
762        let os = match rest
763            .iter()
764            .find_map(|c| OPERATING_SYSTEMS.iter().find(|(name, _)| c == name))
765        {
766            Some((_, os)) => *os,
767            None if rest.iter().all(|c| *c == "unknown") => Os::None,
768            None => {
769                let named = rest.last().copied().unwrap_or("");
770                return Err(unknown(format!(
771                    "unsupported operating system '{named}' in the target triple '{triple}'"
772                )));
773            }
774        };
775        let env = rest.last().copied().unwrap_or("");
776        // The two ABIs that keep a 64-bit architecture and narrow the pointer.
777        let ptr_bits = if env.starts_with("gnux32") || env.starts_with("gnu_ilp32") {
778            32
779        } else {
780            row.ptr_bits
781        };
782        let arch = row.arch;
783        let apple = os == Os::Darwin;
784        let windows = os == Os::Windows;
785        // `core::ffi::c_char`'s list, and its two overrides.
786        let char_signed = !(matches!(
787            arch,
788            Arch::Aarch64
789                | Arch::Arm
790                | Arch::PowerPc
791                | Arch::PowerPc64
792                | Arch::Riscv32
793                | Arch::Riscv64
794                | Arch::S390x
795        ) && !apple
796            && !windows);
797        let unsigned_wchar = matches!(arch, Arch::Aarch64 | Arch::Arm) && !apple;
798        Ok(Self {
799            arch,
800            os,
801            env: Env::from_component(env, os),
802            char_signed,
803            short_bits: 16,
804            int_bits: 32,
805            // LP64 unless Windows, which is LLP64 — plus the one oddity
806            // `core::ffi` also carries, a wasm32 Linux ABI with a 64-bit
807            // `long`.
808            long_bits: if (ptr_bits == 64 && !windows) || (arch == Arch::Wasm32 && os == Os::Linux)
809            {
810                64
811            } else {
812                32
813            },
814            long_long_bits: 64,
815            ptr_bits,
816            max_scalar_align: if arch == Arch::X86 && !windows { 4 } else { 8 },
817            int128_align: 16,
818            // GCC has `__int128` on the 64-bit architectures, x32 included,
819            // and refuses it on the 32-bit ones.
820            has_int128: row.ptr_bits == 64,
821            big_endian: row.big_endian,
822            wchar_bits: if windows { 16 } else { 32 },
823            wchar_signed: !windows && !unsigned_wchar,
824            wint_bits: if windows { 16 } else { 32 },
825            wint_signed: apple,
826        })
827    }
828
829    /// Whether the C library is the Microsoft one: an `-msvc` environment on
830    /// Windows.
831    ///
832    /// True of `*-windows-msvc` and `*-uwp-windows-msvc`, and of a host this
833    /// crate was itself compiled for with `target_env = "msvc"`. **Not** true of
834    /// mingw-w64 — `*-windows-gnu` and `*-windows-gnullvm` — which is Windows
835    /// with its own runtime libraries in front of the system's, so a rule that
836    /// holds for the Microsoft toolchain must not reach it.
837    ///
838    /// There are two such rules, both in `codegen`: the `printf` family, which
839    /// the UCRT defines inline rather than exporting, so a unit declaring one
840    /// links `legacy_stdio_definitions` (`LEGACY_STDIO`); and the names the UCRT
841    /// exports under another spelling, `time` as `_time64` and the rest, which a
842    /// declaration links by (`MSVC_RENAMED`). The *data model* is the same
843    /// either way, which is why nothing else here asks.
844    pub fn is_msvc(&self) -> bool {
845        self.os == Os::Windows && self.env == Env::Msvc
846    }
847
848    /// Whether the **platform's** `long double` is the IEEE double, eight
849    /// bytes, passed exactly as a `double` is.
850    ///
851    /// cinrs makes `long double` a `double` on every target, which is
852    /// self-consistent for the C a unit defines; this is the question the
853    /// *boundary* with the platform's C library asks. Where it is `true` a
854    /// declared-only function with a `long double` in its prototype takes the
855    /// `double` cinrs passes it. Where it is `false` — the x87 eighty-bit type
856    /// on x86-64 System V and on i386, the 128-bit quad of AArch64 Linux,
857    /// RISC-V, LoongArch, s390x and wasm, IBM's double-double on PowerPC glibc
858    /// — such a call reads the wrong register or the wrong number of bytes,
859    /// and `Sema` refuses it (see `sema::long_double`). The ISO C functions
860    /// with a `double` twin, `powl` and the rest, are linked to the twin on
861    /// every target and do not ask.
862    ///
863    /// The `true` rows are the ABIs whose documents say so: Microsoft's (every
864    /// architecture), AAPCS for 32-bit Arm, Apple's arm64, MIPS o32, AArch64
865    /// Windows under either runtime, 32-bit x86 Android, and PowerPC under
866    /// musl. Anything not listed counts as wide, which is the side a mistake
867    /// is loud on: a refusal rather than a wrong value.
868    pub fn platform_long_double_is_double(&self) -> bool {
869        match self.arch {
870            _ if self.is_msvc() => true,
871            Arch::Arm | Arch::Mips => true,
872            Arch::Aarch64 => matches!(self.os, Os::Darwin | Os::Windows),
873            Arch::X86 => self.env == Env::Bionic,
874            Arch::PowerPc | Arch::PowerPc64 => self.env == Env::Musl,
875            _ => false,
876        }
877    }
878
879    /// The name of the data model this is: `LP64`, `LLP64` or `ILP32`.
880    pub fn data_model(&self) -> &'static str {
881        match (self.int_bits, self.long_bits, self.ptr_bits) {
882            (32, 64, 64) => "LP64",
883            (32, 32, 64) => "LLP64",
884            (32, 32, 32) => "ILP32",
885            _ => "an unusual data model",
886        }
887    }
888
889    /// One line naming the model and the knob that chose it, for the
890    /// data-model assertion.
891    pub fn describe(&self, source: &TargetSource) -> String {
892        format!(
893            "{} ({}-{}, {} 'char', {}-bit 'wchar_t'), chosen from {}",
894            self.data_model(),
895            self.arch.as_str(),
896            self.os.as_str(),
897            if self.char_signed {
898                "signed"
899            } else {
900                "unsigned"
901            },
902            self.wchar_bits,
903            source.describe()
904        )
905    }
906
907    /// Every macro the target's *identity* predefines: the architecture, the
908    /// operating system, the object format.
909    ///
910    /// The data-model family — `__LP64__`, `__ILP32__`, `__CHAR_UNSIGNED__`,
911    /// the `__SIZEOF_*__` and `__*_MAX__` sets, `__BYTE_ORDER__` — is
912    /// arithmetic rather than identity, and the preprocessor builds it from
913    /// the widths above.
914    pub fn macros(&self) -> Vec<(&'static str, String)> {
915        let mut out: Vec<(&'static str, String)> = Vec::new();
916        for (name, value) in self.arch.macros() {
917            out.push((name, (*value).to_owned()));
918        }
919        for (name, value) in self.os.macros() {
920            out.push((name, (*value).to_owned()));
921        }
922        if self.os == Os::Windows && self.ptr_bits == 64 {
923            out.push(("_WIN64", "1".to_owned()));
924        }
925        // wasm is neither ELF nor anything `__ELF__` would be right about.
926        if self.os.is_elf() && self.arch != Arch::Wasm32 {
927            out.push(("__ELF__", "1".to_owned()));
928        }
929        // Which C library a *Linux* target links against, for the bundled
930        // headers that have to lay one of its types out. Nothing else says it:
931        // `__linux__` is true of all three, and the real `__GLIBC__` comes
932        // from glibc's own `<features.h>` rather than from a compiler. These
933        // two are cinrs's own, named so, and defined only where the answer is
934        // known — a `-android` or `-uclibc` triple gets neither, and a header
935        // that needs one then refuses rather than guessing. Elsewhere the
936        // operating system settles the library, so there is nothing to say.
937        if self.os == Os::Linux {
938            match self.env {
939                Env::Gnu => out.push(("__cinrs_glibc__", "1".to_owned())),
940                Env::Musl => out.push(("__cinrs_musl__", "1".to_owned())),
941                _ => {}
942            }
943        }
944        out
945    }
946}
947
948/// The C library this crate was compiled against, for [`TargetModel::host`].
949const fn host_env() -> Env {
950    if cfg!(target_env = "gnu") {
951        Env::Gnu
952    } else if cfg!(target_env = "musl") {
953        Env::Musl
954    } else if cfg!(target_os = "android") {
955        Env::Bionic
956    } else if cfg!(target_env = "uclibc") {
957        Env::Uclibc
958    } else if cfg!(target_env = "msvc") {
959        Env::Msvc
960    } else if cfg!(target_os = "linux") {
961        Env::Gnu
962    } else {
963        Env::None
964    }
965}
966
967/// The architecture this crate was compiled for, for [`TargetModel::host`].
968///
969/// A host whose architecture is not in the table falls back to the one whose
970/// *data model* matches, since that is all the rest of the front end reads it
971/// for; the identity macros are then simply absent, which is the same answer
972/// an unfamiliar compiler gives.
973const fn host_arch() -> Arch {
974    if cfg!(target_arch = "x86_64") {
975        Arch::X86_64
976    } else if cfg!(target_arch = "x86") {
977        Arch::X86
978    } else if cfg!(target_arch = "aarch64") {
979        Arch::Aarch64
980    } else if cfg!(target_arch = "arm") {
981        Arch::Arm
982    } else if cfg!(target_arch = "riscv32") {
983        Arch::Riscv32
984    } else if cfg!(target_arch = "riscv64") {
985        Arch::Riscv64
986    } else if cfg!(target_arch = "wasm32") {
987        Arch::Wasm32
988    } else if cfg!(target_arch = "powerpc") {
989        Arch::PowerPc
990    } else if cfg!(target_arch = "powerpc64") {
991        Arch::PowerPc64
992    } else if cfg!(target_arch = "s390x") {
993        Arch::S390x
994    } else if cfg!(target_arch = "mips") {
995        Arch::Mips
996    } else if cfg!(target_arch = "mips64") {
997        Arch::Mips64
998    } else if cfg!(target_arch = "sparc") {
999        Arch::Sparc
1000    } else if cfg!(target_arch = "sparc64") {
1001        Arch::Sparc64
1002    } else if cfg!(target_arch = "loongarch64") {
1003        Arch::LoongArch64
1004    } else if cfg!(target_pointer_width = "64") {
1005        Arch::X86_64
1006    } else {
1007        Arch::X86
1008    }
1009}
1010
1011/// The operating system this crate was compiled for.
1012const fn host_os() -> Os {
1013    if cfg!(any(target_os = "linux", target_os = "android")) {
1014        Os::Linux
1015    } else if cfg!(target_vendor = "apple") {
1016        Os::Darwin
1017    } else if cfg!(windows) {
1018        Os::Windows
1019    } else if cfg!(target_os = "freebsd") {
1020        Os::FreeBsd
1021    } else if cfg!(target_os = "netbsd") {
1022        Os::NetBsd
1023    } else if cfg!(target_os = "openbsd") {
1024        Os::OpenBsd
1025    } else if cfg!(target_os = "wasi") {
1026        Os::Wasi
1027    } else {
1028        Os::None
1029    }
1030}
1031
1032impl Default for TargetModel {
1033    fn default() -> Self {
1034        Self::host()
1035    }
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::*;
1041
1042    fn model(triple: &str) -> TargetModel {
1043        TargetModel::from_triple(triple).unwrap_or_else(|e| panic!("{triple}: {e}"))
1044    }
1045
1046    fn macros(triple: &str) -> Vec<String> {
1047        model(triple)
1048            .macros()
1049            .into_iter()
1050            .map(|(name, value)| format!("{name}={value}"))
1051            .collect()
1052    }
1053
1054    #[test]
1055    fn the_host_model_is_self_consistent() {
1056        let t = TargetModel::host();
1057        assert!(t.short_bits <= t.int_bits);
1058        assert!(t.int_bits <= t.long_bits);
1059        assert!(t.long_bits <= t.long_long_bits);
1060        assert!(t.long_long_bits >= 64);
1061    }
1062
1063    /// The host model has to agree with `core::ffi`, which is what the
1064    /// generated code uses and what the data-model assertion checks. Every
1065    /// width here is asked of the toolchain rather than of the table.
1066    #[test]
1067    fn the_host_model_agrees_with_core_ffi() {
1068        let t = TargetModel::host();
1069        assert_eq!(t.short_bits, 8 * size_of::<core::ffi::c_short>() as u32);
1070        assert_eq!(t.int_bits, 8 * size_of::<core::ffi::c_int>() as u32);
1071        assert_eq!(t.long_bits, 8 * size_of::<core::ffi::c_long>() as u32);
1072        assert_eq!(
1073            t.long_long_bits,
1074            8 * size_of::<core::ffi::c_longlong>() as u32
1075        );
1076        assert_eq!(t.ptr_bits, 8 * size_of::<*const ()>() as u32);
1077        assert_eq!(t.char_signed, core::ffi::c_char::MIN != 0);
1078        assert_eq!(t.int128_align, align_of::<i128>() as u64);
1079        assert_eq!(t.max_scalar_align, align_of::<u64>() as u64);
1080        assert_eq!(t.max_scalar_align, align_of::<f64>() as u64);
1081    }
1082
1083    #[test]
1084    fn a_64_bit_x86_linux_host_is_lp64() {
1085        if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
1086            assert_eq!(TargetModel::host(), TargetModel::LP64);
1087        }
1088    }
1089
1090    /// The host model and the table have to say the same thing about the host,
1091    /// or `CINRS_TARGET` naming the host triple would change the translation.
1092    #[test]
1093    fn the_table_agrees_with_the_host() {
1094        if cfg!(all(
1095            target_os = "linux",
1096            target_arch = "x86_64",
1097            target_env = "gnu"
1098        )) {
1099            assert_eq!(
1100                model("x86_64-unknown-linux-gnu"),
1101                TargetModel::host(),
1102                "the table and the host disagree"
1103            );
1104        }
1105    }
1106
1107    #[test]
1108    fn the_lp64_family() {
1109        for triple in [
1110            "x86_64-unknown-linux-gnu",
1111            "x86_64-unknown-linux-musl",
1112            "x86_64-unknown-freebsd",
1113            "aarch64-unknown-linux-gnu",
1114            "aarch64-apple-darwin",
1115            "x86_64-apple-darwin",
1116            "riscv64gc-unknown-linux-gnu",
1117            "powerpc64le-unknown-linux-gnu",
1118            "s390x-unknown-linux-gnu",
1119            "loongarch64-unknown-linux-gnu",
1120            "x86_64-unknown-none",
1121        ] {
1122            let t = model(triple);
1123            assert_eq!(t.data_model(), "LP64", "{triple}");
1124            assert_eq!(t.long_bits, 64, "{triple}");
1125            assert_eq!(t.ptr_bits, 64, "{triple}");
1126            assert!(t.has_int128, "{triple}");
1127        }
1128    }
1129
1130    #[test]
1131    fn the_llp64_family() {
1132        for triple in [
1133            "x86_64-pc-windows-msvc",
1134            "x86_64-pc-windows-gnu",
1135            "x86_64-pc-windows-gnullvm",
1136            "aarch64-pc-windows-msvc",
1137            "x86_64-uwp-windows-msvc",
1138        ] {
1139            let t = model(triple);
1140            assert_eq!(t.data_model(), "LLP64", "{triple}");
1141            assert_eq!(t.long_bits, 32, "{triple}");
1142            assert_eq!(t.ptr_bits, 64, "{triple}");
1143            assert_eq!(t.wchar_bits, 16, "{triple}");
1144            assert!(!t.wchar_signed, "{triple}");
1145            assert_eq!(t.wint_bits, 16, "{triple}");
1146            // Windows makes plain `char` signed whatever the architecture,
1147            // AArch64 included; `core::ffi::c_char` says the same.
1148            assert!(t.char_signed, "{triple}");
1149            assert_eq!(t.max_scalar_align, 8, "{triple}");
1150        }
1151    }
1152
1153    /// Which Windows targets are the *Microsoft* library, which is what decides
1154    /// whether a unit calling `printf` links `legacy_stdio_definitions`.
1155    #[test]
1156    fn the_msvc_environment() {
1157        for triple in [
1158            "x86_64-pc-windows-msvc",
1159            "i686-pc-windows-msvc",
1160            "aarch64-pc-windows-msvc",
1161            "x86_64-uwp-windows-msvc",
1162            "thumbv7a-pc-windows-msvc",
1163        ] {
1164            assert!(model(triple).is_msvc(), "{triple} is MSVC");
1165        }
1166        // mingw-w64 is Windows and is not the Microsoft library; neither is
1167        // anything that is not Windows at all.
1168        for triple in [
1169            "x86_64-pc-windows-gnu",
1170            "i686-pc-windows-gnu",
1171            "x86_64-pc-windows-gnullvm",
1172            "aarch64-pc-windows-gnullvm",
1173            "x86_64-unknown-linux-gnu",
1174            "aarch64-apple-darwin",
1175            "x86_64-apple-darwin",
1176            "x86_64-unknown-freebsd",
1177            "wasm32-unknown-unknown",
1178            "thumbv7em-none-eabihf",
1179        ] {
1180            assert!(!model(triple).is_msvc(), "{triple} is not MSVC");
1181        }
1182        assert!(TargetModel::LLP64.is_msvc());
1183        assert!(!TargetModel::LP64.is_msvc());
1184        assert!(!TargetModel::ILP32.is_msvc());
1185        // And the host, which is what an expansion with no `CINRS_TARGET` and
1186        // no pragma is translated for.
1187        assert_eq!(
1188            TargetModel::host().is_msvc(),
1189            cfg!(all(windows, target_env = "msvc"))
1190        );
1191    }
1192
1193    #[test]
1194    fn the_ilp32_family() {
1195        for triple in [
1196            "i686-unknown-linux-gnu",
1197            "i586-unknown-linux-gnu",
1198            "i686-pc-windows-msvc",
1199            "armv7-unknown-linux-gnueabihf",
1200            "thumbv7em-none-eabihf",
1201            "riscv32imac-unknown-none-elf",
1202            "wasm32-unknown-unknown",
1203            "wasm32-wasip1",
1204            "mips-unknown-linux-gnu",
1205            "powerpc-unknown-linux-gnu",
1206            "sparc-unknown-linux-gnu",
1207            "x86_64-unknown-linux-gnux32",
1208            "aarch64-unknown-linux-gnu_ilp32",
1209        ] {
1210            let t = model(triple);
1211            assert_eq!(t.data_model(), "ILP32", "{triple}");
1212            assert_eq!(t.long_bits, 32, "{triple}");
1213            assert_eq!(t.ptr_bits, 32, "{triple}");
1214        }
1215    }
1216
1217    /// `__int128` follows the architecture rather than the pointer: the x32
1218    /// ABI keeps it, the machine still being x86-64.
1219    #[test]
1220    fn int128_follows_the_architecture() {
1221        assert!(model("x86_64-unknown-linux-gnux32").has_int128);
1222        assert!(model("aarch64-unknown-linux-gnu").has_int128);
1223        assert!(!model("i686-unknown-linux-gnu").has_int128);
1224        assert!(!model("wasm32-unknown-unknown").has_int128);
1225        assert!(!model("armv7-unknown-linux-gnueabihf").has_int128);
1226    }
1227
1228    /// The signedness of plain `char`, which has to be `core::ffi::c_char`'s
1229    /// or the generated code fails its own assertion.
1230    #[test]
1231    fn plain_char_signedness_follows_core_ffi() {
1232        for triple in [
1233            "x86_64-unknown-linux-gnu",
1234            "i686-unknown-linux-gnu",
1235            "loongarch64-unknown-linux-gnu",
1236            "wasm32-unknown-unknown",
1237            "mips-unknown-linux-gnu",
1238            "sparc64-unknown-netbsd",
1239            // Windows and Apple override the architecture's own default.
1240            "aarch64-pc-windows-msvc",
1241            "aarch64-apple-darwin",
1242            "armv7-apple-ios",
1243        ] {
1244            assert!(model(triple).char_signed, "{triple} should be signed");
1245        }
1246        for triple in [
1247            "aarch64-unknown-linux-gnu",
1248            "armv7-unknown-linux-gnueabihf",
1249            "thumbv7em-none-eabihf",
1250            "riscv64gc-unknown-linux-gnu",
1251            "riscv32imac-unknown-none-elf",
1252            "powerpc64le-unknown-linux-gnu",
1253            "powerpc-unknown-linux-gnu",
1254            "s390x-unknown-linux-gnu",
1255        ] {
1256            assert!(!model(triple).char_signed, "{triple} should be unsigned");
1257        }
1258    }
1259
1260    /// `long long` and `double` are four-byte aligned by the i386 System V ABI
1261    /// and eight-byte aligned by the Microsoft one — the one place where two
1262    /// ILP32 targets lay a `struct` out differently.
1263    #[test]
1264    fn scalar_alignment_splits_i386_from_i386_on_windows() {
1265        assert_eq!(model("i686-unknown-linux-gnu").max_scalar_align, 4);
1266        assert_eq!(model("i586-unknown-netbsd").max_scalar_align, 4);
1267        assert_eq!(model("i686-pc-windows-msvc").max_scalar_align, 8);
1268        assert_eq!(model("i686-pc-windows-gnu").max_scalar_align, 8);
1269        assert_eq!(model("armv7-unknown-linux-gnueabihf").max_scalar_align, 8);
1270        assert_eq!(model("wasm32-unknown-unknown").max_scalar_align, 8);
1271    }
1272
1273    #[test]
1274    fn endianness() {
1275        for triple in [
1276            "s390x-unknown-linux-gnu",
1277            "powerpc64-unknown-linux-gnu",
1278            "powerpc-unknown-linux-gnu",
1279            "sparc64-unknown-linux-gnu",
1280            "mips-unknown-linux-gnu",
1281            "aarch64_be-unknown-linux-gnu",
1282        ] {
1283            assert!(model(triple).big_endian, "{triple} is big-endian");
1284        }
1285        for triple in [
1286            "x86_64-unknown-linux-gnu",
1287            "powerpc64le-unknown-linux-gnu",
1288            "mipsel-unknown-linux-gnu",
1289            "mips64el-unknown-linux-gnuabi64",
1290            "aarch64-unknown-linux-gnu",
1291        ] {
1292            assert!(!model(triple).big_endian, "{triple} is little-endian");
1293        }
1294    }
1295
1296    /// `wchar_t` is `unsigned int` on Arm outside Apple's platforms, `unsigned
1297    /// short` on Windows and `int` everywhere else; `wint_t` is `int` on
1298    /// Apple's, `unsigned short` on Windows and `unsigned int` elsewhere.
1299    #[test]
1300    fn wchar_and_wint() {
1301        let arm = model("aarch64-unknown-linux-gnu");
1302        assert_eq!((arm.wchar_bits, arm.wchar_signed), (32, false));
1303        assert_eq!((arm.wint_bits, arm.wint_signed), (32, false));
1304
1305        let mac = model("aarch64-apple-darwin");
1306        assert_eq!((mac.wchar_bits, mac.wchar_signed), (32, true));
1307        assert_eq!((mac.wint_bits, mac.wint_signed), (32, true));
1308
1309        let win = model("x86_64-pc-windows-msvc");
1310        assert_eq!((win.wchar_bits, win.wchar_signed), (16, false));
1311        assert_eq!((win.wint_bits, win.wint_signed), (16, false));
1312
1313        let linux = model("x86_64-unknown-linux-gnu");
1314        assert_eq!((linux.wchar_bits, linux.wchar_signed), (32, true));
1315        assert_eq!((linux.wint_bits, linux.wint_signed), (32, false));
1316    }
1317
1318    #[test]
1319    fn the_identity_macros() {
1320        let linux = macros("x86_64-unknown-linux-gnu");
1321        for want in [
1322            "__x86_64__=1",
1323            "__amd64__=1",
1324            "__linux__=1",
1325            "__gnu_linux__=1",
1326            "__unix__=1",
1327            "__ELF__=1",
1328        ] {
1329            assert!(linux.contains(&want.to_owned()), "{want} in {linux:?}");
1330        }
1331        assert!(!linux.iter().any(|m| m.starts_with("_WIN")));
1332
1333        let win = macros("x86_64-pc-windows-msvc");
1334        assert!(win.contains(&"_WIN32=1".to_owned()));
1335        assert!(win.contains(&"_WIN64=1".to_owned()));
1336        assert!(!win.iter().any(|m| m.starts_with("__ELF__")));
1337        assert!(!win.iter().any(|m| m.starts_with("__unix")));
1338
1339        let win32 = macros("i686-pc-windows-msvc");
1340        assert!(win32.contains(&"_WIN32=1".to_owned()));
1341        assert!(!win32.contains(&"_WIN64=1".to_owned()));
1342        assert!(win32.contains(&"__i386__=1".to_owned()));
1343
1344        let mac = macros("aarch64-apple-darwin");
1345        assert!(mac.contains(&"__APPLE__=1".to_owned()));
1346        assert!(mac.contains(&"__MACH__=1".to_owned()));
1347        assert!(mac.contains(&"__aarch64__=1".to_owned()));
1348        assert!(!mac.iter().any(|m| m.starts_with("__ELF__")));
1349
1350        let wasm = macros("wasm32-unknown-unknown");
1351        assert!(wasm.contains(&"__wasm32__=1".to_owned()));
1352        assert!(!wasm.iter().any(|m| m.starts_with("__ELF__")));
1353
1354        let riscv = macros("riscv64gc-unknown-linux-gnu");
1355        assert!(riscv.contains(&"__riscv=1".to_owned()));
1356        assert!(riscv.contains(&"__riscv_xlen=64".to_owned()));
1357
1358        let bare = macros("thumbv7em-none-eabihf");
1359        assert!(bare.contains(&"__arm__=1".to_owned()));
1360        assert!(bare.contains(&"__ELF__=1".to_owned()));
1361        assert!(!bare.iter().any(|m| m.starts_with("__linux")));
1362    }
1363
1364    /// The C library a Linux triple names, which is the one thing `__linux__`
1365    /// does not say and the bundled `<threads.h>` has to know.
1366    #[test]
1367    fn the_c_library_of_a_linux_triple() {
1368        for (triple, want) in [
1369            ("x86_64-unknown-linux-gnu", Env::Gnu),
1370            ("armv7-unknown-linux-gnueabihf", Env::Gnu),
1371            ("x86_64-unknown-linux-gnux32", Env::Gnu),
1372            // A Linux triple that names no environment is glibc, which is what
1373            // `rustc` and `gcc` both take it for.
1374            ("x86_64-unknown-linux", Env::Gnu),
1375            ("x86_64-unknown-linux-musl", Env::Musl),
1376            ("aarch64-unknown-linux-musl", Env::Musl),
1377            ("aarch64-linux-android", Env::Bionic),
1378            ("armv7-unknown-linux-uclibceabi", Env::Uclibc),
1379            ("x86_64-pc-windows-msvc", Env::Msvc),
1380            // mingw is `-gnu` and is *not* glibc; the macro below is what keeps
1381            // the two apart, since it is defined only on Linux.
1382            ("x86_64-pc-windows-gnu", Env::Gnu),
1383            ("aarch64-apple-darwin", Env::None),
1384            ("x86_64-unknown-freebsd", Env::None),
1385            ("wasm32-unknown-unknown", Env::None),
1386        ] {
1387            assert_eq!(model(triple).env, want, "{triple}");
1388        }
1389
1390        let glibc = "__cinrs_glibc__=1".to_owned();
1391        let musl = "__cinrs_musl__=1".to_owned();
1392        assert!(macros("x86_64-unknown-linux-gnu").contains(&glibc));
1393        assert!(macros("i686-unknown-linux-gnu").contains(&glibc));
1394        assert!(macros("x86_64-unknown-linux-musl").contains(&musl));
1395        // One or the other, never both, and neither where the answer is not
1396        // known: a `<threads.h>` that guessed would corrupt memory.
1397        for triple in [
1398            "x86_64-unknown-linux-gnu",
1399            "x86_64-unknown-linux-musl",
1400            "aarch64-linux-android",
1401            "x86_64-pc-windows-gnu",
1402            "aarch64-apple-darwin",
1403            "x86_64-unknown-freebsd",
1404            "wasm32-unknown-unknown",
1405        ] {
1406            let macros = macros(triple);
1407            let named = usize::from(macros.contains(&glibc)) + usize::from(macros.contains(&musl));
1408            assert!(named <= 1, "{triple} claims two C libraries");
1409        }
1410        for triple in [
1411            "aarch64-linux-android",
1412            "x86_64-pc-windows-gnu",
1413            "aarch64-apple-darwin",
1414        ] {
1415            let macros = macros(triple);
1416            assert!(!macros.contains(&glibc), "{triple}");
1417            assert!(!macros.contains(&musl), "{triple}");
1418        }
1419    }
1420
1421    #[test]
1422    fn an_unknown_architecture_is_refused() {
1423        let err = TargetModel::from_triple("gizmo-unknown-linux-gnu").unwrap_err();
1424        let message = err.message(&TargetSource::Env("gizmo-unknown-linux-gnu".to_owned()));
1425        assert!(
1426            message.starts_with(
1427                "CINRS_TARGET: unknown architecture 'gizmo' in the target triple \
1428                 'gizmo-unknown-linux-gnu'; set #pragma cinrs target or unset the variable; "
1429            ),
1430            "{message}"
1431        );
1432        assert!(message.contains("the architectures cinrs models are x86, x86_64"));
1433    }
1434
1435    #[test]
1436    fn an_unknown_operating_system_is_refused() {
1437        let err = TargetModel::from_triple("x86_64-unknown-plan9").unwrap_err();
1438        let message = err.message(&TargetSource::Pragma("x86_64-unknown-plan9".to_owned()));
1439        assert!(
1440            message.starts_with(
1441                "#pragma cinrs target: unsupported operating system 'plan9' in the target \
1442                 triple 'x86_64-unknown-plan9'; "
1443            ),
1444            "{message}"
1445        );
1446    }
1447
1448    #[test]
1449    fn the_exotic_data_models_are_refused_by_name() {
1450        let err = TargetModel::from_triple("avr-none-unknown").unwrap_err();
1451        let message = err.message(&TargetSource::Env("avr-none-unknown".to_owned()));
1452        assert!(
1453            message.starts_with(
1454                "CINRS_TARGET: the target triple 'avr-none-unknown' is not supported: on \
1455                 'avr' 'int' is 16 bits and 'double' is 32; set #pragma cinrs target or \
1456                 unset the variable; "
1457            ),
1458            "{message}"
1459        );
1460        assert!(TargetModel::from_triple("msp430-none-elf").is_err());
1461    }
1462
1463    #[test]
1464    fn an_empty_triple_is_refused() {
1465        assert!(TargetModel::from_triple("").is_err());
1466        assert!(TargetModel::from_triple("-linux-gnu").is_err());
1467    }
1468
1469    /// The description the data-model assertion carries names both the model
1470    /// and the knob that chose it.
1471    #[test]
1472    fn the_description_names_the_source() {
1473        let t = model("x86_64-pc-windows-msvc");
1474        let said = t.describe(&TargetSource::Env("x86_64-pc-windows-msvc".to_owned()));
1475        assert_eq!(
1476            said,
1477            "LLP64 (x86_64-windows, signed 'char', 16-bit 'wchar_t'), chosen from \
1478             CINRS_TARGET=x86_64-pc-windows-msvc"
1479        );
1480        let host = TargetModel::host().describe(&TargetSource::Host);
1481        assert!(host.contains("CINRS_TARGET being unset"), "{host}");
1482    }
1483}