1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Target {
29 pub architecture: TargetArchitecture,
30 pub operating_system: TargetOperatingSystem,
31 pub environment: TargetEnvironment,
34 pub variant: ArchVariant,
38}
39
40pub 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 pub fn with_variant(mut self, variant: ArchVariant) -> Self {
69 self.variant = self.architecture.spec().variants.accept(variant);
70 self
71 }
72
73 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 pub fn spec(&self) -> ArchSpec {
90 self.variant.refine(self.architecture.spec())
91 }
92
93 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 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 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 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
237fn 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
259const 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
323pub 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
350pub 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 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
498pub 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
546pub 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
588pub 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}