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