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