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 and RISC-V's arrive with their backends.
33//! Machine models land in `M6`.
34//!
35//! This crate is tier 3 in `spec/18-package-layout.md` section 18.5: its Rust API is
36//! explicitly unstable and will change without a major version bump.
37
38#![doc(html_root_url = "https://docs.rs/rucc-target/0.10.19")]
39
40use std::fmt;
41use std::str::FromStr;
42
43use rucc_abi::DataLayout;
44use rucc_base::float::Format;
45use rucc_tuple::{self as tuple, TargetTuple};
46
47mod abi;
48mod branch;
49mod frame;
50mod operand;
51mod regs;
52pub mod x86_64;
53
54pub use crate::abi::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot};
55pub use crate::branch::{BranchInsts, Fusion};
56pub use crate::frame::{ClassMoves, FrameInsts, Probe};
57pub use crate::operand::{Constraint, OperandDesc, Role};
58pub use crate::regs::{
59    CallRegs, ClassInfo, Guard, PhysReg, Places, RegClass, RegFile, Segment, Trace, Where,
60};
61
62/// A target architecture.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
65// match that needs to change, in this workspace and in anyone else's code. That is
66// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
67// target is a data change: the compiler tells you every place the data is read.
68pub enum Arch {
69    /// x86-64, the first target and the one `M3` brings up.
70    X86_64,
71    /// AArch64, the second target, `M6`.
72    Aarch64,
73    /// 64-bit RISC-V. `spec/10-backend.md` calls this the middle-end canary, because it has
74    /// no condition codes and no complex addressing modes, so anything the middle end got
75    /// away with on x86-64 shows up here.
76    Riscv64,
77}
78
79impl Arch {
80    /// Pointer width in bits.
81    pub const fn pointer_width(self) -> u32 {
82        match self {
83            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => 64,
84        }
85    }
86
87    /// Whether the target is little-endian.
88    pub const fn is_little_endian(self) -> bool {
89        match self {
90            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => true,
91        }
92    }
93
94    /// The name as it appears in a triple.
95    pub const fn as_str(self) -> &'static str {
96        match self {
97            Arch::X86_64 => "x86_64",
98            Arch::Aarch64 => "aarch64",
99            Arch::Riscv64 => "riscv64",
100        }
101    }
102}
103
104/// The operating system a target runs on.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
106// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
107// match that needs to change, in this workspace and in anyone else's code. That is
108// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
109// target is a data change: the compiler tells you every place the data is read.
110pub enum Os {
111    /// Linux, hosted or freestanding.
112    Linux,
113    /// Apple platforms. `spec/12-abi-and-runtime.md` section 12.3 lists the four places
114    /// Apple diverges from AAPCS64, and every one of them is a real bug if missed.
115    Darwin,
116    /// Windows.
117    Windows,
118    /// No operating system, which is what `-ffreestanding` kernel work looks like.
119    None,
120}
121
122impl Os {
123    /// The name as it appears in a triple.
124    pub const fn as_str(self) -> &'static str {
125        match self {
126            Os::Linux => "linux",
127            Os::Darwin => "darwin",
128            Os::Windows => "windows",
129            Os::None => "none",
130        }
131    }
132
133    /// The object file format this operating system uses.
134    pub const fn object_format(self) -> ObjectFormat {
135        match self {
136            Os::Linux | Os::None => ObjectFormat::Elf,
137            Os::Darwin => ObjectFormat::MachO,
138            Os::Windows => ObjectFormat::Coff,
139        }
140    }
141}
142
143/// The C runtime and ABI variant.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
145// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
146// match that needs to change, in this workspace and in anyone else's code. That is
147// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
148// target is a data change: the compiler tells you every place the data is read.
149pub enum Env {
150    /// The default for the operating system.
151    None,
152    /// glibc.
153    Gnu,
154    /// musl.
155    Musl,
156    /// The MSVC ABI.
157    Msvc,
158}
159
160impl Env {
161    /// The name as it appears in a triple, if it appears at all.
162    pub const fn as_str(self) -> &'static str {
163        match self {
164            Env::None => "none",
165            Env::Gnu => "gnu",
166            Env::Musl => "musl",
167            Env::Msvc => "msvc",
168        }
169    }
170}
171
172/// The object file format to emit.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
174// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
175// match that needs to change, in this workspace and in anyone else's code. That is
176// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
177// target is a data change: the compiler tells you every place the data is read.
178pub enum ObjectFormat {
179    /// ELF.
180    Elf,
181    /// Mach-O.
182    MachO,
183    /// COFF.
184    Coff,
185    /// WebAssembly, which is a format for a module rather than for a machine's object file and
186    /// is in this list because the target table has two rows that emit one.
187    Wasm,
188}
189
190impl ObjectFormat {
191    /// The name used in diagnostics and in `--print-config`.
192    pub const fn as_str(self) -> &'static str {
193        match self {
194            ObjectFormat::Elf => "elf",
195            ObjectFormat::MachO => "macho",
196            ObjectFormat::Coff => "coff",
197            ObjectFormat::Wasm => "wasm",
198        }
199    }
200
201    /// The same format as [`rucc_tuple::ObjectFormat`] names it.
202    ///
203    /// The two enumerations exist because the tuple describes forty two targets and this crate
204    /// describes what the compiler emits for one, and they will stay separate for as long as that
205    /// is true. This is the one place they are put side by side.
206    #[must_use]
207    pub const fn from_tuple(format: tuple::ObjectFormat) -> Self {
208        match format {
209            tuple::ObjectFormat::Elf => ObjectFormat::Elf,
210            tuple::ObjectFormat::MachO => ObjectFormat::MachO,
211            tuple::ObjectFormat::Coff => ObjectFormat::Coff,
212            tuple::ObjectFormat::Wasm => ObjectFormat::Wasm,
213        }
214    }
215}
216
217/// A target triple.
218///
219/// We accept the LLVM-style `arch-vendor-os-env` form because that is what build systems
220/// pass, and we normalise it to the three fields we actually branch on. The vendor field is
221/// parsed and discarded: no decision in the compiler depends on it, and keeping it would
222/// invite one.
223#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
224pub struct Triple {
225    /// The architecture.
226    pub arch: Arch,
227    /// The operating system.
228    pub os: Os,
229    /// The runtime and ABI variant.
230    pub env: Env,
231}
232
233impl Triple {
234    /// A triple from its three parts.
235    pub const fn new(arch: Arch, os: Os, env: Env) -> Self {
236        Self { arch, os, env }
237    }
238
239    /// The same machine as a [`TargetTuple`], which is what the layout and ABI descriptions are
240    /// written over.
241    ///
242    /// The tuple carries ten fields and this carries three, so this fills the other seven in from
243    /// their defaults, and every one of those defaults is the answer for the targets this type can
244    /// spell. There is no `x32` here and no big-endian AArch64, so the data model and the byte
245    /// order follow the architecture, and the sub-architecture, the versions and the float ABI have
246    /// nothing to say about any of the combinations.
247    ///
248    /// The environment is narrowed rather than copied across. This type will hold
249    /// `Triple { os: Darwin, env: Gnu }`, because its parser takes the fields by content and
250    /// `aarch64-apple-darwin-gnu` is a string somebody can type, and that is not a machine: a
251    /// Darwin target has one libc and it is not glibc. A tuple refuses to describe one, so the
252    /// pairs that are not machines are mapped to the environment the operating system actually
253    /// has.
254    ///
255    /// # Panics
256    ///
257    /// Never, for a triple this type can hold, which `every_triple_describes_a_machine` checks by
258    /// building all forty eight of them.
259    #[must_use]
260    pub fn tuple(self) -> TargetTuple {
261        let arch = match self.arch {
262            Arch::X86_64 => tuple::Arch::X86_64,
263            Arch::Aarch64 => tuple::Arch::Aarch64,
264            Arch::Riscv64 => tuple::Arch::Riscv64,
265        };
266        let os = match self.os {
267            Os::Linux => tuple::Os::Linux,
268            // macOS rather than iOS, because the three field triple cannot tell them apart and
269            // this compiler is hosted on the one and not on the other.
270            Os::Darwin => tuple::Os::MacOs,
271            Os::Windows => tuple::Os::Windows,
272            Os::None => tuple::Os::None,
273        };
274        let env = match (self.os, self.env) {
275            (Os::Linux, Env::Musl) => tuple::Env::Musl,
276            (Os::Linux, _) => tuple::Env::Gnu,
277            // mingw-w64 is a real Windows environment and the one place `gnu` survives the
278            // narrowing, because it has a different `long double` from MSVC on the same OS.
279            (Os::Windows, Env::Gnu) => tuple::Env::Gnu,
280            (Os::Windows, _) => tuple::Env::Msvc,
281            // Darwin and freestanding have no libc to name.
282            (Os::Darwin | Os::None, _) => tuple::Env::None,
283        };
284        TargetTuple::builder(arch, os)
285            .env(env)
286            .build()
287            .expect("every triple this type can hold describes a machine")
288    }
289
290    /// The triple that describes the same machine as `target`, if this type can spell it.
291    ///
292    /// The inverse of [`Triple::tuple`], and computed by running that function over every triple
293    /// there is rather than by writing the narrowing out a second time. A second table would be a
294    /// second thing to keep in step, and the failure it invites is not a compile error: it is one
295    /// row of the matrix quietly answering as a neighbour.
296    ///
297    /// It returns `None` for most of the target table, and that is the honest answer rather than a
298    /// gap to be papered over. `rucc-abi` describes the scalar layout of all forty two rows, and
299    /// this type holds three fields with three architectures in the first, so seventeen of those
300    /// rows have a [`TargetInfo`] and the other twenty five do not. Anything that needs to lay a
301    /// record out for `s390x-linux-gnu` needs that gap closed rather than an approximation of it.
302    ///
303    /// The environment of the answer is the narrowed one, so the triple this gives back is the
304    /// canonical spelling of that machine: `Env::None` on Darwin and on a freestanding target,
305    /// never the `Env::Gnu` that a parser will accept from a string somebody typed.
306    #[must_use]
307    pub fn from_tuple(target: TargetTuple) -> Option<Triple> {
308        // Four triples narrow onto `x86_64-linux-gnu`, because a Darwin triple claiming glibc is
309        // a string somebody can type and not a machine. So a match is not enough on its own: the
310        // answer is the candidate whose environment came through the narrowing unchanged, and
311        // anything else is only a fallback for the day a narrowing loses a spelling entirely.
312        let mut fallback = None;
313        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
314            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
315                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
316                    let candidate = Triple::new(arch, os, env);
317                    if candidate.tuple() != target {
318                        continue;
319                    }
320                    // By name rather than by a match on the pair, so that an environment added to
321                    // either enumeration does not need a line here. The one name the two spell
322                    // differently is the absent one, which the tuple writes as nothing.
323                    let survived = match env {
324                        Env::None => target.env() == tuple::Env::None,
325                        _ => env.as_str() == target.env().as_str(),
326                    };
327                    if survived {
328                        return Some(candidate);
329                    }
330                    fallback.get_or_insert(candidate);
331                }
332            }
333        }
334        fallback
335    }
336
337    /// The triple of the machine this compiler is running on.
338    ///
339    /// Used as the default target, which is what makes `rucc hello.c` work with no flags.
340    /// Unknown host combinations are not an error here: they are reported by the driver,
341    /// where there is somewhere to report them to.
342    pub fn host() -> Option<Self> {
343        let arch = match std::env::consts::ARCH {
344            "x86_64" => Arch::X86_64,
345            "aarch64" => Arch::Aarch64,
346            "riscv64" => Arch::Riscv64,
347            _ => return None,
348        };
349        // Which libc this is matters, and `std::env::consts` does not say. A compiler built on
350        // Alpine and defaulting to `x86_64-unknown-linux-gnu` describes a machine it is not
351        // running on: musl and glibc disagree about `int_fast16_t` among other things, and a
352        // header that is written out of the predefined type names picks the disagreement up.
353        // The libc rucc itself was linked against is the best evidence available about the one
354        // the code it compiles will be linked against, and it is right on every machine where
355        // rucc was built for the machine it runs on.
356        let linux = if cfg!(target_env = "musl") { Env::Musl } else { Env::Gnu };
357        let (os, env) = match std::env::consts::OS {
358            "linux" => (Os::Linux, linux),
359            "macos" => (Os::Darwin, Env::None),
360            "windows" => (Os::Windows, Env::Msvc),
361            _ => return None,
362        };
363        Some(Self::new(arch, os, env))
364    }
365}
366
367impl fmt::Display for Triple {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        // Always four fields, always the same spelling, because this string ends up in
370        // `--print-config` output that people diff.
371        write!(f, "{}-unknown-{}-{}", self.arch.as_str(), self.os.as_str(), self.env.as_str())
372    }
373}
374
375/// Why a triple failed to parse.
376#[derive(Debug, Clone, PartialEq, Eq)]
377pub struct ParseTripleError {
378    /// The triple as given.
379    pub input: String,
380    /// What specifically was not recognised.
381    pub reason: &'static str,
382}
383
384impl fmt::Display for ParseTripleError {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        write!(f, "unsupported target triple `{}`: {}", self.input, self.reason)
387    }
388}
389
390impl std::error::Error for ParseTripleError {}
391
392impl FromStr for Triple {
393    type Err = ParseTripleError;
394
395    fn from_str(s: &str) -> Result<Self, Self::Err> {
396        let err = |reason| ParseTripleError { input: s.to_owned(), reason };
397        let mut parts = s.split('-');
398
399        let arch = match parts.next() {
400            Some("x86_64" | "amd64") => Arch::X86_64,
401            Some("aarch64" | "arm64") => Arch::Aarch64,
402            Some("riscv64") => Arch::Riscv64,
403            _ => return Err(err("unknown architecture")),
404        };
405
406        // The vendor field is optional in practice. `x86_64-linux-gnu` and
407        // `x86_64-unknown-linux-gnu` both occur in the wild and mean the same thing, so the
408        // remaining fields are matched by content rather than by position.
409        let rest: Vec<&str> = parts.collect();
410        let mut os = None;
411        let mut env = None;
412        for part in &rest {
413            match *part {
414                "linux" => os = Some(Os::Linux),
415                "darwin" | "macos" | "macosx" | "ios" => os = Some(Os::Darwin),
416                "windows" | "win32" => os = Some(Os::Windows),
417                // `none` is the one token that means different things in the two positions.
418                // In `x86_64-unknown-none-elf` it is the operating system; in
419                // `aarch64-apple-darwin-none` it is the environment. Which one it is depends
420                // on whether an operating system has already been seen, and that rule is what
421                // makes `Display` round-trip through `FromStr`.
422                "none" if os.is_none() => os = Some(Os::None),
423                "none" => env = Some(Env::None),
424                "elf" => os = os.or(Some(Os::None)),
425                "gnu" | "gnueabi" | "gnueabihf" => env = Some(Env::Gnu),
426                "musl" | "musleabi" | "musleabihf" => env = Some(Env::Musl),
427                "msvc" => env = Some(Env::Msvc),
428                _ => {}
429            }
430        }
431
432        let os = os.ok_or_else(|| err("unknown operating system"))?;
433        let env = env.unwrap_or(match os {
434            Os::Linux => Env::Gnu,
435            Os::Windows => Env::Msvc,
436            Os::Darwin | Os::None => Env::None,
437        });
438        Ok(Self::new(arch, os, env))
439    }
440}
441
442/// The facts about a target that the compiler reads instead of hard-coding.
443///
444/// This is the whole of what a pass is allowed to know about where its output will run.
445/// It grows, and every field added here is one fewer `#[cfg]` somewhere it should not be.
446#[derive(Debug, Clone, PartialEq, Eq)]
447#[non_exhaustive]
448pub struct TargetInfo {
449    /// The machine this describes, as the ten field tuple rather than as a three field triple.
450    ///
451    /// It is the tuple because a record layout is a question every row of the target table has an
452    /// answer to, and a triple can spell fifteen of the forty two. Nothing else in this type had
453    /// to change to widen it: every field below is already derived from `rucc-abi`'s description
454    /// of this tuple, and the ones that were not were the bugs.
455    pub tuple: TargetTuple,
456    /// The sizes, the alignments and the signedness this target's headers were written against.
457    ///
458    /// The widths below are views of this and the alignments are not, which is the reason it is
459    /// kept whole. A `long long` is eight bytes on every row of the table and is aligned to four
460    /// on System V i386 and to eight everywhere else, and no width can say that.
461    pub scalars: DataLayout,
462    /// Width of a pointer in bits.
463    pub pointer_width: u32,
464    /// Whether bytes are ordered little end first.
465    pub little_endian: bool,
466    /// Whether a bare `char` is signed.
467    ///
468    /// Signed on x86-64 and unsigned on AArch64 Linux, which is the classic source of code
469    /// that works on one and not the other, so it is data rather than an assumption.
470    pub char_is_signed: bool,
471    /// Width of `long` in bits. This is the field that separates the LP64 world from
472    /// Windows LLP64.
473    pub long_width: u32,
474    /// Width of `long double` in bits: 80 bits of x87 stored in 128 on every x86-64 target but
475    /// MSVC, 128 of true quad precision on AArch64 Linux and RISC-V, and 64 on Apple's AArch64 and
476    /// under MSVC.
477    ///
478    /// Apple's x86-64 is not one of the 64-bit ones, which is the trap. The change to a `double`
479    /// came with AArch64 and the Intel answer stayed as it was, so `x86_64-apple-darwin` and
480    /// `x86_64-unknown-linux-gnu` agree here and `aarch64-apple-darwin` is the odd one.
481    pub long_double_width: u32,
482    /// The format `long double` actually is, which the width does not say.
483    ///
484    /// It is 128 bits wide on SysV x86-64 and on AArch64 Linux and the two are not the same
485    /// type: one is the x87 eighty bit format padded out to sixteen bytes and the other is
486    /// true quad precision with a hundred and thirteen bits of significand. Anything that
487    /// converts a constant or folds one has to know which, and the width alone cannot say.
488    pub long_double_format: Format,
489    /// The format `_Float64x` is, which is the widest format the target has short of a software
490    /// one.
491    ///
492    /// It follows the architecture and not the operating system, which is what makes it worth a
493    /// field of its own next to `long double`. Apple and Windows define `long double` as a
494    /// `double` and neither of them takes `_Float64x` down with it: the type has to be wider
495    /// than a `_Float64`, so it is the x87 eighty bit format on x86-64 and quad precision on
496    /// AArch64 and RISC-V wherever it is written.
497    ///
498    /// [`None`] on a machine whose widest format is a `double`, which is 32-bit ARM and wasm32.
499    /// The type does not exist there and neither reference defines the macros that describe it,
500    /// so the honest answer is that there is no format rather than a `double` in its place.
501    pub float64x_format: Option<Format>,
502    /// Width of `wchar_t` in bits, which decides what a wide literal is encoded in.
503    ///
504    /// It is 16 on Windows, so a wide string there is UTF-16 and a character outside the basic
505    /// plane takes two elements, and 32 everywhere else, where a wide string is UTF-32 and no
506    /// character takes more than one.
507    pub wchar_width: u32,
508    /// Whether `wchar_t` is signed.
509    ///
510    /// x86-64 Linux makes it a signed `int` and AArch64 Linux makes it an `unsigned int`,
511    /// following the psABI's rule for plain `char`, so `L'\xffffffff'` is minus one on one of
512    /// them and four billion on the other.
513    pub wchar_is_signed: bool,
514    /// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
515    ///
516    /// Above 64 bits the psABIs stop treating a `_BitInt` like a standard integer type and
517    /// start treating it like an array of these, so its size is rounded up to a multiple of
518    /// this and its alignment is this. It is 64 on x86-64 and RISC-V and 128 on AArch64, which
519    /// is why `_BitInt(65)` is sixteen bytes aligned to eight on one and sixteen bytes aligned
520    /// to sixteen on the other. Measured with clang 18 on x86-64 Linux and clang on AArch64
521    /// Darwin rather than read off the documents.
522    pub bit_int_granule: u32,
523    /// The widest access, in bits, this machine performs atomically without taking a lock.
524    ///
525    /// It is what `__atomic_always_lock_free` and `__atomic_is_lock_free` answer from, and it is
526    /// a claim about what this compiler emits rather than about what the processor is capable of.
527    /// Sixty four on every target here. x86-64 does sixteen bytes atomically with `cmpxchg16b`,
528    /// which is not in the baseline the psABI names and which nothing in this compiler writes, and
529    /// AArch64 does the same with its pair instructions, which nothing writes either. A target
530    /// that answered yes for sixteen bytes and then called a library that has to take a lock for
531    /// them would have two answers to one question, and the wrong one is the one in the header.
532    pub lock_free_width: u32,
533    /// The object format to emit.
534    pub object_format: ObjectFormat,
535    /// How bit-fields are allocated into storage, which is the one record layout question where
536    /// two targets in this table run different algorithms rather than the same one over different
537    /// numbers.
538    pub bit_field_style: BitFieldStyle,
539    /// Whether an unnamed bit-field raises the record's alignment the way a named one does.
540    ///
541    /// Almost everywhere it does not, which is why `struct { char c; int :20; }` is four bytes
542    /// aligned to one on x86-64 and four aligned to four with the field named. AAPCS64 says
543    /// otherwise and says it for the zero width member too, so `struct { unsigned :0; }` is
544    /// aligned to four on AArch64 Linux and to one on Apple's AArch64, on Windows on AArch64, on
545    /// x86-64 and on RISC-V. Measured with the pinned reference across every row that has one,
546    /// because it is neither an architecture rule nor an operating system rule: it is the ABI, and
547    /// Apple and Microsoft each dropped it.
548    ///
549    /// Windows says yes as well, and there it is not AAPCS64 but Microsoft's own rule, which is
550    /// why the two facts are separate fields rather than one. In a `union` the Microsoft rule goes
551    /// further and no bit-field contributes alignment at all, named or not, so this field is only
552    /// half the answer there and [`BitFieldStyle`] carries the other half.
553    pub unnamed_bit_field_aligns: bool,
554    /// How large a record with no storage in it is, in bytes, before its alignment is applied.
555    ///
556    /// Zero everywhere but MSVC, where it is four. A `struct` with no members is not C at all, it
557    /// is a GNU extension, and C++ gives it a size of one, so there is no standard to read the
558    /// answer out of and the number has to come from whatever else compiles for the target. On
559    /// mingw that is GCC and the answer is zero. On MSVC it is clang, because MSVC itself rejects
560    /// the declaration outright, and clang's Microsoft record layout gives it four bytes and gives
561    /// an array of three of them twelve. So this is a fact about the environment and not about the
562    /// operating system, which is the one place in this type where those two come apart in that
563    /// direction.
564    ///
565    /// It covers a record with no members and a record whose only members occupy nothing, which is
566    /// the zero width bit-field, the zero length array and the flexible array member. All four
567    /// were measured and all four agree.
568    pub empty_record_size: u64,
569    /// What `__builtin_va_list` is, which is the type every `va_list` in every header is a
570    /// typedef of.
571    ///
572    /// [`None`] on a target whose answer is a type this crate does not build yet. 32-bit ARM's is
573    /// a structure of one pointer and s390x's is a structure of four members, and neither is any
574    /// of the four below. A target with no backend cannot compile a call to `va_arg` in any case,
575    /// so saying so beats naming a neighbour's type and having a header believe it.
576    pub va_list: Option<VaList>,
577    /// The registers the machine has, which is [`RegFile::EMPTY`] for an architecture nothing
578    /// has described yet.
579    pub regs: &'static RegFile,
580    /// Which registers the calling convention gives which job, or `None` while the
581    /// architecture has no register file to name them out of.
582    pub call_regs: Option<&'static CallRegs>,
583}
584
585/// The type a target's `__builtin_va_list` is.
586///
587/// A variable argument list is the one place a psABI dictates a C type rather than how a type
588/// travels, and the four answers below are not four spellings of one thing: `sizeof(va_list)` is
589/// eight bytes on Apple's AArch64 and thirty two on Linux's, and on SysV x86-64 a `va_list` is an
590/// array, so a `va_list` passed to a function is passed as a pointer and one assigned to another
591/// is a constraint violation rather than a copy. Code in the wild depends on all of that.
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a fifth answer here is
594// a fifth type to build, and every place that builds one should stop compiling until it does.
595pub enum VaList {
596    /// `char *`, which is what a target whose arguments are all passed in one place needs: the
597    /// address of the next argument and nothing else. Apple's AArch64 and both Windows targets.
598    CharPointer,
599    /// `void *`, which is the RISC-V psABI's spelling of the same thing.
600    VoidPointer,
601    /// `struct __va_list_tag { unsigned gp_offset, fp_offset; void *overflow_arg_area,
602    /// *reg_save_area; } [1]`, the SysV x86-64 one. Arguments arrive in two register files and
603    /// on the stack, so the list is a cursor into each, and the array of one is what makes
604    /// passing it to `vfprintf` pass its address.
605    SysV,
606    /// `struct __va_list { void *__stack, *__gr_top, *__vr_top; int __gr_offs, __vr_offs; }`,
607    /// the AAPCS64 one. The same idea as SysV's, counting down from the top of each save area
608    /// rather than up from the bottom, and not an array.
609    Aapcs,
610}
611
612impl VaList {
613    /// The name used in `--print-config`.
614    #[must_use]
615    pub const fn as_str(self) -> &'static str {
616        match self {
617            VaList::CharPointer => "char-pointer",
618            VaList::VoidPointer => "void-pointer",
619            VaList::SysV => "sysv",
620            VaList::Aapcs => "aapcs",
621        }
622    }
623}
624
625/// How a target allocates bit-fields into storage.
626///
627/// Everything else about laying a record out is one algorithm reading different sizes and
628/// alignments per target. This is not: the two answers below place the same members at different
629/// offsets and give the same struct different sizes, and no amount of changing what an `int` is
630/// turns one into the other. `struct { unsigned m:3; char c; }` is four bytes with the `char` at
631/// offset one under the first and eight bytes with it at offset four under the second.
632#[derive(Debug, Clone, Copy, PartialEq, Eq)]
633// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a third answer here is a
634// third algorithm to write, and every place that chooses between them should stop compiling until
635// it does.
636pub enum BitFieldStyle {
637    /// The Itanium C++ ABI's rule, which every psABI in this table except Windows follows. A
638    /// bit-field goes at the next free bit unless that would make it span more storage than its
639    /// own type occupies, in which case it starts at the next boundary of its alignment. Storage
640    /// is shared between members of different types freely, so `struct { char a:3; unsigned b:3; }`
641    /// is four bytes with both fields in the first one.
642    Itanium,
643    /// Microsoft's rule, which both Windows environments follow and not only MSVC. A run of
644    /// bit-fields is allocated into a unit the size and alignment of the declared type, and the
645    /// unit is closed both when the next member's declared type has a different size and when the
646    /// field does not fit in what is left. An ordinary member closes a unit too, and the closed
647    /// unit occupies its whole declared size whether or not the bits were used. So the same struct
648    /// is eight bytes: a one byte unit for the `char` and a four byte one for the `unsigned`,
649    /// aligned to four.
650    Microsoft,
651}
652
653impl BitFieldStyle {
654    /// The name used in `--print-config`.
655    #[must_use]
656    pub const fn as_str(self) -> &'static str {
657        match self {
658            BitFieldStyle::Itanium => "itanium",
659            BitFieldStyle::Microsoft => "microsoft",
660        }
661    }
662}
663
664/// A width in bits, from a size in bytes.
665///
666/// The fields here are widths because that is what a predefined macro and a diagnostic say, and a
667/// layout is sizes because that is what `sizeof` says. The conversion belongs at the one boundary
668/// between them rather than at every reader of one of these fields.
669fn bits(bytes: u64) -> u32 {
670    u32::try_from(bytes * 8).expect("no standard type is four billion bits wide")
671}
672
673impl TargetInfo {
674    /// The description of `triple`.
675    ///
676    /// The three field triple spells fifteen of the forty two rows of the target table, which is
677    /// every row with a backend and every row a driver will be handed today, so this is what the
678    /// compiler proper calls. [`TargetInfo::for_tuple`] is the one that answers for the whole
679    /// table.
680    #[must_use]
681    pub fn new(triple: Triple) -> Self {
682        Self::for_tuple(triple.tuple())
683    }
684
685    /// The description of `target`.
686    ///
687    /// Every row of the target table has one of these, whether or not there is a backend that can
688    /// emit code for it, because laying a record out and reading a header are questions that do
689    /// not need a backend. The fields that genuinely need one say so: [`TargetInfo::regs`] is
690    /// empty and [`TargetInfo::call_regs`] is [`None`] for an architecture whose register file is
691    /// not written down.
692    #[must_use]
693    pub fn for_tuple(target: TargetTuple) -> Self {
694        // Every size, alignment and signedness below is `rucc-abi`'s answer over the ten field
695        // tuple rather than a match written out here. They were written out here, and the copy was
696        // wrong about `x86_64-apple-darwin`, whose `long double` is the eighty bit x87 format in
697        // sixteen bytes and not a `double`: Apple made that change on AArch64 and left the Intel
698        // answer alone, and a rule keyed on the operating system takes both.
699        let layout = DataLayout::for_target(target);
700        // AArch64, RISC-V and everything else with a row and no backend have register files and
701        // this crate has not written them down yet. They arrive with the backends that need them,
702        // in M6 and M7.
703        let regs = match target.arch() {
704            tuple::Arch::X86_64 => &x86_64::REGS,
705            _ => &RegFile::EMPTY,
706        };
707        let call_regs = match (target.arch(), target.os()) {
708            (tuple::Arch::X86_64, tuple::Os::Windows) => Some(&x86_64::WIN64),
709            // Apple's x86-64 follows SysV, and its divergences from it are on AArch64.
710            (tuple::Arch::X86_64, _) => Some(&x86_64::SYSV),
711            _ => None,
712        };
713        Self {
714            tuple: target,
715            scalars: layout,
716            pointer_width: bits(layout.pointer_size),
717            little_endian: target.is_little_endian(),
718            char_is_signed: layout.char_is_signed,
719            long_width: bits(layout.long_size),
720            long_double_width: bits(layout.long_double.size),
721            long_double_format: layout.long_double.format,
722            float64x_format: float64x_format(target),
723            wchar_width: bits(layout.wchar_size),
724            wchar_is_signed: layout.wchar_is_signed,
725            bit_int_granule: bit_int_granule(target),
726            // Eight bytes everywhere, for the reason the field gives: it is the widest access this
727            // compiler writes an instruction for, and every one of these machines has a wider one
728            // that nothing here reaches. It is a claim about the code this compiler emits, so the
729            // day a backend emits a sixteen byte atomic is the day this stops being one number.
730            lock_free_width: 64,
731            object_format: ObjectFormat::from_tuple(target.object_format()),
732            bit_field_style: bit_field_style(target),
733            unnamed_bit_field_aligns: unnamed_bit_field_aligns(target),
734            // The environment and not the operating system, so `x86_64-windows-gnu` keeps GCC's
735            // zero while `x86_64-windows-msvc` takes clang's four.
736            empty_record_size: match target.env() {
737                tuple::Env::Msvc => 4,
738                _ => 0,
739            },
740            va_list: va_list(target),
741            regs,
742            call_regs,
743        }
744    }
745
746    /// The largest an object may be on this target, in bytes.
747    ///
748    /// `PTRDIFF_MAX`, which is what C 6.5.6 needs it to be: subtracting two pointers into one
749    /// object has to have an answer, and the answer has a `ptrdiff_t` to fit in. So an object
750    /// of exactly this many bytes is allowed and one byte more is not, which is the line GCC
751    /// draws too. It is the only size limit in the compiler and every layout question that has
752    /// one asks here rather than at whatever its own arithmetic happens to overflow at.
753    #[must_use]
754    pub const fn max_object_size(&self) -> u64 {
755        (1u64 << (self.pointer_width - 1)) - 1
756    }
757}
758
759/// The format `_Float64x` is, where the target has one.
760fn float64x_format(target: TargetTuple) -> Option<Format> {
761    match target.arch() {
762        // The x87 unit is on the machine whatever the operating system says a `long double` is,
763        // so `x86_64-apple-darwin` and `x86_64-windows-msvc` both have an eighty bit `_Float64x`
764        // and an eight byte `long double`.
765        tuple::Arch::X86_64 | tuple::Arch::X86 => Some(Format::X87Extended),
766        tuple::Arch::Aarch64
767        | tuple::Arch::Riscv64
768        | tuple::Arch::Riscv32
769        | tuple::Arch::LoongArch64
770        | tuple::Arch::S390x
771        | tuple::Arch::PowerPc64 => Some(Format::Quad),
772        // Nothing on these machines is wider than a `double`, so there is no type here to
773        // describe and neither reference defines the macros that would describe it.
774        tuple::Arch::Arm | tuple::Arch::Arm64Ec | tuple::Arch::Wasm32 => None,
775    }
776}
777
778/// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
779fn bit_int_granule(target: TargetTuple) -> u32 {
780    match target.arch() {
781        // AAPCS64 says a `_BitInt` above sixty four bits is an array of `__int128`, which is the
782        // one psABI that departs from the register width here.
783        tuple::Arch::Aarch64 | tuple::Arch::Arm64Ec => 128,
784        // Everywhere else it is the width of a general purpose register, which is what the psABIs
785        // that have written the rule down all say and what both references do on the rows that
786        // have not.
787        tuple::Arch::X86 | tuple::Arch::Arm | tuple::Arch::Riscv32 => 32,
788        tuple::Arch::X86_64
789        | tuple::Arch::Riscv64
790        | tuple::Arch::LoongArch64
791        | tuple::Arch::PowerPc64
792        | tuple::Arch::S390x
793        | tuple::Arch::Wasm32 => 64,
794    }
795}
796
797/// How this target allocates bit-fields into storage.
798///
799/// Keyed on the operating system rather than the environment, because mingw's answer here is
800/// Microsoft's and not GCC's. That is the whole reason it is not a guess: a rule keyed on
801/// `Env::Msvc` gets `x86_64-windows-gnu` wrong by four bytes on a struct of an `unsigned :3` and a
802/// `char`, and gets it wrong quietly.
803fn bit_field_style(target: TargetTuple) -> BitFieldStyle {
804    match target.os() {
805        tuple::Os::Windows => BitFieldStyle::Microsoft,
806        _ => BitFieldStyle::Itanium,
807    }
808}
809
810/// Whether an unnamed bit-field raises the record's alignment the way a named one does.
811///
812/// AAPCS says it does, on both widths of ARM, and Apple and Microsoft each dropped that rule.
813/// Microsoft then put its own rule in the same place for a `struct`, so Windows says yes again by
814/// a different route, and says something else entirely for a `union`, which [`BitFieldStyle`]
815/// carries rather than this.
816fn unnamed_bit_field_aligns(target: TargetTuple) -> bool {
817    match (target.arch(), target.os()) {
818        (_, tuple::Os::Windows) => true,
819        // A freestanding ARM target is AAPCS proper, so it says yes: there is no operating system
820        // there to have dropped it.
821        (tuple::Arch::Aarch64 | tuple::Arch::Arm | tuple::Arch::Arm64Ec, os) => !os.is_darwin(),
822        _ => false,
823    }
824}
825
826/// What `__builtin_va_list` is on this target, where this crate can build the type.
827fn va_list(target: TargetTuple) -> Option<VaList> {
828    match (target.arch(), target.os()) {
829        // Windows passes every argument in one place and spills the register ones next to the
830        // stack ones, so the list is an address, and Apple does the same on AArch64.
831        (_, tuple::Os::Windows) => Some(VaList::CharPointer),
832        (tuple::Arch::Aarch64, os) if os.is_darwin() => Some(VaList::CharPointer),
833        (tuple::Arch::Aarch64, _) => Some(VaList::Aapcs),
834        // The x32 ABI's list is the same structure with four byte pointers in it, which is what
835        // building it out of this target's pointer type gives, so it is the same answer.
836        (tuple::Arch::X86_64, _) => Some(VaList::SysV),
837        (tuple::Arch::X86, _) => Some(VaList::CharPointer),
838        (tuple::Arch::Riscv64 | tuple::Arch::Riscv32 | tuple::Arch::LoongArch64, _)
839        | (tuple::Arch::Wasm32, _) => Some(VaList::VoidPointer),
840        // 32-bit ARM's is a structure of one pointer, s390x's is a structure of four members, and
841        // PowerPC's is a structure of five. None of them is any of the four types above and this
842        // crate does not build them, so it says so rather than naming a neighbour's.
843        (
844            tuple::Arch::Arm | tuple::Arch::S390x | tuple::Arch::PowerPc64 | tuple::Arch::Arm64Ec,
845            _,
846        ) => None,
847    }
848}
849
850#[cfg(test)]
851mod tests {
852    use super::*;
853
854    #[test]
855    fn parses_a_four_field_triple() {
856        let t: Triple = "x86_64-unknown-linux-gnu".parse().unwrap();
857        assert_eq!(t, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
858    }
859
860    #[test]
861    fn parses_a_triple_with_no_vendor() {
862        let t: Triple = "aarch64-linux-musl".parse().unwrap();
863        assert_eq!(t, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
864    }
865
866    #[test]
867    fn accepts_the_common_aliases() {
868        let a: Triple = "arm64-apple-darwin".parse().unwrap();
869        let b: Triple = "aarch64-apple-darwin".parse().unwrap();
870        assert_eq!(a, b);
871        assert_eq!(a.env, Env::None);
872    }
873
874    #[test]
875    fn fills_in_the_default_environment() {
876        let t: Triple = "x86_64-unknown-linux".parse().unwrap();
877        assert_eq!(t.env, Env::Gnu);
878        let w: Triple = "x86_64-pc-windows".parse().unwrap();
879        assert_eq!(w.env, Env::Msvc);
880    }
881
882    #[test]
883    fn rejects_what_it_does_not_support() {
884        let e = "sparc64-unknown-linux-gnu".parse::<Triple>().unwrap_err();
885        assert_eq!(e.reason, "unknown architecture");
886        let e = "x86_64-unknown-plan9".parse::<Triple>().unwrap_err();
887        assert_eq!(e.reason, "unknown operating system");
888    }
889
890    #[test]
891    fn displays_in_a_normalised_form() {
892        let t: Triple = "amd64-linux-gnu".parse().unwrap();
893        assert_eq!(t.to_string(), "x86_64-unknown-linux-gnu");
894    }
895
896    #[test]
897    fn display_round_trips_through_parse() {
898        for s in [
899            "x86_64-unknown-linux-gnu",
900            "aarch64-unknown-darwin-none",
901            "riscv64-unknown-linux-musl",
902        ] {
903            let t: Triple = s.parse().unwrap();
904            assert_eq!(t.to_string().parse::<Triple>().unwrap(), t);
905        }
906    }
907
908    #[test]
909    fn char_signedness_follows_the_psabi() {
910        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
911        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
912        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
913        assert!(x86.char_is_signed);
914        assert!(!arm.char_is_signed);
915        assert!(mac.char_is_signed, "Apple overrides AAPCS64 back to a signed char");
916    }
917
918    #[test]
919    fn windows_is_llp64() {
920        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
921        assert_eq!(win.pointer_width, 64);
922        assert_eq!(win.long_width, 32);
923    }
924
925    #[test]
926    fn the_largest_object_is_ptrdiff_max() {
927        // Half the address space less one, which is what a pointer subtraction across the whole
928        // of one object has to fit in. gcc 16 on x86-64 prints this same number when it refuses
929        // an array, and takes an object of exactly this many bytes.
930        for triple in ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc"]
931        {
932            let target = TargetInfo::new(triple.parse().unwrap());
933            assert_eq!(target.max_object_size(), 9_223_372_036_854_775_807, "{triple}");
934        }
935    }
936
937    #[test]
938    fn apple_long_double_is_double() {
939        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
940        assert_eq!(mac.long_double_width, 64);
941        assert_eq!(mac.long_double_format, Format::Double);
942        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
943        assert_eq!(linux.long_double_width, 128);
944    }
945
946    #[test]
947    fn apples_x86_64_is_not_one_of_the_targets_that_narrowed_long_double() {
948        // The bug the layout facts moving to `rucc-abi` fixed. This crate used to decide the
949        // width from the operating system, which took both Apple targets, and Apple made the
950        // change on AArch64 only. `facts/x86_64-macos.facts` in tamnd/rucc-cross records
951        // `long_double_format=x87_extended` with `sizeof_long_double=16`, from a reference
952        // compiler, and this used to answer a sixty four bit `double`.
953        //
954        // It is the quiet kind of wrong. `sizeof(long double)` came out at eight where the
955        // headers say sixteen, so `printf("%Lf")` read the wrong bytes and every structure with
956        // a `long double` in it laid out differently from the system's own.
957        let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
958        assert_eq!(mac.long_double_width, 128);
959        assert_eq!(mac.long_double_format, Format::X87Extended);
960
961        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
962        assert_eq!(
963            (mac.long_double_width, mac.long_double_format),
964            (linux.long_double_width, linux.long_double_format)
965        );
966    }
967
968    #[test]
969    fn every_triple_describes_a_machine() {
970        // `Triple::tuple` panics on a pair that is not a machine and this is what says there is
971        // no such pair. All forty eight combinations, including the ones the parser will produce
972        // from a string somebody can type and no machine has, such as a Darwin target claiming
973        // glibc.
974        let mut built = 0;
975        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
976            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
977                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
978                    let triple = Triple::new(arch, os, env);
979                    let tuple = triple.tuple();
980                    assert_eq!(tuple.pointer_width(), 64, "{triple}");
981                    // The one field the narrowing has to preserve, because mingw and MSVC are the
982                    // same operating system with two different `long double`s.
983                    if os == Os::Windows {
984                        let expected = match env {
985                            Env::Gnu => rucc_tuple::Env::Gnu,
986                            _ => rucc_tuple::Env::Msvc,
987                        };
988                        assert_eq!(tuple.env(), expected, "{triple}");
989                    }
990                    built += 1;
991                }
992            }
993        }
994        assert_eq!(built, 48);
995    }
996
997    #[test]
998    fn from_tuple_undoes_the_narrowing() {
999        // Every triple's tuple comes back as a triple describing the same machine. It is not
1000        // always the triple it started as, because the narrowing is many to one: a Darwin target
1001        // claiming glibc and the same one claiming nothing are one machine, and the answer is the
1002        // spelling that names no libc.
1003        for arch in [Arch::X86_64, Arch::Aarch64, Arch::Riscv64] {
1004            for os in [Os::Linux, Os::Darwin, Os::Windows, Os::None] {
1005                for env in [Env::None, Env::Gnu, Env::Musl, Env::Msvc] {
1006                    let triple = Triple::new(arch, os, env);
1007                    let back = Triple::from_tuple(triple.tuple())
1008                        .unwrap_or_else(|| panic!("{triple} has a tuple and no way back"));
1009                    assert_eq!(back.tuple(), triple.tuple(), "{triple}");
1010                    assert_eq!(back.arch, arch, "{triple}");
1011                    assert_eq!(back.os, os, "{triple}");
1012                }
1013            }
1014        }
1015    }
1016
1017    #[test]
1018    fn from_tuple_gives_the_canonical_environment() {
1019        let musl = Triple::from_tuple("aarch64-linux-musl".parse().unwrap()).unwrap();
1020        assert_eq!(musl, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
1021        let gnu = Triple::from_tuple("x86_64-linux-gnu".parse().unwrap()).unwrap();
1022        assert_eq!(gnu, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
1023        // Darwin and freestanding name no libc, so the answer does too, even though the parser
1024        // will hand this type a Darwin triple with `gnu` on the end.
1025        let macos = Triple::from_tuple("aarch64-macos".parse().unwrap()).unwrap();
1026        assert_eq!(macos, Triple::new(Arch::Aarch64, Os::Darwin, Env::None));
1027        let bare = Triple::from_tuple("riscv64-none".parse().unwrap()).unwrap();
1028        assert_eq!(bare, Triple::new(Arch::Riscv64, Os::None, Env::None));
1029        // The two Windows environments stay apart, which is the whole reason the narrowing keeps
1030        // the environment there and nowhere else.
1031        let mingw = Triple::from_tuple("x86_64-windows-gnu".parse().unwrap()).unwrap();
1032        assert_eq!(mingw.env, Env::Gnu);
1033        let msvc = Triple::from_tuple("x86_64-windows-msvc".parse().unwrap()).unwrap();
1034        assert_eq!(msvc.env, Env::Msvc);
1035    }
1036
1037    #[test]
1038    fn from_tuple_says_no_rather_than_saying_something_near() {
1039        // Twenty five of the forty two rows have no triple, and the answer is `None` rather than
1040        // a neighbour. `rucc-abi` knows the scalar layout of every one of these and this type
1041        // cannot hold any of them, which is the gap the record layout engine inherits.
1042        for tuple in [
1043            "i686-linux-gnu",
1044            "armv7-linux-gnueabihf",
1045            "s390x-linux-gnu",
1046            "powerpc64le-linux-gnu",
1047            "loongarch64-linux-gnu",
1048            "x86_64-linux-gnux32",
1049            "aarch64-linux-android",
1050            "aarch64-ios",
1051            "wasm32-wasip1",
1052            "x86_64-freebsd",
1053        ] {
1054            let target = tuple.parse().unwrap();
1055            assert_eq!(Triple::from_tuple(target), None, "{tuple}");
1056        }
1057    }
1058
1059    #[test]
1060    fn mingw_and_msvc_are_one_operating_system_with_two_long_doubles() {
1061        // The narrowing in `Triple::tuple` keeps the environment on Windows for this reason and
1062        // throws it away everywhere else. GCC's Windows targets keep the eighty bit `long double`
1063        // and Microsoft's make it a `double`, on the same processor and the same OS.
1064        let mingw = TargetInfo::new("x86_64-pc-windows-gnu".parse().unwrap());
1065        assert_eq!(mingw.long_double_width, 128);
1066        assert_eq!(mingw.long_double_format, Format::X87Extended);
1067
1068        let msvc = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1069        assert_eq!(msvc.long_double_width, 64);
1070        assert_eq!(msvc.long_double_format, Format::Double);
1071
1072        // And they agree about everything the operating system does decide.
1073        assert_eq!(mingw.long_width, msvc.long_width);
1074        assert_eq!(mingw.wchar_width, msvc.wchar_width);
1075        assert_eq!(mingw.object_format, msvc.object_format);
1076    }
1077
1078    #[test]
1079    fn wchar_t_divides_the_targets_in_two_directions_at_once() {
1080        // Windows narrows it to sixteen bits, which makes a wide string UTF-16 there and
1081        // UTF-32 everywhere else, and AArch64 Linux makes it unsigned without narrowing it.
1082        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1083        assert_eq!((windows.wchar_width, windows.wchar_is_signed), (16, false));
1084        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1085        assert_eq!((arm.wchar_width, arm.wchar_is_signed), (32, false));
1086        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1087        assert_eq!((linux.wchar_width, linux.wchar_is_signed), (32, true));
1088        // Apple keeps it signed on the same processor where Linux does not, in the same way it
1089        // keeps plain `char` signed there.
1090        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1091        assert_eq!((mac.wchar_width, mac.wchar_is_signed), (32, true));
1092    }
1093
1094    #[test]
1095    fn va_list_is_the_psabis_type_and_not_one_type_with_four_spellings() {
1096        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1097        assert_eq!(linux.va_list, Some(VaList::SysV));
1098        // x86-64 Darwin follows SysV here, and AArch64 Darwin does not follow AAPCS64.
1099        let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
1100        assert_eq!(mac.va_list, Some(VaList::SysV));
1101        let arm_mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1102        assert_eq!(arm_mac.va_list, Some(VaList::CharPointer));
1103        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1104        assert_eq!(arm.va_list, Some(VaList::Aapcs));
1105        // Windows passes everything one way on both processors, so both get the simple one.
1106        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1107        assert_eq!(win.va_list, Some(VaList::CharPointer));
1108        let arm_win = TargetInfo::new("aarch64-pc-windows-msvc".parse().unwrap());
1109        assert_eq!(arm_win.va_list, Some(VaList::CharPointer));
1110        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
1111        assert_eq!(riscv.va_list, Some(VaList::VoidPointer));
1112    }
1113
1114    #[test]
1115    fn two_targets_agree_on_the_width_of_long_double_and_not_on_the_type() {
1116        // Sixteen bytes on both, and a different number in them: the x87 format has sixty four
1117        // bits of significand and quad precision has a hundred and thirteen, so a constant
1118        // converted for one is the wrong bits for the other.
1119        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1120        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1121        assert_eq!(x86.long_double_width, arm.long_double_width);
1122        assert_eq!(x86.long_double_format, Format::X87Extended);
1123        assert_eq!(arm.long_double_format, Format::Quad);
1124        assert_eq!(x86.long_double_format.precision(), 64);
1125        assert_eq!(arm.long_double_format.precision(), 113);
1126        // Windows keeps the name and drops the type, the way Apple does.
1127        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1128        assert_eq!(windows.long_double_format, Format::Double);
1129    }
1130
1131    #[test]
1132    fn float64x_follows_the_processor_where_long_double_follows_the_operating_system() {
1133        // `_Float64x` is the widest format the hardware has, and no ABI takes it away the way
1134        // Apple and Windows take `long double` away. So the two fields say the same thing on
1135        // Linux and disagree everywhere else, which is the whole reason there are two of them.
1136        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
1137        assert_eq!(x86.float64x_format, Some(Format::X87Extended));
1138        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
1139        assert_eq!(arm.float64x_format, Some(Format::Quad));
1140        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
1141        assert_eq!(riscv.float64x_format, Some(Format::Quad));
1142
1143        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
1144        assert_eq!(mac.long_double_format, Format::Double);
1145        assert_eq!(mac.float64x_format, Some(Format::Quad));
1146        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
1147        assert_eq!(windows.long_double_format, Format::Double);
1148        assert_eq!(windows.float64x_format, Some(Format::X87Extended));
1149    }
1150
1151    #[test]
1152    fn the_object_format_follows_the_operating_system() {
1153        assert_eq!(Os::Linux.object_format(), ObjectFormat::Elf);
1154        assert_eq!(Os::Darwin.object_format(), ObjectFormat::MachO);
1155        assert_eq!(Os::Windows.object_format(), ObjectFormat::Coff);
1156    }
1157
1158    #[test]
1159    fn a_target_carries_its_registers_and_says_so_when_it_has_none() {
1160        let of = |triple: &str| TargetInfo::new(triple.parse().unwrap());
1161        let linux = of("x86_64-unknown-linux-gnu");
1162        assert_eq!(linux.regs.reg_named("rdi"), Some((x86_64::GPR, x86_64::RDI)));
1163        assert_eq!(linux.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
1164        // Apple's x86-64 is SysV and Windows is the one that is not.
1165        let apple = of("x86_64-apple-darwin");
1166        assert_eq!(apple.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RDI));
1167        let windows = of("x86_64-pc-windows-msvc");
1168        assert_eq!(windows.regs.len(x86_64::GPR), 16);
1169        assert_eq!(windows.call_regs.map(|regs| regs.int_args[0]), Some(x86_64::RCX));
1170        // Not described yet, and saying nothing is the answer rather than saying x86-64's.
1171        let arm = of("aarch64-unknown-linux-gnu");
1172        assert!(arm.regs.is_empty());
1173        assert!(arm.call_regs.is_none());
1174    }
1175
1176    #[test]
1177    fn the_host_triple_is_one_we_support() {
1178        // Every host in spec/15-testing.md section 15.7 must be recognised, and CI runs on
1179        // all three, so a failure here means a host we claim support for stopped resolving.
1180        let host = Triple::host().expect("the host must be a supported target");
1181        assert_eq!(host.to_string().parse::<Triple>().unwrap(), host);
1182    }
1183}