1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Target {
28 pub architecture: TargetArchitecture,
29 pub operating_system: TargetOperatingSystem,
30 pub environment: TargetEnvironment,
33 pub variant: ArchVariant,
37}
38
39pub 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 pub fn with_variant(mut self, variant: ArchVariant) -> Self {
68 self.variant = self.architecture.spec().variants.accept(variant);
69 self
70 }
71
72 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 pub fn spec(&self) -> ArchSpec {
89 self.variant.refine(self.architecture.spec())
90 }
91
92 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 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 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 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
231fn 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
253const 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
317pub 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
344pub 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 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
480pub 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
528pub 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
570pub 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}