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