Skip to main content

rucc_target/
lib.rs

1//! Target descriptions: triples, and the facts about a target that the rest of the
2//! compiler reads rather than hard-codes.
3//!
4//! Design: `spec/12-abi-and-runtime.md`. Layer rank 2, see `spec/18-package-layout.md`.
5//!
6//! The rule from `spec/18-package-layout.md` section 18.2 is that there is no
7//! target-specific code outside this crate, `rucc-tuple`, `rucc-abi`, `rucc-sysroot` and the
8//! per-target rule sets. Those four are one group rather than four exceptions: the tuple names
9//! a machine, `rucc-abi` says what its types look like and how its calls are made,
10//! `rucc-sysroot` says where its headers and libraries are, and this crate is what the rest of
11//! the compiler reads all of it through. Everything a pass
12//! needs to know about a target is a field it can read here. That rule is what makes the
13//! claim in `spec/10-backend.md` testable, namely that a new target is a rule set and a few
14//! data files, and `M10` brings up a fourth target specifically to put a number on it.
15//!
16//! [`TargetInfo::call`] is the other half of that rule and the one with teeth. How a structure
17//! travels between a caller and a callee is the target's answer rather than C's, so the walk to
18//! the IR flattens a C type into a [`Shape`] and asks here what form it takes. Every psABI rule
19//! is behind [`Call`] and nothing outside this crate matches on an architecture to find one.
20//! The rules themselves are `rucc-abi`'s, as data rather than as code, and this crate hands the
21//! question over to them. It answers [`None`] on a target whose ABI is not written down yet,
22//! which today is AArch64 on Windows and nothing else.
23//!
24//! # Status
25//!
26//! Triple parsing and the basic data model are real, which is what `rucc --print-config`
27//! reports, and so is the argument classification of every psABI in
28//! `spec/12-abi-and-runtime.md` sections 12.2 to 12.5, which `rucc-abi` describes as data and
29//! this crate selects between. x86-64's register file is written down,
30//! in [`x86_64`], along with what each of the two conventions over it does with each register,
31//! what each of its machine instructions does with its operands, and which instructions a frame
32//! is made of, which is [`FrameInsts`]. AArch64's register file and the two conventions over it,
33//! AAPCS64 and Apple's, are in [`aarch64`], and its instructions arrive with its backend in `M6`.
34//! RISC-V's arrive with its own. Machine models land in `M6`.
35//!
36//! This crate is tier 3 in `spec/18-package-layout.md` section 18.5: its Rust API is
37//! explicitly unstable and will change without a major version bump.
38
39#![doc(html_root_url = "https://docs.rs/rucc-target/0.18.0")]
40
41use std::fmt;
42use std::str::FromStr;
43
44use rucc_abi::DataLayout;
45use rucc_base::float::Format;
46use rucc_tuple::{self as tuple, TargetTuple};
47
48pub mod aarch64;
49mod abi;
50mod bits;
51mod branch;
52mod flags;
53mod frame;
54pub mod isa;
55mod machine;
56mod named;
57mod operand;
58mod regs;
59mod short;
60pub mod template;
61mod timing;
62mod typenames;
63pub mod x86_64;
64
65pub use crate::abi::{
66    AbiDescription, Arg, Call, Convention, Kind, Narrow, Pass, Piece, Scalar, Shape, Slot, Variadic,
67};
68pub use crate::bits::BitInsts;
69pub use crate::branch::{BranchInsts, Fusion, Move};
70pub use crate::flags::{Compare, FlagInsts, Reader, Reads, Zeroing};
71pub use crate::frame::{ClassMoves, FrameInsts, Kept, Pair, Probe};
72pub use crate::isa::{Choices, Feature, Isa, Target, TargetRefusal};
73pub use crate::machine::{Address, MachineInsts};
74pub use crate::operand::{Constraint, OperandDesc, Role};
75pub use crate::regs::{
76    CallRegs, Chkstk, ClassInfo, Conventions, Guard, PhysReg, Places, RegClass, RegFile, Segment,
77    Trace, Where,
78};
79pub use crate::short::{Copied, Narrowed, ShortInsts, Stepped, Tested, Zeroed};
80pub use crate::timing::{Timing, TimingInsts, Unit};
81pub use crate::typenames::{Lane, TypeName};
82
83/// A target architecture.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
85// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
86// match that needs to change, in this workspace and in anyone else's code. That is
87// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
88// target is a data change: the compiler tells you every place the data is read.
89pub enum Arch {
90    /// x86-64, the first target and the one `M3` brings up.
91    X86_64,
92    /// AArch64, the second target, `M6`.
93    Aarch64,
94    /// 64-bit RISC-V. `spec/10-backend.md` calls this the middle-end canary, because it has
95    /// no condition codes and no complex addressing modes, so anything the middle end got
96    /// away with on x86-64 shows up here.
97    Riscv64,
98}
99
100impl Arch {
101    /// Pointer width in bits.
102    pub const fn pointer_width(self) -> u32 {
103        match self {
104            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => 64,
105        }
106    }
107
108    /// Whether the target is little-endian.
109    pub const fn is_little_endian(self) -> bool {
110        match self {
111            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => true,
112        }
113    }
114
115    /// The name as it appears in a triple.
116    pub const fn as_str(self) -> &'static str {
117        match self {
118            Arch::X86_64 => "x86_64",
119            Arch::Aarch64 => "aarch64",
120            Arch::Riscv64 => "riscv64",
121        }
122    }
123}
124
125/// The operating system a target runs on.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
127// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
128// match that needs to change, in this workspace and in anyone else's code. That is
129// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
130// target is a data change: the compiler tells you every place the data is read.
131pub enum Os {
132    /// Linux, hosted or freestanding.
133    Linux,
134    /// Apple platforms. `spec/12-abi-and-runtime.md` section 12.3 lists the four places
135    /// Apple diverges from AAPCS64, and every one of them is a real bug if missed.
136    Darwin,
137    /// Windows.
138    Windows,
139    /// No operating system, which is what `-ffreestanding` kernel work looks like.
140    None,
141}
142
143impl Os {
144    /// The name as it appears in a triple.
145    pub const fn as_str(self) -> &'static str {
146        match self {
147            Os::Linux => "linux",
148            Os::Darwin => "darwin",
149            Os::Windows => "windows",
150            Os::None => "none",
151        }
152    }
153
154    /// The object file format this operating system uses.
155    pub const fn object_format(self) -> ObjectFormat {
156        match self {
157            Os::Linux | Os::None => ObjectFormat::Elf,
158            Os::Darwin => ObjectFormat::MachO,
159            Os::Windows => ObjectFormat::Coff,
160        }
161    }
162}
163
164/// The C runtime and ABI variant.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
166// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
167// match that needs to change, in this workspace and in anyone else's code. That is
168// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
169// target is a data change: the compiler tells you every place the data is read.
170pub enum Env {
171    /// The default for the operating system.
172    None,
173    /// glibc.
174    Gnu,
175    /// musl.
176    Musl,
177    /// The MSVC ABI.
178    Msvc,
179}
180
181impl Env {
182    /// The name as it appears in a triple, if it appears at all.
183    pub const fn as_str(self) -> &'static str {
184        match self {
185            Env::None => "none",
186            Env::Gnu => "gnu",
187            Env::Musl => "musl",
188            Env::Msvc => "msvc",
189        }
190    }
191}
192
193/// The object file format to emit.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
195// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
196// match that needs to change, in this workspace and in anyone else's code. That is
197// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
198// target is a data change: the compiler tells you every place the data is read.
199pub enum ObjectFormat {
200    /// ELF.
201    Elf,
202    /// Mach-O.
203    MachO,
204    /// COFF.
205    Coff,
206    /// WebAssembly, which is a format for a module rather than for a machine's object file and
207    /// is in this list because the target table has two rows that emit one.
208    Wasm,
209}
210
211impl ObjectFormat {
212    /// The name used in diagnostics and in `--print-config`.
213    pub const fn as_str(self) -> &'static str {
214        match self {
215            ObjectFormat::Elf => "elf",
216            ObjectFormat::MachO => "macho",
217            ObjectFormat::Coff => "coff",
218            ObjectFormat::Wasm => "wasm",
219        }
220    }
221
222    /// The same format as [`rucc_tuple::ObjectFormat`] names it.
223    ///
224    /// The two enumerations exist because the tuple describes forty two targets and this crate
225    /// describes what the compiler emits for one, and they will stay separate for as long as that
226    /// is true. This is the one place they are put side by side.
227    #[must_use]
228    pub const fn from_tuple(format: tuple::ObjectFormat) -> Self {
229        match format {
230            tuple::ObjectFormat::Elf => ObjectFormat::Elf,
231            tuple::ObjectFormat::MachO => ObjectFormat::MachO,
232            tuple::ObjectFormat::Coff => ObjectFormat::Coff,
233            tuple::ObjectFormat::Wasm => ObjectFormat::Wasm,
234        }
235    }
236}
237
238/// A target triple.
239///
240/// We accept the LLVM-style `arch-vendor-os-env` form because that is what build systems
241/// pass, and we normalise it to the three fields we actually branch on. The vendor field is
242/// parsed and discarded: no decision in the compiler depends on it, and keeping it would
243/// invite one.
244#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
245pub struct Triple {
246    /// The architecture.
247    pub arch: Arch,
248    /// The operating system.
249    pub os: Os,
250    /// The runtime and ABI variant.
251    pub env: Env,
252}
253
254impl Triple {
255    /// A triple from its three parts.
256    pub const fn new(arch: Arch, os: Os, env: Env) -> Self {
257        Self { arch, os, env }
258    }
259
260    /// The same machine as a [`TargetTuple`], which is what the layout and ABI descriptions are
261    /// written over.
262    ///
263    /// The tuple carries ten fields and this carries three, so this fills the other seven in from
264    /// their defaults, and every one of those defaults is the answer for the targets this type can
265    /// spell. There is no `x32` here and no big-endian AArch64, so the data model and the byte
266    /// order follow the architecture, and the sub-architecture, the versions and the float ABI have
267    /// nothing to say about any of the combinations.
268    ///
269    /// The environment is narrowed rather than copied across. This type will hold
270    /// `Triple { os: Darwin, env: Gnu }`, because its parser takes the fields by content and
271    /// `aarch64-apple-darwin-gnu` is a string somebody can type, and that is not a machine: a
272    /// Darwin target has one libc and it is not glibc. A tuple refuses to describe one, so the
273    /// pairs that are not machines are mapped to the environment the operating system actually
274    /// has.
275    ///
276    /// # Panics
277    ///
278    /// Never, for a triple this type can hold, which `every_triple_describes_a_machine` checks by
279    /// building all forty eight of them.
280    #[must_use]
281    pub fn tuple(self) -> TargetTuple {
282        let arch = match self.arch {
283            Arch::X86_64 => tuple::Arch::X86_64,
284            Arch::Aarch64 => tuple::Arch::Aarch64,
285            Arch::Riscv64 => tuple::Arch::Riscv64,
286        };
287        let os = match self.os {
288            Os::Linux => tuple::Os::Linux,
289            // macOS rather than iOS, because the three field triple cannot tell them apart and
290            // this compiler is hosted on the one and not on the other.
291            Os::Darwin => tuple::Os::MacOs,
292            Os::Windows => tuple::Os::Windows,
293            Os::None => tuple::Os::None,
294        };
295        let env = match (self.os, self.env) {
296            (Os::Linux, Env::Musl) => tuple::Env::Musl,
297            (Os::Linux, _) => tuple::Env::Gnu,
298            // mingw-w64 is a real Windows environment and the one place `gnu` survives the
299            // narrowing, because it has a different `long double` from MSVC on the same OS.
300            (Os::Windows, Env::Gnu) => tuple::Env::Gnu,
301            (Os::Windows, _) => tuple::Env::Msvc,
302            // Darwin and freestanding have no libc to name.
303            (Os::Darwin | Os::None, _) => tuple::Env::None,
304        };
305        TargetTuple::builder(arch, os)
306            .env(env)
307            .build()
308            .expect("every triple this type can hold describes a machine")
309    }
310
311    /// The triple that describes the same machine as `target`, if this type can spell it.
312    ///
313    /// The inverse of [`Triple::tuple`], and computed by running that function over every triple
314    /// there is rather than by writing the narrowing out a second time. A second table would be a
315    /// second thing to keep in step, and the failure it invites is not a compile error: it is one
316    /// row of the matrix quietly answering as a neighbour.
317    ///
318    /// It returns `None` for most of the target table, and that is the honest answer rather than a
319    /// gap to be papered over. `rucc-abi` describes the scalar layout of all forty two rows, and
320    /// this type holds three fields with three architectures in the first, so seventeen of those
321    /// rows have a [`TargetInfo`] and the other twenty five do not. Anything that needs to lay a
322    /// record out for `s390x-linux-gnu` needs that gap closed rather than an approximation of it.
323    ///
324    /// The environment of the answer is the narrowed one, so the triple this gives back is the
325    /// canonical spelling of that machine: `Env::None` on Darwin and on a freestanding target,
326    /// never the `Env::Gnu` that a parser will accept from a string somebody typed. A deployment
327    /// target or a glibc release does not change which triple a tuple narrows to, so
328    /// `aarch64-macos.13` is the Darwin triple rather than a miss.
329    #[must_use]
330    pub fn from_tuple(target: TargetTuple) -> Option<Triple> {
331        // Four triples narrow onto `x86_64-linux-gnu`, because a Darwin triple claiming glibc is
332        // a string somebody can type and not a machine. So a match is not enough on its own: the
333        // answer is the candidate whose environment came through the narrowing unchanged, and
334        // anything else is only a fallback for the day a narrowing loses a spelling entirely.
335        let mut fallback = None;
336        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
337            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
338                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
339                    let candidate = Triple::new(arch, os, env);
340                    if candidate.tuple() != target.without_versions() {
341                        continue;
342                    }
343                    // By name rather than by a match on the pair, so that an environment added to
344                    // either enumeration does not need a line here. The one name the two spell
345                    // differently is the absent one, which the tuple writes as nothing.
346                    let survived = match env {
347                        Env::None => target.env() == tuple::Env::None,
348                        _ => env.as_str() == target.env().as_str(),
349                    };
350                    if survived {
351                        return Some(candidate);
352                    }
353                    fallback.get_or_insert(candidate);
354                }
355            }
356        }
357        fallback
358    }
359
360    /// The triple of the machine this compiler is running on.
361    ///
362    /// Used as the default target, which is what makes `rucc hello.c` work with no flags.
363    /// Unknown host combinations are not an error here: they are reported by the driver,
364    /// where there is somewhere to report them to.
365    pub fn host() -> Option<Self> {
366        let arch = match std::env::consts::ARCH {
367            "x86_64" => Arch::X86_64,
368            "aarch64" => Arch::Aarch64,
369            "riscv64" => Arch::Riscv64,
370            _ => return None,
371        };
372        // Which libc this is matters, and `std::env::consts` does not say. A compiler built on
373        // Alpine and defaulting to `x86_64-unknown-linux-gnu` describes a machine it is not
374        // running on: musl and glibc disagree about `int_fast16_t` among other things, and a
375        // header that is written out of the predefined type names picks the disagreement up.
376        // The libc rucc itself was linked against is the best evidence available about the one
377        // the code it compiles will be linked against, and it is right on every machine where
378        // rucc was built for the machine it runs on.
379        let linux = if cfg!(target_env = "musl") { Env::Musl } else { Env::Gnu };
380        // Windows is gnu whichever ABI rucc itself was built for. An `rucc.exe` built with MSVC
381        // still has no Windows SDK to link against on a fresh machine, and it can fetch the
382        // mingw-w64 sysroot, so `rucc hello.c` works there only if the default is the one it can
383        // fetch. `--target=x86_64-windows-msvc` is still there for somebody who has the SDK.
384        let (os, env) = match std::env::consts::OS {
385            "linux" => (Os::Linux, linux),
386            "macos" => (Os::Darwin, Env::None),
387            "windows" => (Os::Windows, Env::Gnu),
388            _ => return None,
389        };
390        Some(Self::new(arch, os, env))
391    }
392}
393
394impl fmt::Display for Triple {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        // Always four fields, always the same spelling, because this string ends up in
397        // `--print-config` output that people diff.
398        write!(f, "{}-unknown-{}-{}", self.arch.as_str(), self.os.as_str(), self.env.as_str())
399    }
400}
401
402/// Why a triple failed to parse.
403#[derive(Debug, Clone, PartialEq, Eq)]
404pub struct ParseTripleError {
405    /// The triple as given.
406    pub input: String,
407    /// What specifically was not recognised.
408    pub reason: &'static str,
409}
410
411impl fmt::Display for ParseTripleError {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        write!(f, "unsupported target triple `{}`: {}", self.input, self.reason)
414    }
415}
416
417impl std::error::Error for ParseTripleError {}
418
419impl FromStr for Triple {
420    type Err = ParseTripleError;
421
422    fn from_str(s: &str) -> Result<Self, Self::Err> {
423        let err = |reason| ParseTripleError { input: s.to_owned(), reason };
424        let mut parts = s.split('-');
425
426        let arch = match parts.next() {
427            Some("x86_64" | "amd64") => Arch::X86_64,
428            Some("aarch64" | "arm64") => Arch::Aarch64,
429            Some("riscv64") => Arch::Riscv64,
430            _ => return Err(err("unknown architecture")),
431        };
432
433        // The vendor field is optional in practice. `x86_64-linux-gnu` and
434        // `x86_64-unknown-linux-gnu` both occur in the wild and mean the same thing, so the
435        // remaining fields are matched by content rather than by position.
436        let rest: Vec<&str> = parts.collect();
437        let mut os = None;
438        let mut env = None;
439        for part in &rest {
440            match *part {
441                "linux" => os = Some(Os::Linux),
442                "darwin" | "macos" | "macosx" | "ios" => os = Some(Os::Darwin),
443                "windows" | "win32" => os = Some(Os::Windows),
444                // `none` is the one token that means different things in the two positions.
445                // In `x86_64-unknown-none-elf` it is the operating system; in
446                // `aarch64-apple-darwin-none` it is the environment. Which one it is depends
447                // on whether an operating system has already been seen, and that rule is what
448                // makes `Display` round-trip through `FromStr`.
449                "none" if os.is_none() => os = Some(Os::None),
450                "none" => env = Some(Env::None),
451                "elf" => os = os.or(Some(Os::None)),
452                "gnu" | "gnueabi" | "gnueabihf" => env = Some(Env::Gnu),
453                "musl" | "musleabi" | "musleabihf" => env = Some(Env::Musl),
454                "msvc" => env = Some(Env::Msvc),
455                _ => {}
456            }
457        }
458
459        let os = os.ok_or_else(|| err("unknown operating system"))?;
460        // The same defaults as `rucc_tuple::Os::default_env`, so that `x86_64-pc-windows` means
461        // one target whichever of the two parsers read it, and it means the mingw-w64 one because
462        // that is the one a fresh machine can link for.
463        let env = env.unwrap_or(match os {
464            Os::Linux | Os::Windows => Env::Gnu,
465            Os::Darwin | Os::None => Env::None,
466        });
467        Ok(Self::new(arch, os, env))
468    }
469}
470
471/// The facts about a target that the compiler reads instead of hard-coding.
472///
473/// This is the whole of what a pass is allowed to know about where its output will run.
474/// It grows, and every field added here is one fewer `#[cfg]` somewhere it should not be.
475#[derive(Debug, Clone, PartialEq, Eq)]
476#[non_exhaustive]
477pub struct TargetInfo {
478    /// The machine this describes, as the ten field tuple rather than as a three field triple.
479    ///
480    /// It is the tuple because a record layout is a question every row of the target table has an
481    /// answer to, and a triple can spell fifteen of the forty two. Nothing else in this type had
482    /// to change to widen it: every field below is already derived from `rucc-abi`'s description
483    /// of this tuple, and the ones that were not were the bugs.
484    pub tuple: TargetTuple,
485    /// The sizes, the alignments and the signedness this target's headers were written against.
486    ///
487    /// The widths below are views of this and the alignments are not, which is the reason it is
488    /// kept whole. A `long long` is eight bytes on every row of the table and is aligned to four
489    /// on System V i386 and to eight everywhere else, and no width can say that.
490    pub scalars: DataLayout,
491    /// Width of a pointer in bits.
492    pub pointer_width: u32,
493    /// Whether bytes are ordered little end first.
494    pub little_endian: bool,
495    /// Whether a bare `char` is signed.
496    ///
497    /// Signed on x86-64 and unsigned on AArch64 Linux, which is the classic source of code
498    /// that works on one and not the other, so it is data rather than an assumption.
499    pub char_is_signed: bool,
500    /// Width of `long` in bits. This is the field that separates the LP64 world from
501    /// Windows LLP64.
502    pub long_width: u32,
503    /// Width of `long double` in bits: 80 bits of x87 stored in 128 on every x86-64 target but
504    /// MSVC, 128 of true quad precision on AArch64 Linux and RISC-V, and 64 on Apple's AArch64 and
505    /// under MSVC.
506    ///
507    /// Apple's x86-64 is not one of the 64-bit ones, which is the trap. The change to a `double`
508    /// came with AArch64 and the Intel answer stayed as it was, so `x86_64-apple-darwin` and
509    /// `x86_64-unknown-linux-gnu` agree here and `aarch64-apple-darwin` is the odd one.
510    pub long_double_width: u32,
511    /// The format `long double` actually is, which the width does not say.
512    ///
513    /// It is 128 bits wide on SysV x86-64 and on AArch64 Linux and the two are not the same
514    /// type: one is the x87 eighty bit format padded out to sixteen bytes and the other is
515    /// true quad precision with a hundred and thirteen bits of significand. Anything that
516    /// converts a constant or folds one has to know which, and the width alone cannot say.
517    pub long_double_format: Format,
518    /// The format `_Float64x` is, which is the widest format the target has short of a software
519    /// one.
520    ///
521    /// It follows the architecture and not the operating system, which is what makes it worth a
522    /// field of its own next to `long double`. Apple and Windows define `long double` as a
523    /// `double` and neither of them takes `_Float64x` down with it: the type has to be wider
524    /// than a `_Float64`, so it is the x87 eighty bit format on x86-64 and quad precision on
525    /// AArch64 and RISC-V wherever it is written.
526    ///
527    /// [`None`] on a machine whose widest format is a `double`, which is 32-bit ARM and wasm32.
528    /// The type does not exist there and neither reference defines the macros that describe it,
529    /// so the honest answer is that there is no format rather than a `double` in its place.
530    pub float64x_format: Option<Format>,
531    /// Whether the target has `_Float16`.
532    ///
533    /// The named types are not all universal the way `_Float32` and `_Float64` are. gcc 13 has
534    /// this one on x86-64, AArch64 and RISC-V and does not have it on i686, armv7, ppc64le or
535    /// s390x, which was measured by compiling a declaration of it with each of those cross
536    /// compilers. The `__FLT16_*__` macros and the `f16` suffix are defined on exactly the rows
537    /// where the type is, so all three ask this one field.
538    ///
539    /// i686 is the row worth explaining. gcc aims at the baseline of the target rather than at
540    /// whatever chip is under it, and half precision on x86 needs SSE2, which is in the baseline
541    /// of x86-64 and not in the baseline of i686. So the two x86 rows disagree, and a `-msse2`
542    /// on the command line would move the 32-bit one, which is a thing this compiler has no
543    /// place to say yet.
544    pub has_float16: bool,
545    /// Whether the target has `_Float128`.
546    ///
547    /// Every row but 32-bit ARM among the seven measured against gcc 13. x86-64 and i686 have it
548    /// in software, and AArch64, RISC-V, s390x and ppc64le have it because quad precision is
549    /// already the format of something on those machines. armv7 has no format wider than a
550    /// `double` at all, so the type is not there and gcc says so.
551    ///
552    /// This is the ISO spelling. gcc's `__float128` is a narrower thing and is not this field:
553    /// that name exists on x86 and PowerPC only, and on AArch64, RISC-V and s390x gcc offers
554    /// `_Float128` in its place when a program writes it. `__SIZEOF_FLOAT128__` follows the
555    /// vendor name rather than the type, which is why it is missing on rows where the type is
556    /// there.
557    pub has_float128: bool,
558    /// Whether the target has `_Decimal32`, `_Decimal64` and `_Decimal128`.
559    ///
560    /// Only x86-64 Linux today. gcc has the three types on more rows than that, but a decimal is
561    /// a call into libgcc for everything but a move, and the only encoding the back end names
562    /// routines for is the binary integer one x86 uses. PowerPC and s390x use the densely packed
563    /// encoding and are a different set of routines, and the other rows are untested, so a
564    /// program that writes one there is told the type is not available rather than handed code
565    /// nobody has run.
566    pub has_decimal_float: bool,
567    /// Width of `wchar_t` in bits, which decides what a wide literal is encoded in.
568    ///
569    /// It is 16 on Windows, so a wide string there is UTF-16 and a character outside the basic
570    /// plane takes two elements, and 32 everywhere else, where a wide string is UTF-32 and no
571    /// character takes more than one.
572    pub wchar_width: u32,
573    /// Whether `wchar_t` is signed.
574    ///
575    /// x86-64 Linux makes it a signed `int` and AArch64 Linux makes it an `unsigned int`,
576    /// following the psABI's rule for plain `char`, so `L'\xffffffff'` is minus one on one of
577    /// them and four billion on the other.
578    pub wchar_is_signed: bool,
579    /// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
580    ///
581    /// Above 64 bits the psABIs stop treating a `_BitInt` like a standard integer type and
582    /// start treating it like an array of these, so its size is rounded up to a multiple of
583    /// this and its alignment is this. It is 64 on x86-64 and RISC-V and 128 on AArch64, which
584    /// is why `_BitInt(65)` is sixteen bytes aligned to eight on one and sixteen bytes aligned
585    /// to sixteen on the other. Measured with clang 18 on x86-64 Linux and clang on AArch64
586    /// Darwin rather than read off the documents.
587    pub bit_int_granule: u32,
588    /// The widest access, in bits, this machine performs atomically without taking a lock.
589    ///
590    /// It is what `__atomic_always_lock_free` and `__atomic_is_lock_free` answer from, and it is
591    /// a claim about what this compiler emits rather than about what the processor is capable of.
592    /// Sixty four on every target here. x86-64 does sixteen bytes atomically with `cmpxchg16b`,
593    /// which is not in the baseline the psABI names and which nothing in this compiler writes, and
594    /// AArch64 does the same with its pair instructions, which nothing writes either. A target
595    /// that answered yes for sixteen bytes and then called a library that has to take a lock for
596    /// them would have two answers to one question, and the wrong one is the one in the header.
597    pub lock_free_width: u32,
598    /// The object format to emit.
599    pub object_format: ObjectFormat,
600    /// Whether the output says where an unwind lands, which is the language specific data area
601    /// beside a function's call frame information that a cleanup under `-fexceptions` needs.
602    ///
603    /// ELF on x86-64 and AArch64 and nothing else yet. It is a claim about what this compiler
604    /// writes rather than about what the platform can do, so lowering turns down a cleanup it could
605    /// not honour on the others instead of emitting one the unwinder would skip.
606    pub landing_pads: bool,
607    /// Whether a table in read only data may hold how far a label is from the table, as a four
608    /// byte relocation measured from where it is written.
609    ///
610    /// x86-64 ELF, which is the one output whose writer has been taught that relocation for data.
611    /// Everywhere else a jump table holds whole addresses.
612    pub relative_tables: bool,
613    /// How bit-fields are allocated into storage, which is the one record layout question where
614    /// two targets in this table run different algorithms rather than the same one over different
615    /// numbers.
616    pub bit_field_style: BitFieldStyle,
617    /// Whether an unnamed bit-field raises the record's alignment the way a named one does.
618    ///
619    /// Almost everywhere it does not, which is why `struct { char c; int :20; }` is four bytes
620    /// aligned to one on x86-64 and four aligned to four with the field named. AAPCS64 says
621    /// otherwise and says it for the zero width member too, so `struct { unsigned :0; }` is
622    /// aligned to four on AArch64 Linux and to one on Apple's AArch64, on Windows on AArch64, on
623    /// x86-64 and on RISC-V. Measured with the pinned reference across every row that has one,
624    /// because it is neither an architecture rule nor an operating system rule: it is the ABI, and
625    /// Apple and Microsoft each dropped it.
626    ///
627    /// Windows says yes as well, and there it is not AAPCS64 but Microsoft's own rule, which is
628    /// why the two facts are separate fields rather than one. In a `union` the Microsoft rule goes
629    /// further and no bit-field contributes alignment at all, named or not, so this field is only
630    /// half the answer there and [`BitFieldStyle`] carries the other half.
631    pub unnamed_bit_field_aligns: bool,
632    /// How large a record with no storage in it is, in bytes, before its alignment is applied.
633    ///
634    /// Zero everywhere but MSVC, where it is four. A `struct` with no members is not C at all, it
635    /// is a GNU extension, and C++ gives it a size of one, so there is no standard to read the
636    /// answer out of and the number has to come from whatever else compiles for the target. On
637    /// mingw that is GCC and the answer is zero. On MSVC it is clang, because MSVC itself rejects
638    /// the declaration outright, and clang's Microsoft record layout gives it four bytes and gives
639    /// an array of three of them twelve. So this is a fact about the environment and not about the
640    /// operating system, which is the one place in this type where those two come apart in that
641    /// direction.
642    ///
643    /// It covers a record with no members and a record whose only members occupy nothing, which is
644    /// the zero width bit-field, the zero length array and the flexible array member. All four
645    /// were measured and all four agree.
646    pub empty_record_size: u64,
647    /// What `__builtin_va_list` is, which is the type every `va_list` in every header is a
648    /// typedef of.
649    ///
650    /// [`None`] on a target whose answer is a type this crate does not build yet. 32-bit ARM's is
651    /// a structure of one pointer and s390x's is a structure of four members, and neither is any
652    /// of the four below. A target with no backend cannot compile a call to `va_arg` in any case,
653    /// so saying so beats naming a neighbour's type and having a header believe it.
654    pub va_list: Option<VaList>,
655    /// The registers the machine has, which is [`RegFile::EMPTY`] for an architecture nothing
656    /// has described yet.
657    pub regs: &'static RegFile,
658    /// Which registers the calling convention gives which job, or `None` while the
659    /// architecture has no register file to name them out of.
660    pub call_regs: Option<&'static CallRegs>,
661    /// How long this machine's instructions take, or `None` for an architecture with no backend.
662    ///
663    /// [`None`] rather than a model of a machine nobody measured, for the reason the two fields
664    /// above are: a scheduler told made up numbers about a processor has no way to find out they
665    /// were made up. `--print-config` prints [`TimingInsts::model`] off this, which is the first
666    /// thing anybody comparing two runs of a benchmark wants to know.
667    pub timing: Option<&'static TimingInsts>,
668}
669
670/// The type a target's `__builtin_va_list` is.
671///
672/// A variable argument list is the one place a psABI dictates a C type rather than how a type
673/// travels, and the four answers below are not four spellings of one thing: `sizeof(va_list)` is
674/// eight bytes on Apple's AArch64 and thirty two on Linux's, and on SysV x86-64 a `va_list` is an
675/// array, so a `va_list` passed to a function is passed as a pointer and one assigned to another
676/// is a constraint violation rather than a copy. Code in the wild depends on all of that.
677#[derive(Debug, Clone, Copy, PartialEq, Eq)]
678// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a fifth answer here is
679// a fifth type to build, and every place that builds one should stop compiling until it does.
680pub enum VaList {
681    /// `char *`, which is what a target whose arguments are all passed in one place needs: the
682    /// address of the next argument and nothing else. Apple's AArch64 and both Windows targets.
683    CharPointer,
684    /// `void *`, which is the RISC-V psABI's spelling of the same thing.
685    VoidPointer,
686    /// `struct __va_list_tag { unsigned gp_offset, fp_offset; void *overflow_arg_area,
687    /// *reg_save_area; } [1]`, the SysV x86-64 one. Arguments arrive in two register files and
688    /// on the stack, so the list is a cursor into each, and the array of one is what makes
689    /// passing it to `vfprintf` pass its address.
690    SysV,
691    /// `struct __va_list { void *__stack, *__gr_top, *__vr_top; int __gr_offs, __vr_offs; }`,
692    /// the AAPCS64 one. The same idea as SysV's, counting down from the top of each save area
693    /// rather than up from the bottom, and not an array.
694    Aapcs,
695}
696
697impl VaList {
698    /// The name used in `--print-config`.
699    #[must_use]
700    pub const fn as_str(self) -> &'static str {
701        match self {
702            VaList::CharPointer => "char-pointer",
703            VaList::VoidPointer => "void-pointer",
704            VaList::SysV => "sysv",
705            VaList::Aapcs => "aapcs",
706        }
707    }
708}
709
710/// How a target allocates bit-fields into storage.
711///
712/// Everything else about laying a record out is one algorithm reading different sizes and
713/// alignments per target. This is not: the two answers below place the same members at different
714/// offsets and give the same struct different sizes, and no amount of changing what an `int` is
715/// turns one into the other. `struct { unsigned m:3; char c; }` is four bytes with the `char` at
716/// offset one under the first and eight bytes with it at offset four under the second.
717#[derive(Debug, Clone, Copy, PartialEq, Eq)]
718// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a third answer here is a
719// third algorithm to write, and every place that chooses between them should stop compiling until
720// it does.
721pub enum BitFieldStyle {
722    /// The Itanium C++ ABI's rule, which every psABI in this table except Windows follows. A
723    /// bit-field goes at the next free bit unless that would make it span more storage than its
724    /// own type occupies, in which case it starts at the next boundary of its alignment. Storage
725    /// is shared between members of different types freely, so `struct { char a:3; unsigned b:3; }`
726    /// is four bytes with both fields in the first one.
727    Itanium,
728    /// Microsoft's rule, which both Windows environments follow and not only MSVC. A run of
729    /// bit-fields is allocated into a unit the size and alignment of the declared type, and the
730    /// unit is closed both when the next member's declared type has a different size and when the
731    /// field does not fit in what is left. An ordinary member closes a unit too, and the closed
732    /// unit occupies its whole declared size whether or not the bits were used. So the same struct
733    /// is eight bytes: a one byte unit for the `char` and a four byte one for the `unsigned`,
734    /// aligned to four.
735    Microsoft,
736}
737
738impl BitFieldStyle {
739    /// The name used in `--print-config`.
740    #[must_use]
741    pub const fn as_str(self) -> &'static str {
742        match self {
743            BitFieldStyle::Itanium => "itanium",
744            BitFieldStyle::Microsoft => "microsoft",
745        }
746    }
747}
748
749/// A width in bits, from a size in bytes.
750///
751/// The fields here are widths because that is what a predefined macro and a diagnostic say, and a
752/// layout is sizes because that is what `sizeof` says. The conversion belongs at the one boundary
753/// between them rather than at every reader of one of these fields.
754/// Whether `target` is the one output the unwind tables and relative jump tables are written for.
755fn x86_64_elf(target: TargetTuple, pointer_size: u64) -> bool {
756    target.arch() == tuple::Arch::X86_64
757        && target.object_format() == tuple::ObjectFormat::Elf
758        && pointer_size == 8
759}
760
761fn bits(bytes: u64) -> u32 {
762    u32::try_from(bytes * 8).expect("no standard type is four billion bits wide")
763}
764
765impl TargetInfo {
766    /// The description of `triple`.
767    ///
768    /// The three field triple spells fifteen of the forty two rows of the target table, which is
769    /// every row with a backend and every row a driver will be handed today, so this is what the
770    /// compiler proper calls. [`TargetInfo::for_tuple`] is the one that answers for the whole
771    /// table.
772    #[must_use]
773    pub fn new(triple: Triple) -> Self {
774        Self::for_tuple(triple.tuple())
775    }
776
777    /// The type names this target's compiler has before any header is read, and what each is.
778    ///
779    /// Empty everywhere but AArch64, where gcc has the Advanced SIMD and SVE types and glibc's
780    /// `<math.h>` names them.
781    #[must_use]
782    pub fn type_names(&self) -> &'static [(&'static str, TypeName)] {
783        typenames::type_names(self.tuple.arch())
784    }
785
786    /// Whether a file-scope `register T x asm ("name")` on this target can be kept as what it says.
787    ///
788    /// Such a variable is the register for the whole program, so it can only be honoured for a
789    /// register the code generator never hands out and nothing else writes behind its back. On
790    /// AArch64 that is `x18`, which rucc keeps off every target because Windows and Apple give it
791    /// to the platform, and which mingw-w64's `winnt.h` declares this way so that `NtCurrentTeb`
792    /// reads the thread's TEB out of it.
793    #[must_use]
794    pub fn keeps_register_for_the_program(&self, name: &str) -> bool {
795        self.tuple.arch() == tuple::Arch::Aarch64 && name == "x18"
796    }
797
798    /// Whether an unnamed bit-field raises the record's alignment under `style`, which is the
799    /// target's own rule or the one a `gcc_struct` or `ms_struct` attribute chose.
800    ///
801    /// Under the target's own rule it is [`TargetInfo::unnamed_bit_field_aligns`]. Microsoft's rule
802    /// says yes everywhere. The Itanium rule that `gcc_struct` asks for on Windows says what it
803    /// says on the same architecture's other rows: no on x86-64, and yes on AArch64, where AAPCS64
804    /// says so. So `struct { char c; int :20; } __attribute__((gcc_struct))` is four bytes aligned
805    /// to one from mingw-w64 gcc on x86-64 and four aligned to four from llvm-mingw's clang on
806    /// AArch64, which is also what gcc for AArch64 Linux makes of it without the attribute.
807    #[must_use]
808    pub fn unnamed_bit_field_aligns_under(&self, style: BitFieldStyle) -> bool {
809        if style == self.bit_field_style {
810            return self.unnamed_bit_field_aligns;
811        }
812        match style {
813            BitFieldStyle::Microsoft => true,
814            BitFieldStyle::Itanium => {
815                matches!(
816                    self.tuple.arch(),
817                    tuple::Arch::Aarch64 | tuple::Arch::Arm | tuple::Arch::Arm64Ec
818                ) && !self.tuple.os().is_darwin()
819            }
820        }
821    }
822
823    /// The description of `target`.
824    ///
825    /// Every row of the target table has one of these, whether or not there is a backend that can
826    /// emit code for it, because laying a record out and reading a header are questions that do
827    /// not need a backend. The fields that genuinely need one say so: [`TargetInfo::regs`] is
828    /// empty and [`TargetInfo::call_regs`] is [`None`] for an architecture whose register file is
829    /// not written down.
830    #[must_use]
831    pub fn for_tuple(target: TargetTuple) -> Self {
832        // Every size, alignment and signedness below is `rucc-abi`'s answer over the ten field
833        // tuple rather than a match written out here. They were written out here, and the copy was
834        // wrong about `x86_64-apple-darwin`, whose `long double` is the eighty bit x87 format in
835        // sixteen bytes and not a `double`: Apple made that change on AArch64 and left the Intel
836        // answer alone, and a rule keyed on the operating system takes both.
837        let layout = DataLayout::for_target(target);
838        // RISC-V and everything else with a row and no backend have register files and this crate
839        // has not written them down yet. They arrive with the backends that need them. AArch64's is
840        // here ahead of its backend, because the convention over it is what the ABI tests and the
841        // debugging information read, and [`TargetInfo::regs`] having it does not make anything
842        // try to generate code: that is `rucc_codegen::Machine::for_target`'s decision.
843        let regs = match target.arch() {
844            tuple::Arch::X86_64 => &x86_64::REGS,
845            tuple::Arch::Aarch64 => &aarch64::REGS,
846            _ => &RegFile::EMPTY,
847        };
848        let call_regs = match (target.arch(), target.os(), target.env()) {
849            // The environment, and this is the one question it decides about a convention. What the
850            // two Windows runtimes disagree about is the name of the routine a large frame reaches
851            // its pages by calling, which is in the runtime rather than in the compiler, so a build
852            // against mingw-w64 and a build against Microsoft's runtime want different names for the
853            // same routine.
854            (tuple::Arch::X86_64, tuple::Os::Windows, tuple::Env::Gnu) => Some(&x86_64::MINGW64),
855            (tuple::Arch::X86_64, tuple::Os::Windows, _) => Some(&x86_64::WIN64),
856            // Apple's x86-64 follows SysV, and its divergences from it are on AArch64.
857            (tuple::Arch::X86_64, _, _) => Some(&x86_64::SYSV),
858            // Windows on AArch64 reserves `x18`, passes every argument of a variadic function in
859            // the x registers and homes them at the top of the callee's frame.
860            (tuple::Arch::Aarch64, tuple::Os::Windows, _) => Some(&aarch64::WINDOWS),
861            (tuple::Arch::Aarch64, os, _) if os.is_darwin() => Some(&aarch64::DARWIN),
862            (tuple::Arch::Aarch64, _, _) => Some(&aarch64::AAPCS64),
863            _ => None,
864        };
865        // The same rule as the register file. A model is a measurement of a processor, and there
866        // is nothing to measure until there is a backend emitting instructions for it.
867        let timing = match target.arch() {
868            tuple::Arch::X86_64 => Some(&x86_64::TIMING),
869            _ => None,
870        };
871        Self {
872            tuple: target,
873            scalars: layout,
874            pointer_width: bits(layout.pointer_size),
875            little_endian: target.is_little_endian(),
876            char_is_signed: layout.char_is_signed,
877            long_width: bits(layout.long_size),
878            long_double_width: bits(layout.long_double.size),
879            long_double_format: layout.long_double.format,
880            float64x_format: float64x_format(target),
881            has_float16: has_float16(target),
882            has_float128: has_float128(target),
883            has_decimal_float: matches!(
884                (target.arch(), target.os()),
885                (tuple::Arch::X86_64, tuple::Os::Linux)
886            ),
887            wchar_width: bits(layout.wchar_size),
888            wchar_is_signed: layout.wchar_is_signed,
889            bit_int_granule: bit_int_granule(target),
890            // Eight bytes everywhere, for the reason the field gives: it is the widest access this
891            // compiler writes an instruction for, and every one of these machines has a wider one
892            // that nothing here reaches. It is a claim about the code this compiler emits, so the
893            // day a backend emits a sixteen byte atomic is the day this stops being one number.
894            lock_free_width: 64,
895            object_format: ObjectFormat::from_tuple(target.object_format()),
896            landing_pads: x86_64_elf(target, layout.pointer_size)
897                || (target.arch() == tuple::Arch::Aarch64
898                    && target.object_format() == tuple::ObjectFormat::Elf),
899            relative_tables: x86_64_elf(target, layout.pointer_size),
900            bit_field_style: bit_field_style(target),
901            unnamed_bit_field_aligns: unnamed_bit_field_aligns(target),
902            // The environment and not the operating system, so `x86_64-windows-gnu` keeps GCC's
903            // zero while `x86_64-windows-msvc` takes clang's four.
904            empty_record_size: match target.env() {
905                tuple::Env::Msvc => 4,
906                _ => 0,
907            },
908            va_list: va_list(target),
909            regs,
910            call_regs,
911            timing,
912        }
913    }
914
915    /// The largest an object may be on this target, in bytes.
916    ///
917    /// `PTRDIFF_MAX`, which is what C 6.5.6 needs it to be: subtracting two pointers into one
918    /// object has to have an answer, and the answer has a `ptrdiff_t` to fit in. So an object
919    /// of exactly this many bytes is allowed and one byte more is not, which is the line GCC
920    /// draws too. It is the only size limit in the compiler and every layout question that has
921    /// one asks here rather than at whatever its own arithmetic happens to overflow at.
922    #[must_use]
923    pub const fn max_object_size(&self) -> u64 {
924        (1u64 << (self.pointer_width - 1)) - 1
925    }
926}
927
928/// The format `_Float64x` is, where the target has one.
929fn float64x_format(target: TargetTuple) -> Option<Format> {
930    match target.arch() {
931        // The x87 unit is on the machine whatever the operating system says a `long double` is,
932        // so `x86_64-apple-darwin` and `x86_64-windows-msvc` both have an eighty bit `_Float64x`
933        // and an eight byte `long double`.
934        tuple::Arch::X86_64 | tuple::Arch::X86 => Some(Format::X87Extended),
935        tuple::Arch::Aarch64
936        | tuple::Arch::Riscv64
937        | tuple::Arch::Riscv32
938        | tuple::Arch::LoongArch64
939        | tuple::Arch::S390x
940        | tuple::Arch::PowerPc64 => Some(Format::Quad),
941        // Nothing on these machines is wider than a `double`, so there is no type here to
942        // describe and neither reference defines the macros that would describe it.
943        tuple::Arch::Arm | tuple::Arch::Arm64Ec | tuple::Arch::Wasm32 => None,
944    }
945}
946
947/// Whether the target has `_Float16`.
948fn has_float16(target: TargetTuple) -> bool {
949    match target.arch() {
950        // Half precision is in the baseline of these: SSE2 on x86-64, the FP16 storage format
951        // every ARMv8 has, and RISC-V, where gcc gives the type whether or not the hardware has
952        // the instructions to go with it.
953        tuple::Arch::X86_64
954        | tuple::Arch::Aarch64
955        | tuple::Arch::Arm64Ec
956        | tuple::Arch::Riscv64
957        | tuple::Arch::Riscv32 => true,
958        // i686 for the reason the field gives, which is the baseline and not the chip, and the
959        // rest are machines gcc 13 has not written the type for.
960        tuple::Arch::X86
961        | tuple::Arch::Arm
962        | tuple::Arch::LoongArch64
963        | tuple::Arch::PowerPc64
964        | tuple::Arch::S390x
965        | tuple::Arch::Wasm32 => false,
966    }
967}
968
969/// Whether the target has `_Float128`.
970fn has_float128(target: TargetTuple) -> bool {
971    match target.arch() {
972        // Either the machine already has quad precision, which is the AArch64, RISC-V, s390x and
973        // PowerPC answer, or the compiler provides it in software, which is what x86 does.
974        tuple::Arch::X86_64
975        | tuple::Arch::X86
976        | tuple::Arch::Aarch64
977        | tuple::Arch::Arm64Ec
978        | tuple::Arch::Riscv64
979        | tuple::Arch::Riscv32
980        | tuple::Arch::LoongArch64
981        | tuple::Arch::PowerPc64
982        | tuple::Arch::S390x => true,
983        // The same two rows that have no `_Float64x`, and for the same reason: nothing on the
984        // machine is wider than a `double` and neither reference offers a type that is.
985        tuple::Arch::Arm | tuple::Arch::Wasm32 => false,
986    }
987}
988
989/// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
990fn bit_int_granule(target: TargetTuple) -> u32 {
991    match target.arch() {
992        // AAPCS64 says a `_BitInt` above sixty four bits is an array of `__int128`, which is the
993        // one psABI that departs from the register width here.
994        tuple::Arch::Aarch64 | tuple::Arch::Arm64Ec => 128,
995        // Everywhere else it is the width of a general purpose register, which is what the psABIs
996        // that have written the rule down all say and what both references do on the rows that
997        // have not.
998        tuple::Arch::X86 | tuple::Arch::Arm | tuple::Arch::Riscv32 => 32,
999        tuple::Arch::X86_64
1000        | tuple::Arch::Riscv64
1001        | tuple::Arch::LoongArch64
1002        | tuple::Arch::PowerPc64
1003        | tuple::Arch::S390x
1004        | tuple::Arch::Wasm32 => 64,
1005    }
1006}
1007
1008/// How this target allocates bit-fields into storage.
1009///
1010/// Keyed on the operating system rather than the environment, because mingw's answer here is
1011/// Microsoft's and not GCC's. That is the whole reason it is not a guess: a rule keyed on
1012/// `Env::Msvc` gets `x86_64-windows-gnu` wrong by four bytes on a struct of an `unsigned :3` and a
1013/// `char`, and gets it wrong quietly.
1014fn bit_field_style(target: TargetTuple) -> BitFieldStyle {
1015    match target.os() {
1016        tuple::Os::Windows => BitFieldStyle::Microsoft,
1017        _ => BitFieldStyle::Itanium,
1018    }
1019}
1020
1021/// Whether an unnamed bit-field raises the record's alignment the way a named one does.
1022///
1023/// AAPCS says it does, on both widths of ARM, and Apple and Microsoft each dropped that rule.
1024/// Microsoft then put its own rule in the same place for a `struct`, so Windows says yes again by
1025/// a different route, and says something else entirely for a `union`, which [`BitFieldStyle`]
1026/// carries rather than this.
1027fn unnamed_bit_field_aligns(target: TargetTuple) -> bool {
1028    match (target.arch(), target.os()) {
1029        (_, tuple::Os::Windows) => true,
1030        // A freestanding ARM target is AAPCS proper, so it says yes: there is no operating system
1031        // there to have dropped it.
1032        (tuple::Arch::Aarch64 | tuple::Arch::Arm | tuple::Arch::Arm64Ec, os) => !os.is_darwin(),
1033        _ => false,
1034    }
1035}
1036
1037/// What `__builtin_va_list` is on this target, where this crate can build the type.
1038fn va_list(target: TargetTuple) -> Option<VaList> {
1039    match (target.arch(), target.os()) {
1040        // Windows passes every argument in one place and spills the register ones next to the
1041        // stack ones, so the list is an address, and Apple does the same on AArch64.
1042        (_, tuple::Os::Windows) => Some(VaList::CharPointer),
1043        (tuple::Arch::Aarch64, os) if os.is_darwin() => Some(VaList::CharPointer),
1044        (tuple::Arch::Aarch64, _) => Some(VaList::Aapcs),
1045        // The x32 ABI's list is the same structure with four byte pointers in it, which is what
1046        // building it out of this target's pointer type gives, so it is the same answer.
1047        (tuple::Arch::X86_64, _) => Some(VaList::SysV),
1048        (tuple::Arch::X86, _) => Some(VaList::CharPointer),
1049        (tuple::Arch::Riscv64 | tuple::Arch::Riscv32 | tuple::Arch::LoongArch64, _)
1050        | (tuple::Arch::Wasm32, _) => Some(VaList::VoidPointer),
1051        // 32-bit ARM's is a structure of one pointer, s390x's is a structure of four members, and
1052        // PowerPC's is a structure of five. None of them is any of the four types above and this
1053        // crate does not build them, so it says so rather than naming a neighbour's.
1054        (
1055            tuple::Arch::Arm | tuple::Arch::S390x | tuple::Arch::PowerPc64 | tuple::Arch::Arm64Ec,
1056            _,
1057        ) => None,
1058    }
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063    use super::*;
1064
1065    #[test]
1066    fn parses_a_four_field_triple() {
1067        let t: Triple = "x86_64-unknown-linux-gnu".parse().unwrap();
1068        assert_eq!(t, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1069    }
1070
1071    #[test]
1072    fn parses_a_triple_with_no_vendor() {
1073        let t: Triple = "aarch64-linux-musl".parse().unwrap();
1074        assert_eq!(t, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
1075    }
1076
1077    #[test]
1078    fn accepts_the_common_aliases() {
1079        let a: Triple = "arm64-apple-darwin".parse().unwrap();
1080        let b: Triple = "aarch64-apple-darwin".parse().unwrap();
1081        assert_eq!(a, b);
1082        assert_eq!(a.env, Env::None);
1083    }
1084
1085    #[test]
1086    fn fills_in_the_default_environment() {
1087        let t: Triple = "x86_64-unknown-linux".parse().unwrap();
1088        assert_eq!(t.env, Env::Gnu);
1089        let w: Triple = "x86_64-pc-windows".parse().unwrap();
1090        assert_eq!(w.env, Env::Gnu);
1091    }
1092
1093    #[test]
1094    fn rejects_what_it_does_not_support() {
1095        let e = "sparc64-unknown-linux-gnu".parse::<Triple>().unwrap_err();
1096        assert_eq!(e.reason, "unknown architecture");
1097        let e = "x86_64-unknown-plan9".parse::<Triple>().unwrap_err();
1098        assert_eq!(e.reason, "unknown operating system");
1099    }
1100
1101    #[test]
1102    fn displays_in_a_normalised_form() {
1103        let t: Triple = "amd64-linux-gnu".parse().unwrap();
1104        assert_eq!(t.to_string(), "x86_64-unknown-linux-gnu");
1105    }
1106
1107    #[test]
1108    fn display_round_trips_through_parse() {
1109        for s in [
1110            "x86_64-unknown-linux-gnu",
1111            "aarch64-unknown-darwin-none",
1112            "riscv64-unknown-linux-musl",
1113        ] {
1114            let t: Triple = s.parse().unwrap();
1115            assert_eq!(t.to_string().parse::<Triple>().unwrap(), t);
1116        }
1117    }
1118
1119    #[test]
1120    fn char_signedness_follows_the_psabi() {
1121        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1122        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1123        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1124        assert!(x86.char_is_signed);
1125        assert!(!arm.char_is_signed);
1126        assert!(mac.char_is_signed, "Apple overrides AAPCS64 back to a signed char");
1127    }
1128
1129    #[test]
1130    fn windows_is_llp64() {
1131        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1132        assert_eq!(win.pointer_width, 64);
1133        assert_eq!(win.long_width, 32);
1134    }
1135
1136    #[test]
1137    fn the_largest_object_is_ptrdiff_max() {
1138        // Half the address space less one, which is what a pointer subtraction across the whole
1139        // of one object has to fit in. gcc 16 on x86-64 prints this same number when it refuses
1140        // an array, and takes an object of exactly this many bytes.
1141        for triple in ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc"]
1142        {
1143            let target = TargetInfo::new(triple.parse().unwrap());
1144            assert_eq!(target.max_object_size(), 9_223_372_036_854_775_807, "{triple}");
1145        }
1146    }
1147
1148    #[test]
1149    fn apple_long_double_is_double() {
1150        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1151        assert_eq!(mac.long_double_width, 64);
1152        assert_eq!(mac.long_double_format, Format::Double);
1153        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1154        assert_eq!(linux.long_double_width, 128);
1155    }
1156
1157    #[test]
1158    fn apples_x86_64_is_not_one_of_the_targets_that_narrowed_long_double() {
1159        // The bug the layout facts moving to `rucc-abi` fixed. This crate used to decide the
1160        // width from the operating system, which took both Apple targets, and Apple made the
1161        // change on AArch64 only. `facts/x86_64-macos.facts` in tamnd/rucc-cross records
1162        // `long_double_format=x87_extended` with `sizeof_long_double=16`, from a reference
1163        // compiler, and this used to answer a sixty four bit `double`.
1164        //
1165        // It is the quiet kind of wrong. `sizeof(long double)` came out at eight where the
1166        // headers say sixteen, so `printf("%Lf")` read the wrong bytes and every structure with
1167        // a `long double` in it laid out differently from the system's own.
1168        let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
1169        assert_eq!(mac.long_double_width, 128);
1170        assert_eq!(mac.long_double_format, Format::X87Extended);
1171
1172        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1173        assert_eq!(
1174            (mac.long_double_width, mac.long_double_format),
1175            (linux.long_double_width, linux.long_double_format)
1176        );
1177    }
1178
1179    #[test]
1180    fn every_triple_describes_a_machine() {
1181        // `Triple::tuple` panics on a pair that is not a machine and this is what says there is
1182        // no such pair. All forty eight combinations, including the ones the parser will produce
1183        // from a string somebody can type and no machine has, such as a Darwin target claiming
1184        // glibc.
1185        let mut built = 0;
1186        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
1187            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
1188                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
1189                    let triple = Triple::new(arch, os, env);
1190                    let tuple = triple.tuple();
1191                    assert_eq!(tuple.pointer_width(), 64, "{triple}");
1192                    // The one field the narrowing has to preserve, because mingw and MSVC are the
1193                    // same operating system with two different `long double`s.
1194                    if os == Os::Windows {
1195                        let expected = match env {
1196                            Env::Gnu => rucc_tuple::Env::Gnu,
1197                            _ => rucc_tuple::Env::Msvc,
1198                        };
1199                        assert_eq!(tuple.env(), expected, "{triple}");
1200                    }
1201                    built += 1;
1202                }
1203            }
1204        }
1205        assert_eq!(built, 48);
1206    }
1207
1208    #[test]
1209    fn from_tuple_undoes_the_narrowing() {
1210        // Every triple's tuple comes back as a triple describing the same machine. It is not
1211        // always the triple it started as, because the narrowing is many to one: a Darwin target
1212        // claiming glibc and the same one claiming nothing are one machine, and the answer is the
1213        // spelling that names no libc.
1214        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
1215            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
1216                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
1217                    let triple = Triple::new(arch, os, env);
1218                    let back = Triple::from_tuple(triple.tuple())
1219                        .unwrap_or_else(|| panic!("{triple} has a tuple and no way back"));
1220                    assert_eq!(back.tuple(), triple.tuple(), "{triple}");
1221                    assert_eq!(back.arch, arch, "{triple}");
1222                    assert_eq!(back.os, os, "{triple}");
1223                }
1224            }
1225        }
1226    }
1227
1228    #[test]
1229    fn from_tuple_gives_the_canonical_environment() {
1230        let musl = Triple::from_tuple("aarch64-linux-musl".parse().unwrap()).unwrap();
1231        assert_eq!(musl, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
1232        let gnu = Triple::from_tuple("x86_64-linux-gnu".parse().unwrap()).unwrap();
1233        assert_eq!(gnu, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1234        // Darwin and freestanding name no libc, so the answer does too, even though the parser
1235        // will hand this type a Darwin triple with `gnu` on the end.
1236        let macos = Triple::from_tuple("aarch64-macos".parse().unwrap()).unwrap();
1237        assert_eq!(macos, Triple::new(Arch::Aarch64, Os::Darwin, Env::None));
1238        let bare = Triple::from_tuple("riscv64-none".parse().unwrap()).unwrap();
1239        assert_eq!(bare, Triple::new(Arch::Riscv64, Os::None, Env::None));
1240        // The two Windows environments stay apart, which is the whole reason the narrowing keeps
1241        // the environment there and nowhere else.
1242        let mingw = Triple::from_tuple("x86_64-windows-gnu".parse().unwrap()).unwrap();
1243        assert_eq!(mingw.env, Env::Gnu);
1244        let msvc = Triple::from_tuple("x86_64-windows-msvc".parse().unwrap()).unwrap();
1245        assert_eq!(msvc.env, Env::Msvc);
1246        // A version on either side narrows to the same triple as the tuple without it.
1247        let pinned = Triple::from_tuple("aarch64-macos.13".parse().unwrap()).unwrap();
1248        assert_eq!(pinned, macos);
1249        let old = Triple::from_tuple("x86_64-linux-gnu.2.28".parse().unwrap()).unwrap();
1250        assert_eq!(old, gnu);
1251    }
1252
1253    #[test]
1254    fn from_tuple_says_no_rather_than_saying_something_near() {
1255        // Twenty five of the forty two rows have no triple, and the answer is `None` rather than
1256        // a neighbour. `rucc-abi` knows the scalar layout of every one of these and this type
1257        // cannot hold any of them, which is the gap the record layout engine inherits.
1258        for tuple in [
1259            "i686-linux-gnu",
1260            "armv7-linux-gnueabihf",
1261            "s390x-linux-gnu",
1262            "powerpc64le-linux-gnu",
1263            "loongarch64-linux-gnu",
1264            "x86_64-linux-gnux32",
1265            "aarch64-linux-android",
1266            "aarch64-ios",
1267            "wasm32-wasip1",
1268            "x86_64-freebsd",
1269        ] {
1270            let target = tuple.parse().unwrap();
1271            assert_eq!(Triple::from_tuple(target), None, "{tuple}");
1272        }
1273    }
1274
1275    #[test]
1276    fn mingw_and_msvc_are_one_operating_system_with_two_long_doubles() {
1277        // The narrowing in `Triple::tuple` keeps the environment on Windows for this reason and
1278        // throws it away everywhere else. GCC's Windows targets keep the eighty bit `long double`
1279        // and Microsoft's make it a `double`, on the same processor and the same OS.
1280        let mingw = TargetInfo::new("x86_64-pc-windows-gnu".parse().unwrap());
1281        assert_eq!(mingw.long_double_width, 128);
1282        assert_eq!(mingw.long_double_format, Format::X87Extended);
1283
1284        let msvc = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1285        assert_eq!(msvc.long_double_width, 64);
1286        assert_eq!(msvc.long_double_format, Format::Double);
1287
1288        // And they agree about everything the operating system does decide.
1289        assert_eq!(mingw.long_width, msvc.long_width);
1290        assert_eq!(mingw.wchar_width, msvc.wchar_width);
1291        assert_eq!(mingw.object_format, msvc.object_format);
1292    }
1293
1294    #[test]
1295    fn wchar_t_divides_the_targets_in_two_directions_at_once() {
1296        // Windows narrows it to sixteen bits, which makes a wide string UTF-16 there and
1297        // UTF-32 everywhere else, and AArch64 Linux makes it unsigned without narrowing it.
1298        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1299        assert_eq!((windows.wchar_width, windows.wchar_is_signed), (16, false));
1300        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1301        assert_eq!((arm.wchar_width, arm.wchar_is_signed), (32, false));
1302        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1303        assert_eq!((linux.wchar_width, linux.wchar_is_signed), (32, true));
1304        // Apple keeps it signed on the same processor where Linux does not, in the same way it
1305        // keeps plain `char` signed there.
1306        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1307        assert_eq!((mac.wchar_width, mac.wchar_is_signed), (32, true));
1308    }
1309
1310    #[test]
1311    fn va_list_is_the_psabis_type_and_not_one_type_with_four_spellings() {
1312        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1313        assert_eq!(linux.va_list, Some(VaList::SysV));
1314        // x86-64 Darwin follows SysV here, and AArch64 Darwin does not follow AAPCS64.
1315        let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
1316        assert_eq!(mac.va_list, Some(VaList::SysV));
1317        let arm_mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1318        assert_eq!(arm_mac.va_list, Some(VaList::CharPointer));
1319        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1320        assert_eq!(arm.va_list, Some(VaList::Aapcs));
1321        // Windows passes everything one way on both processors, so both get the simple one.
1322        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1323        assert_eq!(win.va_list, Some(VaList::CharPointer));
1324        let arm_win = TargetInfo::new("aarch64-pc-windows-msvc".parse().unwrap());
1325        assert_eq!(arm_win.va_list, Some(VaList::CharPointer));
1326        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
1327        assert_eq!(riscv.va_list, Some(VaList::VoidPointer));
1328    }
1329
1330    #[test]
1331    fn two_targets_agree_on_the_width_of_long_double_and_not_on_the_type() {
1332        // Sixteen bytes on both, and a different number in them: the x87 format has sixty four
1333        // bits of significand and quad precision has a hundred and thirteen, so a constant
1334        // converted for one is the wrong bits for the other.
1335        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1336        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1337        assert_eq!(x86.long_double_width, arm.long_double_width);
1338        assert_eq!(x86.long_double_format, Format::X87Extended);
1339        assert_eq!(arm.long_double_format, Format::Quad);
1340        assert_eq!(x86.long_double_format.precision(), 64);
1341        assert_eq!(arm.long_double_format.precision(), 113);
1342        // Windows keeps the name and drops the type, the way Apple does.
1343        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1344        assert_eq!(windows.long_double_format, Format::Double);
1345    }
1346
1347    #[test]
1348    fn float64x_follows_the_processor_where_long_double_follows_the_operating_system() {
1349        // `_Float64x` is the widest format the hardware has, and no ABI takes it away the way
1350        // Apple and Windows take `long double` away. So the two fields say the same thing on
1351        // Linux and disagree everywhere else, which is the whole reason there are two of them.
1352        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1353        assert_eq!(x86.float64x_format, Some(Format::X87Extended));
1354        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1355        assert_eq!(arm.float64x_format, Some(Format::Quad));
1356        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
1357        assert_eq!(riscv.float64x_format, Some(Format::Quad));
1358
1359        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1360        assert_eq!(mac.long_double_format, Format::Double);
1361        assert_eq!(mac.float64x_format, Some(Format::Quad));
1362        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1363        assert_eq!(windows.long_double_format, Format::Double);
1364        assert_eq!(windows.float64x_format, Some(Format::X87Extended));
1365    }
1366
1367    #[test]
1368    fn the_named_floating_types_are_not_on_every_machine() {
1369        // gcc 13, measured with the cross compilers rather than reasoned about. `_Float16` is on
1370        // three of these seven and `_Float128` is on six, and the two lists are not the same
1371        // list, which is why there are two fields.
1372        // The three field triple spells three architectures, and four of these rows are not
1373        // among them, so this asks the tuple the way the layout tests do.
1374        let of = |tuple: &str| TargetInfo::for_tuple(tuple.parse().expect("a row in the table"));
1375        let rows = [
1376            ("x86_64-linux-gnu", true, true),
1377            ("i686-linux-gnu", false, true),
1378            ("aarch64-linux-gnu", true, true),
1379            ("armv7-linux-gnueabihf", false, false),
1380            ("powerpc64le-linux-gnu", false, true),
1381            ("riscv64-linux-gnu", true, true),
1382            ("s390x-linux-gnu", false, true),
1383        ];
1384        for (tuple, float16, float128) in rows {
1385            let target = of(tuple);
1386            assert_eq!(target.has_float16, float16, "{tuple} `_Float16`");
1387            assert_eq!(target.has_float128, float128, "{tuple} `_Float128`");
1388        }
1389        // The operating system has nothing to do with it, the way it has nothing to do with
1390        // `_Float64x`, so Apple and Windows keep both types.
1391        assert!(of("aarch64-apple-darwin").has_float16);
1392        assert!(of("x86_64-pc-windows-msvc").has_float128);
1393    }
1394
1395    #[test]
1396    fn the_decimal_types_are_on_the_one_row_the_back_end_calls_routines_for() {
1397        let of = |tuple: &str| TargetInfo::for_tuple(tuple.parse().expect("a row in the table"));
1398        assert!(of("x86_64-linux-gnu").has_decimal_float);
1399        for tuple in ["aarch64-linux-gnu", "x86_64-pc-windows-msvc", "aarch64-apple-darwin"] {
1400            assert!(!of(tuple).has_decimal_float, "{tuple}");
1401        }
1402    }
1403
1404    #[test]
1405    fn the_object_format_follows_the_operating_system() {
1406        assert_eq!(Os::Linux.object_format(), ObjectFormat::Elf);
1407        assert_eq!(Os::Darwin.object_format(), ObjectFormat::MachO);
1408        assert_eq!(Os::Windows.object_format(), ObjectFormat::Coff);
1409    }
1410
1411    #[test]
1412    fn a_target_carries_its_registers_and_says_so_when_it_has_none() {
1413        let of = |triple: &str| TargetInfo::new(triple.parse().unwrap());
1414        let linux = of("x86_64-unknown-linux-gnu");
1415        assert_eq!(linux.regs.reg_named("rdi"), Some((x86_64::GPR, x86_64::RDI)));
1416        assert_eq!(linux.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
1417        // Apple's x86-64 is SysV and Windows is the one that is not.
1418        let apple = of("x86_64-apple-darwin");
1419        assert_eq!(apple.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
1420        let windows = of("x86_64-pc-windows-msvc");
1421        assert_eq!(windows.regs.len(x86_64::GPR), 16);
1422        assert_eq!(windows.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RCX));
1423        let arm = of("aarch64-unknown-linux-gnu");
1424        assert_eq!(arm.regs.len(aarch64::GPR), 32);
1425        assert_eq!(arm.call_regs.map(|regs| regs.int_args[0]), Some(aarch64::x(0)));
1426        assert_eq!(arm.call_regs.map(|regs| regs.red_zone), Some(0));
1427        assert_eq!(of("aarch64-apple-darwin").call_regs.map(|regs| regs.red_zone), Some(128));
1428        // Windows on AArch64 has registers of its own rather than Linux's, both runtimes alike.
1429        for triple in ["aarch64-pc-windows-msvc", "aarch64-pc-windows-gnu"] {
1430            let regs = of(triple).call_regs.expect("a convention");
1431            assert!(std::ptr::eq(regs, &aarch64::WINDOWS), "{triple}");
1432        }
1433        let riscv = of("riscv64-unknown-linux-gnu");
1434        assert!(riscv.regs.is_empty());
1435        assert!(riscv.call_regs.is_none());
1436    }
1437
1438    /// The two maps from a triple, held against each other.
1439    ///
1440    /// A target's registers and a target's ABI are chosen by two separate matches, one here and one
1441    /// in `rucc_abi::abis::for_target`, and [`CallRegs::abi`] is the link between them. Two matches
1442    /// that can disagree are the thing this crate must not have, so every triple with registers is
1443    /// asked both questions and the answers have to be the same description. What it catches is a
1444    /// target added to one match and not the other, which is a compiler that puts the value in the
1445    /// register one ABI names and the form another one asked for.
1446    #[test]
1447    fn the_registers_and_the_abi_a_target_gets_are_the_same_convention() {
1448        let mut checked = 0;
1449        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
1450            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
1451                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
1452                    let triple = Triple::new(arch, os, env);
1453                    let info = TargetInfo::new(triple);
1454                    let Some(regs) = info.call_regs else { continue };
1455                    let described = rucc_abi::abis::for_target(info.tuple)
1456                        .unwrap_or_else(|| panic!("{triple} has registers and no ABI"));
1457                    assert!(
1458                        std::ptr::eq(regs.abi, described),
1459                        "{triple} has the registers of {} and the ABI of {}",
1460                        regs.abi.name,
1461                        described.name
1462                    );
1463                    checked += 1;
1464                }
1465            }
1466        }
1467        assert!(checked > 0, "no target has registers, so this asserted nothing");
1468    }
1469
1470    /// The timing model, which follows the register file: an architecture with no backend has
1471    /// nothing to measure and says so rather than borrowing a neighbour's numbers.
1472    #[test]
1473    fn a_target_carries_the_model_its_schedules_were_chosen_with() {
1474        let of = |triple: &str| TargetInfo::new(triple.parse().unwrap());
1475        let linux = of("x86_64-unknown-linux-gnu");
1476        let timing = linux.timing.expect("x86-64 has a backend and so has a model");
1477        assert!(timing.model.contains("Skylake"), "{}", timing.model);
1478        assert!(!timing.accurate, "and it says it is not a cycle accurate one");
1479        assert_eq!(timing.of("x64.imul_rr_64").map(|cost| cost.unit), Some(Unit::Mul));
1480
1481        // The same model whatever the operating system, since a model is about the processor.
1482        assert_eq!(of("x86_64-apple-darwin").timing, linux.timing);
1483        assert_eq!(of("x86_64-pc-windows-msvc").timing, linux.timing);
1484
1485        assert!(of("aarch64-unknown-linux-gnu").timing.is_none(), "nobody has measured it here");
1486    }
1487
1488    #[test]
1489    fn the_host_triple_is_one_we_support() {
1490        // Every host in spec/15-testing.md section 15.7 must be recognised, and CI runs on
1491        // all three, so a failure here means a host we claim support for stopped resolving.
1492        let host = Triple::host().expect("the host must be a supported target");
1493        assert_eq!(host.to_string().parse::<Triple>().unwrap(), host);
1494    }
1495}