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