Skip to main content

lamina_platform/
target.rs

1//! Targets: an architecture, an environment, an operating system and a variant.
2//!
3//! Every per-architecture property comes from the table in [`crate::arch_table`]. This
4//! module holds the other three axes and the target-string grammar that selects them.
5
6use std::{fmt, str::FromStr};
7
8use crate::arch_table::{
9    ArchSpec, Endianness, FloatAggregateAbi, OutputArtifact, PointerWidth, REFUSED_SPELLINGS,
10    TargetDataLayout,
11};
12use crate::arch_variant::{
13    Aarch64Level, ArchVariant, Armv7Profile, Armv7Variant, PowerLevel, RiscvIsa,
14};
15use crate::detection::{detect_host_architecture_only, detect_host_os};
16use crate::environment::TargetEnvironment;
17
18pub use crate::arch_table::TargetArchitecture;
19
20/// A target: what to generate code for, and what to link it against.
21///
22/// The four axes are independent and each is declared, never inferred from the build
23/// machine. The target-string grammar is `<architecture>_<operating-system>[_<environment>]`,
24/// where the architecture field may name a sub-architecture variant (`rv64gc`,
25/// `aarch64v8.2`) and the environment field may be left out, in which case the
26/// architecture's declared default for that operating system applies.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Target {
29    pub architecture: TargetArchitecture,
30    pub operating_system: TargetOperatingSystem,
31    /// The C runtime and ABI. Left out of a target string, it is
32    /// [`default_environment`] for the architecture and operating system.
33    pub environment: TargetEnvironment,
34    /// Sub-architecture the target string selected. Every spelling that names only the
35    /// architecture gets the architecture's [`crate::VariantAxis`] default, which is what
36    /// that spelling has always meant.
37    pub variant: ArchVariant,
38}
39
40/// The environment a target means when its target string names none.
41///
42/// Declared, not discovered: an operating system that supplies its own C runtime, or none
43/// at all, declares [`TargetEnvironment::Freestanding`], and every other operating system
44/// takes the architecture's own `hosted_environment` from the arch table.
45pub const fn default_environment(
46    architecture: TargetArchitecture,
47    operating_system: TargetOperatingSystem,
48) -> TargetEnvironment {
49    if operating_system.links_a_c_runtime() {
50        architecture.spec().hosted_environment
51    } else {
52        TargetEnvironment::Freestanding
53    }
54}
55
56impl Target {
57    pub fn new(architecture: TargetArchitecture, operating_system: TargetOperatingSystem) -> Self {
58        Self {
59            architecture,
60            operating_system,
61            environment: default_environment(architecture, operating_system),
62            variant: architecture.spec().variants.default_variant(),
63        }
64    }
65
66    /// Select a sub-architecture. A variant of another family is replaced by the
67    /// architecture's default, so the target stays consistent.
68    pub fn with_variant(mut self, variant: ArchVariant) -> Self {
69        self.variant = self.architecture.spec().variants.accept(variant);
70        self
71    }
72
73    /// Select an environment the architecture accepts.
74    pub fn with_environment(mut self, environment: TargetEnvironment) -> Result<Self, String> {
75        let spec = self.architecture.spec();
76        if !spec.accepts_environment(environment) {
77            return Err(format!(
78                "Environment '{environment}' is not supported for {}\n  Hint: {} accepts {}",
79                spec.token,
80                spec.token,
81                accepted_environments(&spec)
82            ));
83        }
84        self.environment = environment;
85        Ok(self)
86    }
87
88    /// Everything the architecture states, as this target's variant refines it.
89    pub fn spec(&self) -> ArchSpec {
90        self.variant.refine(self.architecture.spec())
91    }
92
93    /// Architecture half of the target string, carrying the variant when it is not the
94    /// default, so `Display` and `FromStr` stay inverses.
95    pub fn arch_token(&self) -> String {
96        let spec = self.architecture.spec();
97        if self.variant == spec.variants.default_variant() {
98            return spec.token.to_string();
99        }
100        match self.variant {
101            ArchVariant::Riscv(isa) => isa.march(),
102            ArchVariant::None
103            | ArchVariant::Armv7(_)
104            | ArchVariant::Aarch64(_)
105            | ArchVariant::PowerPC64(_)
106            | ArchVariant::X86(_) => self
107                .variant_spelling()
108                .map_or_else(|| spec.token.to_string(), |(name, _)| name.to_string()),
109        }
110    }
111
112    fn variant_spelling(&self) -> Option<(&'static str, Option<TargetEnvironment>)> {
113        VARIANT_SPELLINGS
114            .iter()
115            .find(|(_, arch, variant, _)| *arch == self.architecture && *variant == self.variant)
116            .map(|(name, _, _, environment)| (*name, *environment))
117    }
118
119    /// The environment the rest of the target string already implies, and so the one the
120    /// environment field is left out for.
121    pub fn implied_environment(&self) -> TargetEnvironment {
122        self.variant_spelling()
123            .and_then(|(_, environment)| environment)
124            .unwrap_or_else(|| default_environment(self.architecture, self.operating_system))
125    }
126
127    pub fn pointer_width(&self) -> PointerWidth {
128        self.spec().pointer_width
129    }
130
131    pub fn has_64bit_int_registers(&self) -> bool {
132        self.spec().has_64bit_int_registers()
133    }
134
135    pub fn endianness(&self) -> Endianness {
136        self.spec().endianness
137    }
138
139    pub fn stack_alignment_at_call(&self) -> usize {
140        self.spec().stack_alignment_at_call
141    }
142
143    pub fn honors_argument_alignment(&self) -> bool {
144        self.spec().honors_argument_alignment
145    }
146
147    pub fn output_artifact(&self) -> OutputArtifact {
148        self.spec().output_artifact
149    }
150
151    pub fn data_layout(&self) -> TargetDataLayout {
152        let mut layout = self.spec().data_layout();
153        layout.float_aggregates = match (layout.float_aggregates, self.operating_system) {
154            (FloatAggregateAbi::SysV, TargetOperatingSystem::Windows) => FloatAggregateAbi::Win64,
155            (abi, _) => abi,
156        };
157        layout
158    }
159
160    /// The ELF interpreter this target's environment asks for, if it asks for one.
161    pub fn interpreter(&self) -> Option<&'static str> {
162        self.spec().interpreter(self.environment)
163    }
164
165    pub fn gpu_triple(&self) -> Option<&'static str> {
166        match (self.architecture, self.operating_system) {
167            (TargetArchitecture::Nvptx64, TargetOperatingSystem::Cuda) => {
168                Some("nvptx64-nvidia-cuda")
169            }
170            (TargetArchitecture::Amdgcn, TargetOperatingSystem::AmdHsa) => {
171                Some("amdgcn-amd-amdhsa")
172            }
173            _ => None,
174        }
175    }
176
177    pub fn to_str(&self) -> String {
178        self.to_string()
179    }
180
181    /// Detect the host system's target.
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use lamina_platform::Target;
187    /// let host = Target::detect_host().expect("host is a supported target");
188    /// println!("Host target: {}", host);
189    /// ```
190    ///
191    /// Fails for a host whose architecture or OS lamina has no backend for, rather than
192    /// guessing a target for it.
193    pub fn detect_host() -> Result<Self, String> {
194        let arch = detect_host_architecture_only();
195        let os = detect_host_os();
196        let architecture = parse_architecture(arch)
197            .map_err(|_| format!("host architecture '{arch}' has no lamina backend"))?;
198        let operating_system = TargetOperatingSystem::from_str(os)
199            .map_err(|_| format!("host operating system '{os}' has no lamina backend"))?;
200        Ok(Self::new(architecture, operating_system))
201    }
202}
203
204fn accepted_environments(spec: &ArchSpec) -> String {
205    let mut names: Vec<&str> = spec
206        .environments
207        .iter()
208        .map(|environment| environment.token())
209        .collect();
210    names.push(TargetEnvironment::Freestanding.token());
211    names.join(", ")
212}
213
214impl FromStr for Target {
215    type Err = String;
216
217    fn from_str(target: &str) -> Result<Self, Self::Err> {
218        let (head, named_environment) = split_environment(target);
219        let head = TARGET_ALIASES
220            .iter()
221            .find(|(alias, _)| *alias == head)
222            .map_or(head, |(_, canonical)| *canonical);
223        let parts: Vec<&str> = head.rsplitn(2, '_').collect();
224        if parts.len() != 2 {
225            return Err("Invalid target format: expected 'architecture_os'".to_string());
226        }
227        let (architecture, variant, spelling_environment) = parse_arch_token(parts[1])?;
228        let os = TargetOperatingSystem::from_str(parts[0]).map_err(str::to_string)?;
229        let target = Self::new(architecture, os).with_variant(variant);
230        match named_environment.or(spelling_environment) {
231            Some(environment) => target.with_environment(environment),
232            None => Ok(target),
233        }
234    }
235}
236
237/// Split a trailing environment field off a target string. The last underscore field is
238/// the environment only when it names one, so `x86_64_linux` is unaffected.
239fn split_environment(target: &str) -> (&str, Option<TargetEnvironment>) {
240    match target.rsplit_once('_') {
241        Some((head, tail)) => match tail.parse::<TargetEnvironment>() {
242            Ok(environment) => (head, Some(environment)),
243            Err(_) => (target, None),
244        },
245        None => (target, None),
246    }
247}
248
249impl fmt::Display for Target {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        write!(f, "{}_{}", self.arch_token(), self.operating_system)?;
252        if self.environment != self.implied_environment() {
253            write!(f, "_{}", self.environment)?;
254        }
255        Ok(())
256    }
257}
258
259/// Target strings that name a sub-architecture, with the environment the spelling implies
260/// when it is not the architecture's default. Open-ended RISC-V ISA strings are parsed
261/// instead, in [`parse_arch_token`].
262const VARIANT_SPELLINGS: &[(
263    &str,
264    TargetArchitecture,
265    ArchVariant,
266    Option<TargetEnvironment>,
267)] = &[
268    (
269        "armv7a-neon",
270        TargetArchitecture::Armv7,
271        ArchVariant::Armv7(Armv7Variant {
272            neon: true,
273            fpu: Some("neon-vfpv4"),
274            ..Armv7Variant::cortex_a7()
275        }),
276        None,
277    ),
278    (
279        "armv7a-sf",
280        TargetArchitecture::Armv7,
281        ArchVariant::Armv7(Armv7Variant {
282            fpu: None,
283            ..Armv7Variant::cortex_a7()
284        }),
285        Some(TargetEnvironment::Eabi),
286    ),
287    (
288        "armv7r",
289        TargetArchitecture::Armv7,
290        ArchVariant::Armv7(Armv7Variant {
291            profile: Armv7Profile::R,
292            fpu: None,
293            ..Armv7Variant::cortex_a7()
294        }),
295        Some(TargetEnvironment::Eabi),
296    ),
297    (
298        "aarch64v8.1",
299        TargetArchitecture::Aarch64,
300        ArchVariant::Aarch64(Aarch64Level::V8_1),
301        None,
302    ),
303    (
304        "aarch64v8.2",
305        TargetArchitecture::Aarch64,
306        ArchVariant::Aarch64(Aarch64Level::V8_2),
307        None,
308    ),
309    (
310        "ppc64le-power9",
311        TargetArchitecture::PowerPC64,
312        ArchVariant::PowerPC64(PowerLevel::Power9),
313        None,
314    ),
315    (
316        "ppc64le-power10",
317        TargetArchitecture::PowerPC64,
318        ArchVariant::PowerPC64(PowerLevel::Power10),
319        None,
320    ),
321];
322
323/// Architecture half of a target string, with the sub-architecture it names and the
324/// environment that spelling implies. A spelling that names only the architecture takes
325/// the architecture's variant-axis default and implies no environment.
326pub fn parse_arch_token(
327    token: &str,
328) -> Result<(TargetArchitecture, ArchVariant, Option<TargetEnvironment>), String> {
329    if let Some((_, arch, variant, environment)) = VARIANT_SPELLINGS
330        .iter()
331        .find(|(name, _, _, _)| *name == token)
332    {
333        return Ok((*arch, *variant, *environment));
334    }
335    if token.starts_with("rv") {
336        let isa = RiscvIsa::from_str(token).map_err(str::to_string)?;
337        let arch = match isa.xlen {
338            32 => TargetArchitecture::Riscv32,
339            64 => TargetArchitecture::Riscv64,
340            #[cfg(feature = "nightly")]
341            128 => TargetArchitecture::Riscv128,
342            _ => return Err("no backend for that RISC-V width".to_string()),
343        };
344        return Ok((arch, ArchVariant::Riscv(isa), None));
345    }
346    let arch = parse_architecture(token).map_err(str::to_string)?;
347    Ok((arch, arch.spec().variants.default_variant(), None))
348}
349
350/// The architecture a bare spelling names.
351///
352/// Refusals are consulted first, so a machine lamina has decided not to implement is
353/// refused with its reason rather than resolving to a neighbour.
354pub fn parse_architecture(spelling: &str) -> Result<TargetArchitecture, &'static str> {
355    if let Some((_, reason)) = REFUSED_SPELLINGS.iter().find(|(name, _)| *name == spelling) {
356        return Err(reason);
357    }
358    TargetArchitecture::from_spelling(spelling).ok_or("Unknown architecture")
359}
360
361impl FromStr for TargetArchitecture {
362    type Err = &'static str;
363
364    fn from_str(s: &str) -> Result<Self, Self::Err> {
365        parse_architecture(s)
366    }
367}
368
369impl fmt::Display for TargetArchitecture {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        write!(f, "{}", self.spec().token)
372    }
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum StackProbe {
377    PageLoop,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub enum TargetOperatingSystem {
382    Cuda,
383    AmdHsa,
384    Linux,
385    MacOS,
386    Windows,
387    Android,
388    IOS,
389    TvOS,
390    WatchOS,
391    VisionOS,
392    FreeBSD,
393    OpenBSD,
394    NetBSD,
395    DragonFly,
396    Redox,
397    Horizon,
398    Psp,
399    Psx,
400    Vita,
401    Orbis,
402    Prospero,
403    Wasi,
404    #[cfg(feature = "nightly")]
405    Artery,
406    Unknown,
407}
408
409impl TargetOperatingSystem {
410    pub const fn stack_probe(self) -> Option<StackProbe> {
411        match self {
412            Self::Windows | Self::Linux | Self::MacOS => Some(StackProbe::PageLoop),
413            _ => None,
414        }
415    }
416
417    /// Whether a target on this operating system links C runtime objects and a libc that
418    /// the environment axis chooses.
419    ///
420    /// False where the operating system supplies its own runtime — Darwin, Windows, WASI
421    /// — or where there is nothing to link at all, as on the GPU targets. Those declare
422    /// [`TargetEnvironment::Freestanding`] and the axis selects nothing for them.
423    pub const fn links_a_c_runtime(self) -> bool {
424        match self {
425            Self::Linux
426            | Self::Android
427            | Self::FreeBSD
428            | Self::OpenBSD
429            | Self::NetBSD
430            | Self::DragonFly
431            | Self::Redox
432            | Self::Horizon
433            | Self::Psp
434            | Self::Psx
435            | Self::Vita
436            | Self::Orbis
437            | Self::Prospero
438            | Self::Unknown => true,
439            Self::Cuda
440            | Self::AmdHsa
441            | Self::MacOS
442            | Self::Windows
443            | Self::IOS
444            | Self::TvOS
445            | Self::WatchOS
446            | Self::VisionOS
447            | Self::Wasi => false,
448            #[cfg(feature = "nightly")]
449            Self::Artery => false,
450        }
451    }
452}
453
454impl FromStr for TargetOperatingSystem {
455    type Err = &'static str;
456
457    fn from_str(s: &str) -> Result<Self, Self::Err> {
458        match s {
459            "cuda" => Ok(Self::Cuda),
460            "amdhsa" => Ok(Self::AmdHsa),
461            "linux" => Ok(Self::Linux),
462            "macos" => Ok(Self::MacOS),
463            "windows" => Ok(Self::Windows),
464            "android" => Ok(Self::Android),
465            "ios" => Ok(Self::IOS),
466            "tvos" => Ok(Self::TvOS),
467            "watchos" => Ok(Self::WatchOS),
468            "visionos" => Ok(Self::VisionOS),
469            "freebsd" => Ok(Self::FreeBSD),
470            "openbsd" => Err(
471                "Operating system 'openbsd' is not implemented\n  Hint: lamina has no OpenBSD syscall table, dynamic linker path or ELF brand. FreeBSD is the BSD that is implemented: write 'freebsd' (e.g. x86_64_freebsd)",
472            ),
473            "netbsd" => Err(
474                "Operating system 'netbsd' is not implemented\n  Hint: lamina has no NetBSD syscall table, dynamic linker path or ELF brand. FreeBSD is the BSD that is implemented: write 'freebsd' (e.g. x86_64_freebsd)",
475            ),
476            "dragonfly" => Err(
477                "Operating system 'dragonfly' is not implemented\n  Hint: lamina has no DragonFly syscall table, dynamic linker path or ELF brand. FreeBSD is the BSD that is implemented: write 'freebsd' (e.g. x86_64_freebsd)",
478            ),
479            "redox" => Ok(Self::Redox),
480            "horizon" => Ok(Self::Horizon),
481            "psp" => Ok(Self::Psp),
482            "psx" => Ok(Self::Psx),
483            "vita" => Ok(Self::Vita),
484            "orbis" => Ok(Self::Orbis),
485            "prospero" => Ok(Self::Prospero),
486            "wasi" => Ok(Self::Wasi),
487            "unknown" => Ok(Self::Unknown),
488            #[cfg(feature = "nightly")]
489            "artery" => Ok(Self::Artery),
490            other => OS_ALIASES
491                .iter()
492                .find(|(alias, _)| *alias == other)
493                .map_or(Err("Unknown operating system"), |(_, os)| Ok(*os)),
494        }
495    }
496}
497
498/// Second spellings of an operating system the driver accepts.
499///
500/// The parser and the list the driver prints both read this table, so an alias cannot
501/// be accepted and go unmentioned.
502pub const OS_ALIASES: &[(&str, TargetOperatingSystem)] = &[
503    ("bsd", TargetOperatingSystem::FreeBSD),
504    ("ps4", TargetOperatingSystem::Orbis),
505    ("ps5", TargetOperatingSystem::Prospero),
506];
507
508impl fmt::Display for TargetOperatingSystem {
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        let s = match self {
511            Self::Cuda => "cuda",
512            Self::AmdHsa => "amdhsa",
513            Self::Linux => "linux",
514            Self::MacOS => "macos",
515            Self::Windows => "windows",
516            Self::Android => "android",
517            Self::IOS => "ios",
518            Self::TvOS => "tvos",
519            Self::WatchOS => "watchos",
520            Self::VisionOS => "visionos",
521            Self::FreeBSD => "freebsd",
522            Self::OpenBSD => "openbsd",
523            Self::NetBSD => "netbsd",
524            Self::DragonFly => "dragonfly",
525            Self::Redox => "redox",
526            Self::Horizon => "horizon",
527            Self::Psp => "psp",
528            Self::Psx => "psx",
529            Self::Vita => "vita",
530            Self::Orbis => "orbis",
531            Self::Prospero => "prospero",
532            Self::Wasi => "wasi",
533            #[cfg(feature = "nightly")]
534            Self::Artery => "artery",
535            Self::Unknown => "unknown",
536        };
537        write!(f, "{s}")
538    }
539}
540
541const TARGET_ALIASES: &[(&str, &str)] = &[
542    ("riscv64_unknown", "riscv64_linux"),
543    ("riscv64gc_linux", "riscv64_linux"),
544];
545
546/// Every target spelling the driver accepts that [`host_arch_list`] does not name.
547///
548/// Gathered from the tables the parser itself reads: the architecture spellings in the
549/// arch table, the sub-architecture spellings in [`VARIANT_SPELLINGS`] and the
550/// operating-system aliases in [`OS_ALIASES`]. A spelling cannot be accepted and left
551/// out of the list the driver prints.
552///
553/// Open-ended RISC-V ISA strings such as `rv32imac` are parsed rather than tabulated,
554/// so they are not here; the driver names them separately.
555pub fn target_alias_list() -> Vec<String> {
556    let listed = host_arch_list();
557    let mut aliases: Vec<String> = Vec::new();
558    for arch in TargetArchitecture::all() {
559        let spec = arch.spec();
560        for target in spec.targets {
561            let Some((_, os)) = target.rsplit_once('_') else {
562                continue;
563            };
564            for spelling in spec.spellings {
565                aliases.push(format!("{spelling}_{os}"));
566            }
567            for (spelling, variant_arch, _, _) in VARIANT_SPELLINGS {
568                if *variant_arch == arch {
569                    aliases.push(format!("{spelling}_{os}"));
570                }
571            }
572            for (alias, aliased) in OS_ALIASES {
573                if os.parse::<TargetOperatingSystem>() == Ok(*aliased) {
574                    aliases.push(format!("{}_{alias}", spec.token));
575                }
576            }
577        }
578    }
579    for (alias, _) in TARGET_ALIASES {
580        aliases.push((*alias).to_string());
581    }
582    aliases.retain(|alias| !listed.contains(&alias.as_str()));
583    aliases.sort();
584    aliases.dedup();
585    aliases
586}
587
588/// Every target string the driver advertises, gathered from the arch table.
589///
590/// Derived from the same blocks the properties come from, so an architecture cannot be
591/// added without deciding which targets it offers, and the list cannot fall behind.
592pub fn host_arch_list() -> Vec<&'static str> {
593    TargetArchitecture::all()
594        .into_iter()
595        .flat_map(|arch| arch.spec().targets.iter().copied())
596        .collect()
597}
598
599#[cfg(test)]
600mod tests {
601    #![allow(clippy::expect_used, clippy::unwrap_used)]
602
603    use super::*;
604    use crate::arch_variant::VariantAxis;
605    use crate::cpu_level::X86CpuLevel;
606    use crate::environment::{CompilerRuntime, Libc};
607
608    fn listed(name: &str) -> bool {
609        host_arch_list().contains(&name)
610    }
611
612    #[test]
613    fn freebsd_is_a_target_the_driver_also_accepts() {
614        let target: Target = "x86_64_freebsd".parse().expect("freebsd parses");
615        assert_eq!(target.architecture, TargetArchitecture::X86_64);
616        assert_eq!(target.operating_system, TargetOperatingSystem::FreeBSD);
617        assert!(listed("x86_64_freebsd"));
618        assert_eq!(
619            "bsd".parse::<TargetOperatingSystem>(),
620            Ok(TargetOperatingSystem::FreeBSD)
621        );
622    }
623
624    #[test]
625    fn every_advertised_alias_parses_to_a_listed_target() {
626        let aliases = target_alias_list();
627        assert!(!aliases.is_empty(), "the alias list is empty");
628        for alias in &aliases {
629            let target: Target = alias
630                .parse()
631                .unwrap_or_else(|e| panic!("{alias} does not parse: {e}"));
632            let bare = Target::new(target.architecture, target.operating_system);
633            assert!(listed(&bare.to_string()), "{alias} resolves to {bare}");
634            assert!(!listed(alias), "{alias} is already a listed target");
635        }
636    }
637
638    #[test]
639    fn the_alias_list_names_the_spellings_from_every_table_it_draws_on() {
640        let aliases = target_alias_list();
641        for witness in [
642            "x86_64_bsd",
643            "x86_64_ps4",
644            "x86_64_ps5",
645            "i386_linux",
646            "x86_linux",
647            "systemz_linux",
648            "loong64_linux",
649            "mips64le_linux",
650            "powerpc64_linux",
651            "armv7m_unknown",
652            "cortex-m3_unknown",
653            "ppc64le-power10_linux",
654            "aarch64v8.2_linux",
655            "armv7a-neon_linux",
656        ] {
657            assert!(
658                aliases.iter().any(|alias| alias == witness),
659                "{witness} is missing from {aliases:?}"
660            );
661        }
662    }
663
664    #[test]
665    fn the_other_bsds_refuse_with_a_reason() {
666        for name in ["openbsd", "netbsd", "dragonfly"] {
667            let error = name
668                .parse::<TargetOperatingSystem>()
669                .expect_err("this BSD is not implemented");
670            assert!(error.contains("is not implemented"), "{name}: {error}");
671            assert!(error.contains("Hint:"), "{name}: {error}");
672            assert!(error.contains("freebsd"), "{name}: {error}");
673            assert!(!listed(&format!("x86_64_{name}")), "{name}");
674        }
675    }
676
677    #[test]
678    fn gpu_targets_round_trip() {
679        for (name, triple) in [
680            ("nvptx64_cuda", "nvptx64-nvidia-cuda"),
681            ("amdgcn_amdhsa", "amdgcn-amd-amdhsa"),
682        ] {
683            let target: Target = name.parse().unwrap();
684            assert_eq!(target.to_string(), name);
685            assert_eq!(target.gpu_triple(), Some(triple));
686            assert!(listed(name));
687        }
688    }
689
690    #[test]
691    fn parses_known_mobile_and_console_targets() {
692        assert_eq!(
693            Target::from_str("aarch64_ios").unwrap(),
694            Target::new(TargetArchitecture::Aarch64, TargetOperatingSystem::IOS)
695        );
696        assert_eq!(
697            Target::from_str("aarch64_horizon").unwrap(),
698            Target::new(TargetArchitecture::Aarch64, TargetOperatingSystem::Horizon)
699        );
700        assert_eq!(
701            Target::from_str("armv7_linux").unwrap(),
702            Target::new(TargetArchitecture::Armv7, TargetOperatingSystem::Linux)
703        );
704    }
705
706    #[test]
707    fn big_endian_powerpc64_is_elfv2_and_elfv1_is_refused() {
708        for name in ["ppc64_linux", "powerpc64_linux"] {
709            assert_eq!(
710                Target::from_str(name).unwrap(),
711                Target::new(
712                    TargetArchitecture::PowerPC64Be,
713                    TargetOperatingSystem::Linux
714                )
715            );
716        }
717        assert!(
718            Target::from_str("ppc64-elfv1_linux")
719                .unwrap_err()
720                .contains("ELFv1")
721        );
722        assert_eq!(
723            Target::from_str("ppc64le_linux").unwrap(),
724            Target::new(TargetArchitecture::PowerPC64, TargetOperatingSystem::Linux)
725        );
726        assert!(listed("ppc64le_linux"));
727        assert!(listed("ppc64_linux"));
728    }
729
730    #[test]
731    fn arm32_refuses_and_names_armv7() {
732        for name in ["arm32_linux", "arm32_android", "arm32_vita"] {
733            let error = Target::from_str(name).expect_err("arm32 is not a machine");
734            assert!(error.contains("is not implemented"), "{name}: {error}");
735            assert!(error.contains("Hint:"), "{name}: {error}");
736            assert!(error.contains("armv7"), "{name}: {error}");
737            assert!(!listed(name), "{name}");
738        }
739    }
740
741    #[test]
742    fn mips64_spells_both_byte_orders_and_refuses_o32() {
743        assert_eq!(
744            Target::from_str("mips64_linux").unwrap().endianness(),
745            Endianness::Big
746        );
747        assert_eq!(
748            Target::from_str("mips64el_linux").unwrap().endianness(),
749            Endianness::Little
750        );
751        assert!(Target::from_str("mips_linux").unwrap_err().contains("O32"));
752    }
753
754    #[test]
755    fn wasm_targets_round_trip() {
756        for name in ["wasm32_wasi", "wasm32_unknown", "wasm64_unknown"] {
757            let target: Target = name.parse().unwrap();
758            assert_eq!(target.to_string(), name);
759            assert!(listed(name));
760        }
761    }
762
763    #[test]
764    fn displays_known_mobile_and_console_os_names() {
765        assert_eq!(TargetOperatingSystem::VisionOS.to_string(), "visionos");
766        assert_eq!(TargetOperatingSystem::Psp.to_string(), "psp");
767        assert_eq!(TargetOperatingSystem::Psx.to_string(), "psx");
768    }
769
770    #[test]
771    fn riscv64_aliases_resolve_to_riscv64_linux() {
772        let linux = Target::new(TargetArchitecture::Riscv64, TargetOperatingSystem::Linux);
773        for name in ["riscv64_linux", "riscv64_unknown", "riscv64gc_linux"] {
774            let target: Target = name.parse().unwrap();
775            assert_eq!(target, linux);
776            assert!(listed(name));
777        }
778        assert_eq!(linux.to_string(), "riscv64_linux");
779    }
780
781    #[test]
782    fn pointer_width_follows_the_architecture() {
783        let width = |name: &str| name.parse::<Target>().unwrap().pointer_width();
784        assert_eq!(width("x86_64_linux"), PointerWidth::Bits64);
785        assert_eq!(width("aarch64_linux"), PointerWidth::Bits64);
786        assert_eq!(width("riscv64_unknown"), PointerWidth::Bits64);
787        assert_eq!(width("riscv32_unknown"), PointerWidth::Bits32);
788        assert_eq!(width("armv7_linux"), PointerWidth::Bits32);
789        assert_eq!(width("wasm32_unknown"), PointerWidth::Bits32);
790        assert_eq!(width("wasm64_unknown"), PointerWidth::Bits64);
791        assert_eq!(PointerWidth::Bits32.bytes(), 4);
792        assert_eq!(PointerWidth::Bits64.bytes(), 8);
793        assert_eq!(PointerWidth::Bits128.bytes(), 16);
794    }
795
796    #[test]
797    fn only_the_named_targets_are_big_endian() {
798        for name in host_arch_list() {
799            let target: Target = name.parse().expect(name);
800            let expected = match target.architecture {
801                TargetArchitecture::S390x
802                | TargetArchitecture::PowerPC64Be
803                | TargetArchitecture::Mips64 => Endianness::Big,
804                _ => Endianness::Little,
805            };
806            assert_eq!(target.endianness(), expected, "{name}");
807        }
808    }
809
810    #[test]
811    fn x86_64_and_aarch64_honor_argument_alignment() {
812        let honors = |name: &str| name.parse::<Target>().unwrap().honors_argument_alignment();
813        assert!(honors("x86_64_linux"));
814        assert!(honors("aarch64_linux"));
815        for name in [
816            "riscv64_linux",
817            "riscv32_linux",
818            "ppc64le_linux",
819            "i686_linux",
820        ] {
821            assert!(!honors(name), "{name}");
822        }
823    }
824
825    #[test]
826    fn stack_alignment_is_sixteen_where_there_is_a_stack() {
827        let align = |name: &str| name.parse::<Target>().unwrap().stack_alignment_at_call();
828        for name in [
829            "x86_64_linux",
830            "i686_linux",
831            "aarch64_linux",
832            "armv7_linux",
833            "riscv64_linux",
834            "ppc64le_linux",
835        ] {
836            assert_eq!(align(name), 16, "{name}");
837        }
838        assert_eq!(align("wasm32_wasi"), 1);
839    }
840
841    #[test]
842    fn only_native_targets_reach_a_linker() {
843        let artifact = |name: &str| name.parse::<Target>().unwrap().output_artifact();
844        assert_eq!(artifact("x86_64_linux"), OutputArtifact::Executable);
845        assert_eq!(artifact("wasm32_wasi"), OutputArtifact::Module);
846        assert_eq!(artifact("nvptx64_cuda"), OutputArtifact::Text);
847        assert_eq!(artifact("amdgcn_amdhsa"), OutputArtifact::Text);
848    }
849
850    #[test]
851    fn data_layout_separates_pointers_from_registers() {
852        let layout = "wasm32_wasi".parse::<Target>().unwrap().data_layout();
853        assert_eq!(layout.pointer_width, PointerWidth::Bits32);
854        assert_eq!(layout.int_register_bits, 64);
855        assert!(layout.has_64bit_int_registers());
856        assert_eq!(layout.endianness, Endianness::Little);
857    }
858
859    #[test]
860    fn bare_spellings_keep_their_default_variant() {
861        for name in ["riscv64_linux", "riscv64gc_linux", "riscv64_unknown"] {
862            let target: Target = name.parse().expect(name);
863            assert_eq!(
864                target.variant,
865                ArchVariant::Riscv(RiscvIsa::general(64)),
866                "{name}"
867            );
868        }
869        for name in [
870            "armv7_linux",
871            "aarch64_linux",
872            "ppc64le_linux",
873            "x86_64_linux",
874        ] {
875            let target: Target = name.parse().expect(name);
876            assert_eq!(
877                target.variant,
878                target.architecture.spec().variants.default_variant(),
879                "{name}"
880            );
881            assert_eq!(target.to_string(), name.replace("gc", ""), "{name}");
882        }
883    }
884
885    #[test]
886    fn variant_spellings_parse_and_print_back() {
887        for name in [
888            "rv32imac_linux",
889            "rv64gc_linux",
890            "armv7r_linux",
891            "armv7a-neon_linux",
892            "aarch64v8.2_linux",
893            "ppc64le-power9_linux",
894        ] {
895            let target: Target = name.parse().expect(name);
896            assert_ne!(
897                target.variant,
898                target.architecture.spec().variants.default_variant(),
899                "{name} should not be the default"
900            );
901            assert_eq!(target.to_string(), name, "{name} must round trip");
902        }
903        let rv: Target = "rv32imac_linux".parse().unwrap();
904        assert_eq!(rv.architecture, TargetArchitecture::Riscv32);
905        assert_eq!(rv.variant.riscv().map(|i| i.has_float()), Some(false));
906        let a64: Target = "aarch64v8.2_linux".parse().unwrap();
907        assert_eq!(a64.architecture, TargetArchitecture::Aarch64);
908        assert!(a64.variant.aarch64().unwrap().has_lse_atomics());
909    }
910
911    #[test]
912    fn int_registers_are_not_pointers() {
913        let regs = |name: &str| name.parse::<Target>().unwrap().has_64bit_int_registers();
914        assert!(regs("wasm32_wasi"));
915        assert!(regs("wasm64_unknown"));
916        assert!(regs("x86_64_linux"));
917        assert!(!regs("riscv32_linux"));
918        assert!(!regs("i686_linux"));
919        assert!(!regs("armv7_linux"));
920    }
921
922    #[test]
923    fn every_listed_target_prints_back_to_a_target_that_parses_the_same() {
924        for name in host_arch_list() {
925            let target: Target = name.parse().expect(name);
926            let printed = target.to_string();
927            assert_eq!(
928                printed.parse::<Target>().expect(&printed),
929                target,
930                "{name} printed as {printed}"
931            );
932        }
933    }
934
935    #[test]
936    fn the_variant_axis_rejects_another_familys_variant() {
937        let aarch64 = Target::new(TargetArchitecture::Aarch64, TargetOperatingSystem::Linux)
938            .with_variant(ArchVariant::Riscv(RiscvIsa::general(64)));
939        assert_eq!(aarch64.variant, ArchVariant::Aarch64(Aarch64Level::V8_0));
940        let riscv = Target::new(TargetArchitecture::Riscv64, TargetOperatingSystem::Linux)
941            .with_variant(ArchVariant::X86(X86CpuLevel::V4));
942        assert_eq!(riscv.variant, ArchVariant::Riscv(RiscvIsa::general(64)));
943    }
944
945    #[test]
946    fn x86_cpu_level_is_a_variant_axis_like_the_others() {
947        let spec = TargetArchitecture::X86_64.spec();
948        assert_eq!(spec.variants, VariantAxis::X86(X86CpuLevel::V2));
949        let target = Target::new(TargetArchitecture::X86_64, TargetOperatingSystem::Linux)
950            .with_variant(ArchVariant::X86(X86CpuLevel::V3));
951        assert_eq!(target.variant.x86(), Some(X86CpuLevel::V3));
952    }
953
954    #[test]
955    fn a_bare_linux_target_declares_its_environment() {
956        for name in ["s390x_linux", "x86_64_linux", "riscv64_linux"] {
957            let target: Target = name.parse().expect(name);
958            assert_eq!(target.environment, TargetEnvironment::Gnu, "{name}");
959            assert_eq!(target.environment.libc(), Some(Libc::Glibc), "{name}");
960            assert_eq!(
961                target.environment.compiler_runtime(),
962                CompilerRuntime::LaminaBuiltins,
963                "{name}"
964            );
965            assert_eq!(target.to_string(), name, "{name}");
966        }
967    }
968
969    #[test]
970    fn musl_is_a_target_string_and_picks_the_musl_interpreter() {
971        let target: Target = "s390x_linux_musl".parse().expect("musl target");
972        assert_eq!(target.environment, TargetEnvironment::Musl);
973        assert_eq!(target.interpreter(), Some("/lib/ld-musl-s390x.so.1"));
974        assert_eq!(target.to_string(), "s390x_linux_musl");
975        let gnu: Target = "s390x_linux".parse().unwrap();
976        assert_eq!(gnu.interpreter(), Some("/lib/ld64.so.1"));
977        assert_eq!(
978            gnu.spec().toolchain_triple(TargetEnvironment::Musl),
979            Some("s390x-linux-musl")
980        );
981    }
982
983    #[test]
984    fn an_environment_the_architecture_refuses_is_named_in_the_error() {
985        let error = Target::from_str("x86_64_linux_eabihf").expect_err("x86_64 has no EABI");
986        assert!(error.contains("eabihf"), "{error}");
987        assert!(error.contains("Hint:"), "{error}");
988        assert!(error.contains("gnu, musl, lamina, none"), "{error}");
989    }
990
991    #[test]
992    fn lamina_is_a_target_string_that_names_no_interpreter_and_no_toolchain() {
993        for name in ["x86_64_linux_lamina", "aarch64_linux_lamina"] {
994            let target: Target = name.parse().expect(name);
995            assert_eq!(target.environment, TargetEnvironment::Lamina, "{name}");
996            assert_eq!(target.environment.libc(), Some(Libc::Lamina), "{name}");
997            assert_eq!(
998                target.environment.compiler_runtime(),
999                CompilerRuntime::LaminaBuiltins,
1000                "{name}"
1001            );
1002            assert_eq!(target.interpreter(), None, "{name}");
1003            assert_eq!(
1004                target.spec().toolchain_triple(TargetEnvironment::Lamina),
1005                None,
1006                "{name}"
1007            );
1008            assert_eq!(target.to_string(), name, "{name}");
1009        }
1010    }
1011
1012    #[test]
1013    fn an_architecture_that_does_not_accept_lamina_says_so_by_name() {
1014        for name in ["s390x_linux_lamina", "riscv64_linux_lamina"] {
1015            let error = Target::from_str(name).expect_err(name);
1016            assert!(error.contains("lamina"), "{error}");
1017            assert!(error.contains("not supported"), "{error}");
1018        }
1019    }
1020
1021    #[test]
1022    fn no_architecture_states_a_lamina_interpreter_or_toolchain_yet() {
1023        for architecture in TargetArchitecture::all() {
1024            let spec = architecture.spec();
1025            assert_eq!(spec.interpreters.lamina, None, "{architecture:?}");
1026            assert_eq!(spec.toolchain.lamina, None, "{architecture:?}");
1027            assert_eq!(
1028                spec.interpreter(TargetEnvironment::Lamina),
1029                None,
1030                "{architecture:?}"
1031            );
1032            assert_eq!(
1033                spec.toolchain_triple(TargetEnvironment::Lamina),
1034                None,
1035                "{architecture:?}"
1036            );
1037        }
1038    }
1039
1040    #[test]
1041    fn no_architecture_defaults_to_the_lamina_environment() {
1042        for architecture in TargetArchitecture::all() {
1043            assert_ne!(
1044                architecture.spec().hosted_environment,
1045                TargetEnvironment::Lamina,
1046                "{architecture:?}"
1047            );
1048        }
1049    }
1050
1051    #[test]
1052    fn armv7_spellings_keep_their_float_abi() {
1053        let hard: Target = "armv7_linux".parse().unwrap();
1054        assert_eq!(hard.environment, TargetEnvironment::Eabihf);
1055        assert!(hard.environment.hardware_float());
1056        assert_eq!(hard.interpreter(), Some("/lib/ld-linux-armhf.so.3"));
1057        for name in ["armv7a-sf_linux", "armv7r_linux"] {
1058            let soft: Target = name.parse().expect(name);
1059            assert_eq!(soft.environment, TargetEnvironment::Eabi, "{name}");
1060            assert!(!soft.environment.hardware_float(), "{name}");
1061            assert_eq!(soft.interpreter(), Some("/lib/ld-linux.so.3"), "{name}");
1062            assert_eq!(soft.to_string(), name, "{name}");
1063        }
1064        let neon: Target = "armv7a-neon_linux".parse().unwrap();
1065        assert_eq!(neon.environment, TargetEnvironment::Eabihf);
1066    }
1067
1068    #[test]
1069    fn a_soft_float_riscv_isa_refines_the_interpreter() {
1070        let soft: Target = "rv64imac_linux".parse().unwrap();
1071        assert_eq!(soft.interpreter(), Some("/lib/ld-linux-riscv64-lp64.so.1"));
1072        let hard: Target = "riscv64_linux".parse().unwrap();
1073        assert_eq!(hard.interpreter(), Some("/lib/ld-linux-riscv64-lp64d.so.1"));
1074    }
1075
1076    #[test]
1077    fn targets_without_a_c_runtime_are_freestanding() {
1078        for name in ["x86_64_macos", "x86_64_windows", "wasm32_wasi"] {
1079            let target: Target = name.parse().expect(name);
1080            assert_eq!(
1081                target.environment,
1082                TargetEnvironment::Freestanding,
1083                "{name}"
1084            );
1085            assert!(!target.environment.is_hosted(), "{name}");
1086            assert_eq!(target.to_string(), name, "{name}");
1087        }
1088    }
1089
1090    #[test]
1091    fn msvc_library_arch_is_stated_or_absent_never_guessed() {
1092        assert_eq!(
1093            TargetArchitecture::X86_64.spec().msvc_library_arch,
1094            Some("x64")
1095        );
1096        assert_eq!(
1097            TargetArchitecture::Aarch64.spec().msvc_library_arch,
1098            Some("arm64")
1099        );
1100        assert_eq!(
1101            TargetArchitecture::I686.spec().msvc_library_arch,
1102            Some("x86")
1103        );
1104        for arch in [
1105            TargetArchitecture::S390x,
1106            TargetArchitecture::Riscv64,
1107            TargetArchitecture::PowerPC64,
1108            TargetArchitecture::Mips64,
1109            TargetArchitecture::LoongArch64,
1110        ] {
1111            assert_eq!(arch.spec().msvc_library_arch, None, "{arch}");
1112        }
1113    }
1114
1115    #[test]
1116    fn every_architecture_is_in_the_derived_list() {
1117        let all = TargetArchitecture::all();
1118        assert!(all.contains(&TargetArchitecture::X86_64));
1119        assert!(all.contains(&TargetArchitecture::S390x));
1120        let listed_architectures: Vec<TargetArchitecture> = host_arch_list()
1121            .into_iter()
1122            .filter_map(|name| name.parse::<Target>().ok())
1123            .map(|target| target.architecture)
1124            .collect();
1125        for arch in all {
1126            assert!(!arch.spec().token.is_empty(), "{arch}");
1127            let has_targets = !arch.spec().targets.is_empty();
1128            assert_eq!(
1129                has_targets,
1130                listed_architectures.contains(&arch),
1131                "{arch} target list disagrees with the advertised targets"
1132            );
1133        }
1134    }
1135}