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