Skip to main content

rucc_target/
lib.rs

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