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 1, 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 and the per-target rule sets. Everything a pass
8//! needs to know about a target is a field it can read here. That rule is what makes the
9//! claim in `spec/10-backend.md` testable, namely that a new target is a rule set and a few
10//! data files, and `M10` brings up a fourth target specifically to put a number on it.
11//!
12//! [`TargetInfo::call`] is the other half of that rule and the one with teeth. How a structure
13//! travels between a caller and a callee is the target's answer rather than C's, so the walk to
14//! the IR flattens a C type into a [`Shape`] and asks here what form it takes. Every psABI rule
15//! is behind [`Call`] and nothing outside this crate matches on an architecture to find one.
16//!
17//! # Status
18//!
19//! Triple parsing and the basic data model are real, which is what `rucc --print-config`
20//! reports, and so is the argument classification of every psABI in
21//! `spec/12-abi-and-runtime.md` sections 12.2 to 12.5. A register file is describable, which is
22//! [`RegFile`], and the description of x86-64's own comes with the target that uses it. Machine
23//! models land in `M6`.
24//!
25//! This crate is tier 3 in `spec/18-package-layout.md` section 18.5: its Rust API is
26//! explicitly unstable and will change without a major version bump.
27
28#![doc(html_root_url = "https://docs.rs/rucc-target/0.2.21")]
29
30use std::fmt;
31use std::str::FromStr;
32
33use rucc_base::float::Format;
34
35mod abi;
36mod regs;
37
38pub use crate::abi::{Arg, Call, Kind, Pass, Piece, Scalar, Shape, Slot};
39pub use crate::regs::{ClassInfo, PhysReg, RegClass, RegFile};
40
41/// A target architecture.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
44// match that needs to change, in this workspace and in anyone else's code. That is
45// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
46// target is a data change: the compiler tells you every place the data is read.
47pub enum Arch {
48    /// x86-64, the first target and the one `M3` brings up.
49    X86_64,
50    /// AArch64, the second target, `M6`.
51    Aarch64,
52    /// 64-bit RISC-V. `spec/10-backend.md` calls this the middle-end canary, because it has
53    /// no condition codes and no complex addressing modes, so anything the middle end got
54    /// away with on x86-64 shows up here.
55    Riscv64,
56}
57
58impl Arch {
59    /// Pointer width in bits.
60    pub const fn pointer_width(self) -> u32 {
61        match self {
62            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => 64,
63        }
64    }
65
66    /// Whether the target is little-endian.
67    pub const fn is_little_endian(self) -> bool {
68        match self {
69            Arch::X86_64 | Arch::Aarch64 | Arch::Riscv64 => true,
70        }
71    }
72
73    /// The name as it appears in a triple.
74    pub const fn as_str(self) -> &'static str {
75        match self {
76            Arch::X86_64 => "x86_64",
77            Arch::Aarch64 => "aarch64",
78            Arch::Riscv64 => "riscv64",
79        }
80    }
81}
82
83/// The operating system a target runs on.
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 Os {
90    /// Linux, hosted or freestanding.
91    Linux,
92    /// Apple platforms. `spec/12-abi-and-runtime.md` section 12.3 lists the four places
93    /// Apple diverges from AAPCS64, and every one of them is a real bug if missed.
94    Darwin,
95    /// Windows.
96    Windows,
97    /// No operating system, which is what `-ffreestanding` kernel work looks like.
98    None,
99}
100
101impl Os {
102    /// The name as it appears in a triple.
103    pub const fn as_str(self) -> &'static str {
104        match self {
105            Os::Linux => "linux",
106            Os::Darwin => "darwin",
107            Os::Windows => "windows",
108            Os::None => "none",
109        }
110    }
111
112    /// The object file format this operating system uses.
113    pub const fn object_format(self) -> ObjectFormat {
114        match self {
115            Os::Linux | Os::None => ObjectFormat::Elf,
116            Os::Darwin => ObjectFormat::MachO,
117            Os::Windows => ObjectFormat::Coff,
118        }
119    }
120}
121
122/// The C runtime and ABI variant.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
124// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
125// match that needs to change, in this workspace and in anyone else's code. That is
126// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
127// target is a data change: the compiler tells you every place the data is read.
128pub enum Env {
129    /// The default for the operating system.
130    None,
131    /// glibc.
132    Gnu,
133    /// musl.
134    Musl,
135    /// The MSVC ABI.
136    Msvc,
137}
138
139impl Env {
140    /// The name as it appears in a triple, if it appears at all.
141    pub const fn as_str(self) -> &'static str {
142        match self {
143            Env::None => "none",
144            Env::Gnu => "gnu",
145            Env::Musl => "musl",
146            Env::Msvc => "msvc",
147        }
148    }
149}
150
151/// The object file format to emit.
152#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
153// Deliberately not `#[non_exhaustive]`. Adding a variant here has to break every
154// match that needs to change, in this workspace and in anyone else's code. That is
155// the property `spec/10-backend.md` section 10.8 is claiming when it says adding a
156// target is a data change: the compiler tells you every place the data is read.
157pub enum ObjectFormat {
158    /// ELF.
159    Elf,
160    /// Mach-O.
161    MachO,
162    /// COFF.
163    Coff,
164}
165
166impl ObjectFormat {
167    /// The name used in diagnostics and in `--print-config`.
168    pub const fn as_str(self) -> &'static str {
169        match self {
170            ObjectFormat::Elf => "elf",
171            ObjectFormat::MachO => "macho",
172            ObjectFormat::Coff => "coff",
173        }
174    }
175}
176
177/// A target triple.
178///
179/// We accept the LLVM-style `arch-vendor-os-env` form because that is what build systems
180/// pass, and we normalise it to the three fields we actually branch on. The vendor field is
181/// parsed and discarded: no decision in the compiler depends on it, and keeping it would
182/// invite one.
183#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
184pub struct Triple {
185    /// The architecture.
186    pub arch: Arch,
187    /// The operating system.
188    pub os: Os,
189    /// The runtime and ABI variant.
190    pub env: Env,
191}
192
193impl Triple {
194    /// A triple from its three parts.
195    pub const fn new(arch: Arch, os: Os, env: Env) -> Self {
196        Self { arch, os, env }
197    }
198
199    /// The triple of the machine this compiler is running on.
200    ///
201    /// Used as the default target, which is what makes `rucc hello.c` work with no flags.
202    /// Unknown host combinations are not an error here: they are reported by the driver,
203    /// where there is somewhere to report them to.
204    pub fn host() -> Option<Self> {
205        let arch = match std::env::consts::ARCH {
206            "x86_64" => Arch::X86_64,
207            "aarch64" => Arch::Aarch64,
208            "riscv64" => Arch::Riscv64,
209            _ => return None,
210        };
211        // Which libc this is matters, and `std::env::consts` does not say. A compiler built on
212        // Alpine and defaulting to `x86_64-unknown-linux-gnu` describes a machine it is not
213        // running on: musl and glibc disagree about `int_fast16_t` among other things, and a
214        // header that is written out of the predefined type names picks the disagreement up.
215        // The libc rucc itself was linked against is the best evidence available about the one
216        // the code it compiles will be linked against, and it is right on every machine where
217        // rucc was built for the machine it runs on.
218        let linux = if cfg!(target_env = "musl") { Env::Musl } else { Env::Gnu };
219        let (os, env) = match std::env::consts::OS {
220            "linux" => (Os::Linux, linux),
221            "macos" => (Os::Darwin, Env::None),
222            "windows" => (Os::Windows, Env::Msvc),
223            _ => return None,
224        };
225        Some(Self::new(arch, os, env))
226    }
227}
228
229impl fmt::Display for Triple {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        // Always four fields, always the same spelling, because this string ends up in
232        // `--print-config` output that people diff.
233        write!(f, "{}-unknown-{}-{}", self.arch.as_str(), self.os.as_str(), self.env.as_str())
234    }
235}
236
237/// Why a triple failed to parse.
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct ParseTripleError {
240    /// The triple as given.
241    pub input: String,
242    /// What specifically was not recognised.
243    pub reason: &'static str,
244}
245
246impl fmt::Display for ParseTripleError {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        write!(f, "unsupported target triple `{}`: {}", self.input, self.reason)
249    }
250}
251
252impl std::error::Error for ParseTripleError {}
253
254impl FromStr for Triple {
255    type Err = ParseTripleError;
256
257    fn from_str(s: &str) -> Result<Self, Self::Err> {
258        let err = |reason| ParseTripleError { input: s.to_owned(), reason };
259        let mut parts = s.split('-');
260
261        let arch = match parts.next() {
262            Some("x86_64" | "amd64") => Arch::X86_64,
263            Some("aarch64" | "arm64") => Arch::Aarch64,
264            Some("riscv64") => Arch::Riscv64,
265            _ => return Err(err("unknown architecture")),
266        };
267
268        // The vendor field is optional in practice. `x86_64-linux-gnu` and
269        // `x86_64-unknown-linux-gnu` both occur in the wild and mean the same thing, so the
270        // remaining fields are matched by content rather than by position.
271        let rest: Vec<&str> = parts.collect();
272        let mut os = None;
273        let mut env = None;
274        for part in &rest {
275            match *part {
276                "linux" => os = Some(Os::Linux),
277                "darwin" | "macos" | "macosx" | "ios" => os = Some(Os::Darwin),
278                "windows" | "win32" => os = Some(Os::Windows),
279                // `none` is the one token that means different things in the two positions.
280                // In `x86_64-unknown-none-elf` it is the operating system; in
281                // `aarch64-apple-darwin-none` it is the environment. Which one it is depends
282                // on whether an operating system has already been seen, and that rule is what
283                // makes `Display` round-trip through `FromStr`.
284                "none" if os.is_none() => os = Some(Os::None),
285                "none" => env = Some(Env::None),
286                "elf" => os = os.or(Some(Os::None)),
287                "gnu" | "gnueabi" | "gnueabihf" => env = Some(Env::Gnu),
288                "musl" | "musleabi" | "musleabihf" => env = Some(Env::Musl),
289                "msvc" => env = Some(Env::Msvc),
290                _ => {}
291            }
292        }
293
294        let os = os.ok_or_else(|| err("unknown operating system"))?;
295        let env = env.unwrap_or(match os {
296            Os::Linux => Env::Gnu,
297            Os::Windows => Env::Msvc,
298            Os::Darwin | Os::None => Env::None,
299        });
300        Ok(Self::new(arch, os, env))
301    }
302}
303
304/// The facts about a target that the compiler reads instead of hard-coding.
305///
306/// This is the whole of what a pass is allowed to know about where its output will run.
307/// It grows, and every field added here is one fewer `#[cfg]` somewhere it should not be.
308#[derive(Debug, Clone, PartialEq, Eq)]
309#[non_exhaustive]
310pub struct TargetInfo {
311    /// The triple this describes.
312    pub triple: Triple,
313    /// Width of a pointer in bits.
314    pub pointer_width: u32,
315    /// Whether bytes are ordered little end first.
316    pub little_endian: bool,
317    /// Whether a bare `char` is signed.
318    ///
319    /// Signed on x86-64 and unsigned on AArch64 Linux, which is the classic source of code
320    /// that works on one and not the other, so it is data rather than an assumption.
321    pub char_is_signed: bool,
322    /// Width of `long` in bits. This is the field that separates the LP64 world from
323    /// Windows LLP64.
324    pub long_width: u32,
325    /// Width of `long double` in bits: 80 bits of x87 stored in 128 on SysV x86-64,
326    /// 64 on Apple platforms, 64 on Windows.
327    pub long_double_width: u32,
328    /// The format `long double` actually is, which the width does not say.
329    ///
330    /// It is 128 bits wide on SysV x86-64 and on AArch64 Linux and the two are not the same
331    /// type: one is the x87 eighty bit format padded out to sixteen bytes and the other is
332    /// true quad precision with a hundred and thirteen bits of significand. Anything that
333    /// converts a constant or folds one has to know which, and the width alone cannot say.
334    pub long_double_format: Format,
335    /// The format `_Float64x` is, which is the widest format the target has short of a software
336    /// one.
337    ///
338    /// It follows the architecture and not the operating system, which is what makes it worth a
339    /// field of its own next to `long double`. Apple and Windows define `long double` as a
340    /// `double` and neither of them takes `_Float64x` down with it: the type has to be wider
341    /// than a `_Float64`, so it is the x87 eighty bit format on x86-64 and quad precision on
342    /// AArch64 and RISC-V wherever it is written.
343    pub float64x_format: Format,
344    /// Width of `wchar_t` in bits, which decides what a wide literal is encoded in.
345    ///
346    /// It is 16 on Windows, so a wide string there is UTF-16 and a character outside the basic
347    /// plane takes two elements, and 32 everywhere else, where a wide string is UTF-32 and no
348    /// character takes more than one.
349    pub wchar_width: u32,
350    /// Whether `wchar_t` is signed.
351    ///
352    /// x86-64 Linux makes it a signed `int` and AArch64 Linux makes it an `unsigned int`,
353    /// following the psABI's rule for plain `char`, so `L'\xffffffff'` is minus one on one of
354    /// them and four billion on the other.
355    pub wchar_is_signed: bool,
356    /// The granule a `_BitInt` wider than 64 bits is laid out in, in bits.
357    ///
358    /// Above 64 bits the psABIs stop treating a `_BitInt` like a standard integer type and
359    /// start treating it like an array of these, so its size is rounded up to a multiple of
360    /// this and its alignment is this. It is 64 on x86-64 and RISC-V and 128 on AArch64, which
361    /// is why `_BitInt(65)` is sixteen bytes aligned to eight on one and sixteen bytes aligned
362    /// to sixteen on the other. Measured with clang 18 on x86-64 Linux and clang on AArch64
363    /// Darwin rather than read off the documents.
364    pub bit_int_granule: u32,
365    /// The object format to emit.
366    pub object_format: ObjectFormat,
367    /// What `__builtin_va_list` is, which is the type every `va_list` in every header is a
368    /// typedef of.
369    pub va_list: VaList,
370}
371
372/// The type a target's `__builtin_va_list` is.
373///
374/// A variable argument list is the one place a psABI dictates a C type rather than how a type
375/// travels, and the four answers below are not four spellings of one thing: `sizeof(va_list)` is
376/// eight bytes on Apple's AArch64 and thirty two on Linux's, and on SysV x86-64 a `va_list` is an
377/// array, so a `va_list` passed to a function is passed as a pointer and one assigned to another
378/// is a constraint violation rather than a copy. Code in the wild depends on all of that.
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380// Deliberately not `#[non_exhaustive]`, for the reason [`Arch`] is not: a fifth answer here is
381// a fifth type to build, and every place that builds one should stop compiling until it does.
382pub enum VaList {
383    /// `char *`, which is what a target whose arguments are all passed in one place needs: the
384    /// address of the next argument and nothing else. Apple's AArch64 and both Windows targets.
385    CharPointer,
386    /// `void *`, which is the RISC-V psABI's spelling of the same thing.
387    VoidPointer,
388    /// `struct __va_list_tag { unsigned gp_offset, fp_offset; void *overflow_arg_area,
389    /// *reg_save_area; } [1]`, the SysV x86-64 one. Arguments arrive in two register files and
390    /// on the stack, so the list is a cursor into each, and the array of one is what makes
391    /// passing it to `vfprintf` pass its address.
392    SysV,
393    /// `struct __va_list { void *__stack, *__gr_top, *__vr_top; int __gr_offs, __vr_offs; }`,
394    /// the AAPCS64 one. The same idea as SysV's, counting down from the top of each save area
395    /// rather than up from the bottom, and not an array.
396    Aapcs,
397}
398
399impl VaList {
400    /// The name used in `--print-config`.
401    #[must_use]
402    pub const fn as_str(self) -> &'static str {
403        match self {
404            VaList::CharPointer => "char-pointer",
405            VaList::VoidPointer => "void-pointer",
406            VaList::SysV => "sysv",
407            VaList::Aapcs => "aapcs",
408        }
409    }
410}
411
412impl TargetInfo {
413    /// The description of `triple`.
414    pub fn new(triple: Triple) -> Self {
415        let char_is_signed = match (triple.arch, triple.os) {
416            // The AArch64 and RISC-V psABIs make plain `char` unsigned, and x86-64 SysV
417            // makes it signed. Apple and Windows both override that back to signed on
418            // AArch64, which is the kind of divergence that only ever surfaces as a bug
419            // report from someone whose lexer compares a `char` against a negative value.
420            (Arch::Aarch64 | Arch::Riscv64, Os::Linux | Os::None) => false,
421            _ => true,
422        };
423        let long_width = match triple.os {
424            // Windows is LLP64: `long` stays 32 bits on a 64-bit target.
425            Os::Windows => 32,
426            _ => triple.arch.pointer_width(),
427        };
428        let long_double_width = match triple.os {
429            // Apple defines `long double` as `double`, per spec/12-abi-and-runtime.md
430            // section 12.3, and Windows does the same. On the SysV targets it is a distinct
431            // type: 80 bits of x87 stored in 128 on x86-64, and true quad precision on
432            // AArch64 and RISC-V.
433            Os::Darwin | Os::Windows => 64,
434            Os::Linux | Os::None => 128,
435        };
436        let long_double_format = match (triple.arch, long_double_width) {
437            (_, 64) => Format::Double,
438            // The one place two targets agree on the width and disagree on the type.
439            (Arch::X86_64, _) => Format::X87Extended,
440            (Arch::Aarch64 | Arch::Riscv64, _) => Format::Quad,
441        };
442        let float64x_format = match triple.arch {
443            Arch::X86_64 => Format::X87Extended,
444            Arch::Aarch64 | Arch::Riscv64 => Format::Quad,
445        };
446        let bit_int_granule = match triple.arch {
447            Arch::Aarch64 => 128,
448            Arch::X86_64 | Arch::Riscv64 => 64,
449        };
450        // Windows makes `wchar_t` 16 bits so that a wide string is UTF-16, and AArch64 Linux
451        // makes it unsigned the way it makes plain `char` unsigned. Neither follows from
452        // anything else here, which is why both are their own field.
453        let wchar_width = if triple.os == Os::Windows { 16 } else { 32 };
454        let wchar_is_signed = !matches!(
455            (triple.arch, triple.os),
456            (_, Os::Windows) | (Arch::Aarch64, Os::Linux | Os::None)
457        );
458        let va_list = match (triple.arch, triple.os) {
459            // Windows passes every argument in one place and spills the register ones next to
460            // the stack ones, so the list is an address, and Apple does the same on AArch64.
461            (_, Os::Windows) | (Arch::Aarch64, Os::Darwin) => VaList::CharPointer,
462            (Arch::X86_64, _) => VaList::SysV,
463            (Arch::Aarch64, _) => VaList::Aapcs,
464            (Arch::Riscv64, _) => VaList::VoidPointer,
465        };
466        Self {
467            triple,
468            pointer_width: triple.arch.pointer_width(),
469            little_endian: triple.arch.is_little_endian(),
470            char_is_signed,
471            long_width,
472            long_double_width,
473            long_double_format,
474            float64x_format,
475            wchar_width,
476            wchar_is_signed,
477            bit_int_granule,
478            object_format: triple.os.object_format(),
479            va_list,
480        }
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487
488    #[test]
489    fn parses_a_four_field_triple() {
490        let t: Triple = "x86_64-unknown-linux-gnu".parse().unwrap();
491        assert_eq!(t, Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
492    }
493
494    #[test]
495    fn parses_a_triple_with_no_vendor() {
496        let t: Triple = "aarch64-linux-musl".parse().unwrap();
497        assert_eq!(t, Triple::new(Arch::Aarch64, Os::Linux, Env::Musl));
498    }
499
500    #[test]
501    fn accepts_the_common_aliases() {
502        let a: Triple = "arm64-apple-darwin".parse().unwrap();
503        let b: Triple = "aarch64-apple-darwin".parse().unwrap();
504        assert_eq!(a, b);
505        assert_eq!(a.env, Env::None);
506    }
507
508    #[test]
509    fn fills_in_the_default_environment() {
510        let t: Triple = "x86_64-unknown-linux".parse().unwrap();
511        assert_eq!(t.env, Env::Gnu);
512        let w: Triple = "x86_64-pc-windows".parse().unwrap();
513        assert_eq!(w.env, Env::Msvc);
514    }
515
516    #[test]
517    fn rejects_what_it_does_not_support() {
518        let e = "sparc64-unknown-linux-gnu".parse::<Triple>().unwrap_err();
519        assert_eq!(e.reason, "unknown architecture");
520        let e = "x86_64-unknown-plan9".parse::<Triple>().unwrap_err();
521        assert_eq!(e.reason, "unknown operating system");
522    }
523
524    #[test]
525    fn displays_in_a_normalised_form() {
526        let t: Triple = "amd64-linux-gnu".parse().unwrap();
527        assert_eq!(t.to_string(), "x86_64-unknown-linux-gnu");
528    }
529
530    #[test]
531    fn display_round_trips_through_parse() {
532        for s in [
533            "x86_64-unknown-linux-gnu",
534            "aarch64-unknown-darwin-none",
535            "riscv64-unknown-linux-musl",
536        ] {
537            let t: Triple = s.parse().unwrap();
538            assert_eq!(t.to_string().parse::<Triple>().unwrap(), t);
539        }
540    }
541
542    #[test]
543    fn char_signedness_follows_the_psabi() {
544        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
545        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
546        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
547        assert!(x86.char_is_signed);
548        assert!(!arm.char_is_signed);
549        assert!(mac.char_is_signed, "Apple overrides AAPCS64 back to a signed char");
550    }
551
552    #[test]
553    fn windows_is_llp64() {
554        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
555        assert_eq!(win.pointer_width, 64);
556        assert_eq!(win.long_width, 32);
557    }
558
559    #[test]
560    fn apple_long_double_is_double() {
561        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
562        assert_eq!(mac.long_double_width, 64);
563        assert_eq!(mac.long_double_format, Format::Double);
564        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
565        assert_eq!(linux.long_double_width, 128);
566    }
567
568    #[test]
569    fn wchar_t_divides_the_targets_in_two_directions_at_once() {
570        // Windows narrows it to sixteen bits, which makes a wide string UTF-16 there and
571        // UTF-32 everywhere else, and AArch64 Linux makes it unsigned without narrowing it.
572        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
573        assert_eq!((windows.wchar_width, windows.wchar_is_signed), (16, false));
574        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
575        assert_eq!((arm.wchar_width, arm.wchar_is_signed), (32, false));
576        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
577        assert_eq!((linux.wchar_width, linux.wchar_is_signed), (32, true));
578        // Apple keeps it signed on the same processor where Linux does not, in the same way it
579        // keeps plain `char` signed there.
580        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
581        assert_eq!((mac.wchar_width, mac.wchar_is_signed), (32, true));
582    }
583
584    #[test]
585    fn va_list_is_the_psabis_type_and_not_one_type_with_four_spellings() {
586        let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
587        assert_eq!(linux.va_list, VaList::SysV);
588        // x86-64 Darwin follows SysV here, and AArch64 Darwin does not follow AAPCS64.
589        let mac = TargetInfo::new("x86_64-apple-darwin".parse().unwrap());
590        assert_eq!(mac.va_list, VaList::SysV);
591        let arm_mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
592        assert_eq!(arm_mac.va_list, VaList::CharPointer);
593        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
594        assert_eq!(arm.va_list, VaList::Aapcs);
595        // Windows passes everything one way on both processors, so both get the simple one.
596        let win = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
597        assert_eq!(win.va_list, VaList::CharPointer);
598        let arm_win = TargetInfo::new("aarch64-pc-windows-msvc".parse().unwrap());
599        assert_eq!(arm_win.va_list, VaList::CharPointer);
600        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
601        assert_eq!(riscv.va_list, VaList::VoidPointer);
602    }
603
604    #[test]
605    fn two_targets_agree_on_the_width_of_long_double_and_not_on_the_type() {
606        // Sixteen bytes on both, and a different number in them: the x87 format has sixty four
607        // bits of significand and quad precision has a hundred and thirteen, so a constant
608        // converted for one is the wrong bits for the other.
609        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
610        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
611        assert_eq!(x86.long_double_width, arm.long_double_width);
612        assert_eq!(x86.long_double_format, Format::X87Extended);
613        assert_eq!(arm.long_double_format, Format::Quad);
614        assert_eq!(x86.long_double_format.precision(), 64);
615        assert_eq!(arm.long_double_format.precision(), 113);
616        // Windows keeps the name and drops the type, the way Apple does.
617        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
618        assert_eq!(windows.long_double_format, Format::Double);
619    }
620
621    #[test]
622    fn float64x_follows_the_processor_where_long_double_follows_the_operating_system() {
623        // `_Float64x` is the widest format the hardware has, and no ABI takes it away the way
624        // Apple and Windows take `long double` away. So the two fields say the same thing on
625        // Linux and disagree everywhere else, which is the whole reason there are two of them.
626        let x86 = TargetInfo::new("x86_64-unknown-linux-gnu".parse().unwrap());
627        assert_eq!(x86.float64x_format, Format::X87Extended);
628        let arm = TargetInfo::new("aarch64-unknown-linux-gnu".parse().unwrap());
629        assert_eq!(arm.float64x_format, Format::Quad);
630        let riscv = TargetInfo::new("riscv64-unknown-linux-gnu".parse().unwrap());
631        assert_eq!(riscv.float64x_format, Format::Quad);
632
633        let mac = TargetInfo::new("aarch64-apple-darwin".parse().unwrap());
634        assert_eq!(mac.long_double_format, Format::Double);
635        assert_eq!(mac.float64x_format, Format::Quad);
636        let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse().unwrap());
637        assert_eq!(windows.long_double_format, Format::Double);
638        assert_eq!(windows.float64x_format, Format::X87Extended);
639    }
640
641    #[test]
642    fn the_object_format_follows_the_operating_system() {
643        assert_eq!(Os::Linux.object_format(), ObjectFormat::Elf);
644        assert_eq!(Os::Darwin.object_format(), ObjectFormat::MachO);
645        assert_eq!(Os::Windows.object_format(), ObjectFormat::Coff);
646    }
647
648    #[test]
649    fn the_host_triple_is_one_we_support() {
650        // Every host in spec/15-testing.md section 15.7 must be recognised, and CI runs on
651        // all three, so a failure here means a host we claim support for stopped resolving.
652        let host = Triple::host().expect("the host must be a supported target");
653        assert_eq!(host.to_string().parse::<Triple>().unwrap(), host);
654    }
655}