1use std::{collections::BTreeMap, sync::OnceLock};
3
4use regex::Regex;
5
6const BUILD_TARGET: &str = env!("BUILD_TARGET");
8const BUILD_RUSTFLAGS: &str = env!("BUILD_RUSTFLAGS");
9const COMPILE_TIME_FEATURES_STR: &str = env!("COMPILE_TIME_FEATURES");
10
11static CFG_REGEX: OnceLock<Regex> = OnceLock::new();
13static DETECT_REGEX: OnceLock<Regex> = OnceLock::new();
14
15fn cfg_feature_regex() -> &'static Regex {
29 CFG_REGEX.get_or_init(|| {
30 Regex::new(r#"target_feature\s*=\s*"([^"]+)""#)
31 .expect("Failed to compile cfg feature regex")
32 })
33}
34
35fn runtime_detection_regex() -> &'static Regex {
50 DETECT_REGEX.get_or_init(|| {
51 Regex::new(r#"is_\w+_feature_detected!\s*\(\s*"([^"]+)"\s*\)"#)
52 .expect("Failed to compile runtime detection regex")
53 })
54}
55
56mod config {
58 pub const VENDOR_PATTERNS: &[(&str, &str)] = &[
60 ("apple", "Apple"),
61 ("graviton", "AWS"),
62 ("ampere", "Ampere"),
63 ("intel", "Intel"),
64 ("amd", "AMD"),
65 ];
66 pub const VENDOR_GENERIC: &str = "Generic";
67
68 #[cfg(test)]
70 pub const KNOWN_ARCHITECTURES: &[&str] =
71 &["x86_64", "aarch64", "arm", "riscv64", "wasm32", "wasm64"];
72
73 #[cfg(test)]
75 pub const KNOWN_OS: &[&str] = &[
76 "linux", "macos", "windows", "freebsd", "openbsd", "netbsd", "android", "ios",
77 ];
78
79 pub const SIMD_FEATURES: &[&str] = &[
82 "neon", "sve", "sve2", "dotprod", "fp16", "bf16", "i8mm", "f32mm", "f64mm", "fcma",
83 ];
84
85 pub const CRYPTO_FEATURES: &[&str] = &["aes", "sha2", "sha3", "crc", "pmuv3"];
87
88 pub const MAX_DIRS_TO_SHOW: usize = 5;
90 pub const MAX_FILES_IN_DIR: usize = 10;
91
92 pub const UNKNOWN_CPU: &str = "Unknown CPU";
94 pub const UNKNOWN_VERSION: &str = "unknown";
95
96 pub const SKIP_DIRS: &[&str] = &["target", ".git", "node_modules"];
98 pub const RUST_FILE_EXT: &str = "rs";
99 pub const ARCH_DIR_NAME: &str = "arch";
100
101 pub const CPU_TARGET_NATIVE: &str = "native";
103 pub const CPU_TARGET_GENERIC: &str = "generic";
104}
105
106pub struct PlatformDiagnostics {
107 hardware: HardwareInfo,
108 os_runtime: OSRuntimeInfo,
109 llvm_config: LLVMConfig,
110 code_features: CodeFeatures,
111 codebase_usage: CodebaseUsage,
112}
113
114#[derive(Debug)]
115struct HardwareInfo {
116 cpu_model: String,
117 architecture: &'static str,
118 vendor: String,
119 core_count: usize,
120}
121
122#[derive(Debug)]
123struct OSRuntimeInfo {
124 os: &'static str,
125 kernel_version: String,
126 runtime_features: BTreeMap<&'static str, bool>,
127}
128
129#[derive(Debug)]
130struct LLVMConfig {
131 target_triple: String,
132 target_cpu: String,
133}
134
135#[derive(Debug)]
136struct CodeFeatures {
137 compile_time_features: Vec<String>,
138 runtime_detected_features: BTreeMap<&'static str, bool>,
139}
140
141#[derive(Debug)]
142struct CodebaseUsage {
143 cfg_features: BTreeMap<String, Vec<String>>, runtime_detections: BTreeMap<String, Vec<String>>, arch_modules: Vec<String>, }
147
148const GREEN: &str = "\x1b[32m";
150const YELLOW: &str = "\x1b[33m";
151const BLUE: &str = "\x1b[34m";
152const RED: &str = "\x1b[31m";
153const CYAN: &str = "\x1b[36m";
154const MAGENTA: &str = "\x1b[35m";
155const RESET: &str = "\x1b[0m";
156const BOLD: &str = "\x1b[1m";
157const DIM: &str = "\x1b[2m";
158
159#[cfg(target_os = "macos")]
161fn get_macos_cpu_brand() -> Option<String> {
162 std::process::Command::new("sysctl")
163 .args(["-n", "machdep.cpu.brand_string"])
164 .output()
165 .ok()
166 .and_then(|o| String::from_utf8(o.stdout).ok())
167 .map(|s| s.trim().to_string())
168}
169
170#[cfg(target_os = "macos")]
171fn get_kernel_version_via_uname() -> Option<String> {
172 std::process::Command::new("uname")
173 .arg("-r")
174 .output()
175 .ok()
176 .and_then(|o| String::from_utf8(o.stdout).ok())
177 .map(|s| s.trim().to_string())
178}
179
180#[cfg(target_arch = "aarch64")]
182fn detect_aarch64_features() -> BTreeMap<&'static str, bool> {
183 use std::arch::is_aarch64_feature_detected;
184 let mut features = BTreeMap::new();
185
186 features.insert("neon", is_aarch64_feature_detected!("neon"));
188 features.insert("aes", is_aarch64_feature_detected!("aes"));
189 features.insert("sha2", is_aarch64_feature_detected!("sha2"));
190 features.insert("sha3", is_aarch64_feature_detected!("sha3"));
191 features.insert("crc", is_aarch64_feature_detected!("crc"));
192 features.insert("lse", is_aarch64_feature_detected!("lse"));
193 features.insert("dotprod", is_aarch64_feature_detected!("dotprod"));
194 features.insert("fp16", is_aarch64_feature_detected!("fp16"));
195 features.insert("sve", is_aarch64_feature_detected!("sve"));
196 features.insert("sve2", is_aarch64_feature_detected!("sve2"));
197 features.insert("fcma", is_aarch64_feature_detected!("fcma"));
198 features.insert("rcpc", is_aarch64_feature_detected!("rcpc"));
199 features.insert("rcpc2", is_aarch64_feature_detected!("rcpc2"));
200 features.insert("dpb", is_aarch64_feature_detected!("dpb"));
201 features.insert("dpb2", is_aarch64_feature_detected!("dpb2"));
202 features.insert("bf16", is_aarch64_feature_detected!("bf16"));
203 features.insert("i8mm", is_aarch64_feature_detected!("i8mm"));
204 features.insert("f32mm", is_aarch64_feature_detected!("f32mm"));
205 features.insert("f64mm", is_aarch64_feature_detected!("f64mm"));
206
207 features
208}
209
210#[cfg(target_arch = "x86_64")]
211fn detect_x86_64_features() -> BTreeMap<&'static str, bool> {
212 use std::arch::is_x86_feature_detected;
213 let mut features = BTreeMap::new();
214
215 features.insert("avx", is_x86_feature_detected!("avx"));
217 features.insert("avx2", is_x86_feature_detected!("avx2"));
218 features.insert("avx512f", is_x86_feature_detected!("avx512f"));
219 features.insert("gfni", is_x86_feature_detected!("gfni"));
220 features.insert("aes", is_x86_feature_detected!("aes"));
221 features.insert("pclmulqdq", is_x86_feature_detected!("pclmulqdq"));
222 features.insert("sha", is_x86_feature_detected!("sha"));
223 features.insert("vaes", is_x86_feature_detected!("vaes"));
224 features.insert("vpclmulqdq", is_x86_feature_detected!("vpclmulqdq"));
225
226 features
227}
228
229impl PlatformDiagnostics {
230 #[must_use]
231 pub fn gather() -> Self {
232 Self {
233 hardware: Self::detect_hardware(),
234 os_runtime: Self::detect_os_runtime(),
235 llvm_config: Self::parse_llvm_config(),
236 code_features: Self::analyze_code_features(),
237 codebase_usage: Self::scan_codebase_usage(),
238 }
239 }
240
241 fn detect_hardware() -> HardwareInfo {
242 let cpu_model = Self::get_cpu_model();
243 let vendor = Self::detect_vendor(&cpu_model);
244 let core_count = std::thread::available_parallelism()
245 .map(std::num::NonZeroUsize::get)
246 .unwrap_or(1);
247
248 HardwareInfo {
249 cpu_model,
250 architecture: std::env::consts::ARCH,
251 vendor,
252 core_count,
253 }
254 }
255
256 fn detect_vendor(cpu_model: &str) -> String {
257 let model_lower = cpu_model.to_lowercase();
258 for (pattern, vendor) in config::VENDOR_PATTERNS {
259 if model_lower.contains(pattern) {
260 return vendor.to_string();
261 }
262 }
263 config::VENDOR_GENERIC.to_string()
264 }
265
266 fn get_cpu_model() -> String {
267 #[cfg(target_os = "linux")]
268 {
269 if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
270 if let Some(line) = cpuinfo.lines().find(|l| l.starts_with("model name")) {
272 return line.split(':').nth(1).unwrap_or("").trim().to_string();
273 }
274 if let Some(line) = cpuinfo.lines().find(|l| l.starts_with("CPU implementer")) {
276 let implementer = line.split(':').nth(1).unwrap_or("").trim();
277 if let Some(part_line) = cpuinfo.lines().find(|l| l.starts_with("CPU part")) {
278 let part = part_line.split(':').nth(1).unwrap_or("").trim();
279 return format!("ARM implementer {implementer} part {part}");
280 }
281 }
282 }
283 }
284
285 #[cfg(target_os = "macos")]
286 {
287 if let Some(cpu_brand) = get_macos_cpu_brand() {
288 return cpu_brand;
289 }
290 }
291
292 config::UNKNOWN_CPU.to_string()
293 }
294
295 fn detect_os_runtime() -> OSRuntimeInfo {
296 #[cfg(target_arch = "aarch64")]
297 let runtime_features = detect_aarch64_features();
298
299 #[cfg(target_arch = "x86_64")]
300 let runtime_features = detect_x86_64_features();
301
302 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
303 let runtime_features = BTreeMap::new();
304
305 let kernel_version = Self::get_kernel_version();
306
307 OSRuntimeInfo {
308 os: std::env::consts::OS,
309 kernel_version,
310 runtime_features,
311 }
312 }
313
314 fn get_kernel_version() -> String {
315 #[cfg(target_os = "linux")]
316 {
317 std::fs::read_to_string("/proc/version")
318 .ok()
319 .and_then(|s| s.split_whitespace().nth(2).map(|s| s.to_string()))
320 .unwrap_or_else(|| config::UNKNOWN_VERSION.to_string())
321 }
322
323 #[cfg(target_os = "macos")]
324 {
325 get_kernel_version_via_uname().unwrap_or_else(|| config::UNKNOWN_VERSION.to_string())
326 }
327
328 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
329 {
330 config::UNKNOWN_VERSION.to_string()
331 }
332 }
333
334 fn parse_llvm_config() -> LLVMConfig {
335 let mut target_cpu = config::CPU_TARGET_GENERIC.to_string();
338
339 for (i, part) in BUILD_RUSTFLAGS.split_whitespace().enumerate() {
341 if part == "-C" {
342 if let Some(next) = BUILD_RUSTFLAGS.split_whitespace().nth(i + 1)
344 && let Some(cpu) = next.strip_prefix("target-cpu=")
345 {
346 target_cpu = cpu.to_string();
347 break;
348 }
349 } else if let Some(rest) = part.strip_prefix("-C") {
350 if let Some(cpu) = rest.strip_prefix("target-cpu=") {
352 target_cpu = cpu.to_string();
353 break;
354 }
355 }
356 }
357
358 LLVMConfig {
359 target_triple: BUILD_TARGET.to_string(),
360 target_cpu,
361 }
362 }
363
364 fn analyze_code_features() -> CodeFeatures {
365 let compile_time_features = COMPILE_TIME_FEATURES_STR
366 .split(',')
367 .filter(|s| !s.is_empty())
368 .map(|s| s.to_string())
369 .collect();
370
371 let runtime_detected_features = Self::detect_os_runtime().runtime_features;
372
373 CodeFeatures {
374 compile_time_features,
375 runtime_detected_features,
376 }
377 }
378
379 fn scan_codebase_usage() -> CodebaseUsage {
380 let mut cfg_features = BTreeMap::new();
381 let mut runtime_detections = BTreeMap::new();
382 let mut arch_modules = Vec::new();
383
384 let workspace_root = std::env::var("CARGO_MANIFEST_DIR").ok().and_then(|dir| {
386 let path = std::path::Path::new(&dir);
387 let mut current = Some(path);
389 while let Some(p) = current {
390 let cargo_toml = p.join("Cargo.toml");
391 if cargo_toml.exists()
392 && let Ok(content) = std::fs::read_to_string(&cargo_toml)
393 && content.contains("[workspace]")
394 {
395 return p.to_str().map(std::string::ToString::to_string);
396 }
397 current = p.parent();
398 }
399 None
400 });
401
402 if let Some(root) = workspace_root {
403 let cfg_regex = cfg_feature_regex();
405 let detect_regex = runtime_detection_regex();
406
407 Self::scan_directory_with_regex(
409 std::path::Path::new(&root),
410 &mut cfg_features,
411 &mut runtime_detections,
412 &mut arch_modules,
413 &root,
414 cfg_regex,
415 detect_regex,
416 );
417
418 arch_modules.sort();
419 arch_modules.dedup();
420 }
421
422 CodebaseUsage {
423 cfg_features,
424 runtime_detections,
425 arch_modules,
426 }
427 }
428
429 fn scan_directory_with_regex(
430 dir: &std::path::Path,
431 cfg_features: &mut BTreeMap<String, Vec<String>>,
432 runtime_detections: &mut BTreeMap<String, Vec<String>>,
433 arch_modules: &mut Vec<String>,
434 root: &str,
435 cfg_regex: &Regex,
436 detect_regex: &Regex,
437 ) {
438 if let Some(name) = dir.file_name().and_then(|n| n.to_str())
440 && (config::SKIP_DIRS.contains(&name) || name.starts_with('.'))
441 {
442 return;
443 }
444
445 if let Ok(entries) = std::fs::read_dir(dir) {
446 for entry in entries.flatten() {
447 let path = entry.path();
448
449 if path.is_dir() {
450 if path.file_name() == Some(std::ffi::OsStr::new(config::ARCH_DIR_NAME)) {
452 if let Ok(arch_entries) = std::fs::read_dir(&path) {
454 for arch_entry in arch_entries.flatten() {
455 if arch_entry.path().is_dir()
456 && let Some(name) = arch_entry.file_name().to_str()
457 {
458 arch_modules.push(name.to_string());
459 }
460 }
461 }
462 }
463
464 Self::scan_directory_with_regex(
466 &path,
467 cfg_features,
468 runtime_detections,
469 arch_modules,
470 root,
471 cfg_regex,
472 detect_regex,
473 );
474 } else if path.extension() == Some(std::ffi::OsStr::new(config::RUST_FILE_EXT)) {
475 if let Ok(content) = std::fs::read_to_string(&path) {
477 let relative_path = path
478 .strip_prefix(root)
479 .unwrap_or(&path)
480 .to_string_lossy()
481 .to_string();
482
483 for cap in cfg_regex.captures_iter(&content) {
485 if let Some(feature_match) = cap.get(1) {
486 let feature = feature_match.as_str();
487 if !feature.is_empty() && !feature.contains('.') {
489 cfg_features
490 .entry(feature.to_string())
491 .or_default()
492 .push(relative_path.clone());
493 }
494 }
495 }
496
497 for cap in detect_regex.captures_iter(&content) {
499 if let Some(feature_match) = cap.get(1) {
500 let feature = feature_match.as_str();
501 if !feature.is_empty()
503 && !feature.contains('.') && feature != "feature"
504 {
505 runtime_detections
506 .entry(feature.to_string())
507 .or_default()
508 .push(relative_path.clone());
509 }
510 }
511 }
512 }
513 }
514 }
515 }
516 }
517
518 pub fn print(&self) {
519 println!("\n{BOLD}Platform Feature Report{RESET}\n");
520
521 self.print_hardware();
523 println!();
524
525 self.print_os_runtime();
527 println!();
528
529 self.print_llvm();
531 println!();
532
533 self.print_available_instructions();
535 println!();
536
537 self.print_codebase_usage();
539 }
540
541 fn print_hardware(&self) {
542 println!(
543 "{BOLD}{CYAN}Hardware:{RESET} {} {} ({} cores)",
544 self.hardware.vendor, self.hardware.architecture, self.hardware.core_count
545 );
546 println!("{CYAN}CPU:{RESET} {}", self.hardware.cpu_model);
547
548 match self.hardware.vendor.as_str() {
549 "Apple" => {
550 println!(
551 "{CYAN}Features:{RESET} {GREEN}✓{RESET}AMX, {GREEN}✓{RESET}Neural Engine, {GREEN}✓{RESET}P+E cores, {RED}✗{RESET}SVE/SVE2, {GREEN}✓{RESET}NEON"
552 );
553 }
554 "AWS" => {
555 println!(
556 "{CYAN}Features:{RESET} {GREEN}✓{RESET}SVE-256bit, {GREEN}✓{RESET}Server memory, {GREEN}✓{RESET}Large cache, {RED}✗{RESET}AMX, {GREEN}✓{RESET}NEON"
557 );
558 }
559 _ => {
560 println!(
561 "{CYAN}Features:{RESET} {YELLOW}?{RESET}Vendor-specific, {GREEN}✓{RESET}NEON, {YELLOW}?{RESET}Crypto"
562 );
563 }
564 }
565 }
566
567 fn print_os_runtime(&self) {
568 println!(
569 "{BOLD}{CYAN}OS/Runtime:{RESET} {} (kernel {})",
570 self.os_runtime.os, self.os_runtime.kernel_version
571 );
572
573 let detected: Vec<&str> = self
575 .os_runtime
576 .runtime_features
577 .iter()
578 .filter(|(_, v)| **v)
579 .map(|(k, _)| *k)
580 .collect();
581 let not_found: Vec<&str> = self
582 .os_runtime
583 .runtime_features
584 .iter()
585 .filter(|(_, v)| !**v)
586 .map(|(k, _)| *k)
587 .collect();
588
589 if !detected.is_empty() {
590 println!("{GREEN}Detected:{RESET} {}", detected.join(", "));
591 }
592 if !not_found.is_empty() {
593 println!("{DIM}Not available:{RESET} {}", not_found.join(", "));
594 }
595 }
596
597 fn print_llvm(&self) {
598 println!("{BOLD}{CYAN}Compilation Target:{RESET}");
599 println!("{CYAN}Triple:{RESET} {}", self.llvm_config.target_triple);
600 println!("{CYAN}CPU:{RESET} {}", self.llvm_config.target_cpu);
601
602 match self.llvm_config.target_cpu.as_str() {
603 config::CPU_TARGET_NATIVE => {
604 println!(
605 "{CYAN}Strategy:{RESET} {YELLOW}Native{RESET} - Optimized for this specific CPU"
606 );
607 println!("{DIM} Binary only runs on CPUs with same features{RESET}");
608 }
609 config::CPU_TARGET_GENERIC => {
610 println!(
611 "{CYAN}Strategy:{RESET} {GREEN}Generic{RESET} - Portable across all {} CPUs",
612 if self.llvm_config.target_triple.contains("aarch64") {
613 "ARM64"
614 } else if self.llvm_config.target_triple.contains("x86_64") {
615 "x86-64"
616 } else {
617 "target"
618 }
619 );
620 println!(
621 "{DIM} Uses explicit features but no CPU-specific scheduling{RESET}"
622 );
623 }
624 cpu if cpu.starts_with("apple-") => {
625 println!(
626 "{CYAN}Strategy:{RESET} {MAGENTA}Apple Silicon{RESET} - Optimized for {cpu}"
627 );
628 println!("{DIM} Enables AMX, disables SVE{RESET}");
629 }
630 cpu if cpu.contains("neoverse") => {
631 println!("{CYAN}Strategy:{RESET} {BLUE}Server ARM{RESET} - Optimized for {cpu}");
632 println!("{DIM} Enables SVE, optimized for cloud workloads{RESET}");
633 }
634 _ => {
635 println!("{CYAN}Strategy:{RESET} Custom CPU target");
636 }
637 }
638 }
639
640 fn print_available_instructions(&self) {
641 println!("{BOLD}{CYAN}Available CPU Instructions:{RESET}");
642
643 let mut simd_features = Vec::new();
645 let mut crypto_features = Vec::new();
646 let mut arch_features = Vec::new();
647
648 for feature in &self.code_features.compile_time_features {
649 if config::SIMD_FEATURES.contains(&feature.as_str()) {
650 simd_features.push(feature.as_str());
651 } else if config::CRYPTO_FEATURES.contains(&feature.as_str()) {
652 crypto_features.push(feature.as_str());
653 } else if !feature.starts_with("v8.") && feature != "vh" {
654 arch_features.push(feature.as_str());
655 }
656 }
657
658 println!(
659 "{CYAN}Total:{RESET} {} CPU features available to compiler",
660 self.code_features.compile_time_features.len()
661 );
662
663 if !simd_features.is_empty() {
664 simd_features.sort_unstable();
665 println!(" {GREEN}SIMD:{RESET} {}", simd_features.join(", "));
666 }
667 if !crypto_features.is_empty() {
668 crypto_features.sort_unstable();
669 println!(" {GREEN}Crypto:{RESET} {}", crypto_features.join(", "));
670 }
671 if !arch_features.is_empty() {
672 arch_features.sort_unstable();
673 if arch_features.len() <= 6 {
675 println!(" {GREEN}Other:{RESET} {}", arch_features.join(", "));
676 } else {
677 println!(" {GREEN}Other:{RESET}");
679 for chunk in arch_features.chunks(8) {
680 println!(" {}", chunk.join(", "));
681 }
682 }
683 }
684
685 #[cfg(target_arch = "aarch64")]
687 {
688 let important_missing = vec!["sve", "sve2"]
689 .into_iter()
690 .filter(|f| {
691 !self
692 .code_features
693 .compile_time_features
694 .iter()
695 .any(|feature| feature == f)
696 })
697 .collect::<Vec<_>>();
698 if !important_missing.is_empty() {
699 println!(
700 " {DIM}Not available:{RESET} {} (code paths requiring these are excluded)",
701 important_missing.join(", ")
702 );
703 }
704 }
705 }
706
707 fn print_feature_locations(&self, _feature: &str, locations: &[String]) {
708 let mut by_dir: BTreeMap<String, Vec<String>> = BTreeMap::new();
710 for loc in locations {
711 if let Some(slash_pos) = loc.rfind('/') {
712 let dir = loc[..slash_pos].to_string();
713 let file = loc[slash_pos + 1..].to_string();
714 let files = by_dir.entry(dir).or_default();
715 if !files.contains(&file) {
716 files.push(file);
717 }
718 } else {
719 let files = by_dir.entry(String::new()).or_default();
720 if !files.contains(loc) {
721 files.push(loc.clone());
722 }
723 }
724 }
725
726 let mut shown = 0;
727 for (dir_count, (dir, files)) in by_dir.iter().enumerate() {
728 if dir_count >= config::MAX_DIRS_TO_SHOW && by_dir.len() > config::MAX_DIRS_TO_SHOW {
729 println!(" ... in {} more files", locations.len() - shown);
730 break;
731 }
732
733 if files.len() == 1 {
734 println!(" {}/{}", dir, files[0]);
735 shown += 1;
736 } else if files.len() <= config::MAX_FILES_IN_DIR {
737 println!(" {}/: {}", dir, files.join(", "));
739 shown += files.len();
740 } else {
741 let first_10: Vec<_> = files
743 .iter()
744 .take(config::MAX_FILES_IN_DIR)
745 .cloned()
746 .collect();
747 println!(
748 " {}/: {} (and {} more)",
749 dir,
750 first_10.join(", "),
751 files.len() - config::MAX_FILES_IN_DIR
752 );
753 shown += files.len();
754 }
755 }
756 }
757
758 fn print_codebase_usage(&self) {
759 println!("{BOLD}{CYAN}Codebase Analysis:{RESET}");
761
762 if self.codebase_usage.cfg_features.is_empty()
763 && self.codebase_usage.runtime_detections.is_empty()
764 && self.codebase_usage.arch_modules.is_empty()
765 {
766 println!("{DIM} No feature usage detected{RESET}");
767 return;
768 }
769
770 if !self.codebase_usage.arch_modules.is_empty() {
772 println!(
773 "{MAGENTA}Arch modules:{RESET} {}",
774 self.codebase_usage.arch_modules.join(", ")
775 );
776 }
777
778 if !self.codebase_usage.cfg_features.is_empty() {
779 let mut enabled_used = Vec::new();
781 let mut disabled_used = Vec::new();
782
783 for feature in self.codebase_usage.cfg_features.keys() {
784 if self.code_features.compile_time_features.contains(feature) {
786 enabled_used.push(feature.clone());
787 } else {
788 disabled_used.push(feature.clone());
789 }
790 }
791
792 if !enabled_used.is_empty() {
793 println!("{GREEN}Used & Enabled:{RESET}");
794 for feature in &enabled_used {
795 if let Some(locations) = self.codebase_usage.cfg_features.get(feature) {
796 println!(" {GREEN}{feature}:{RESET}");
797 self.print_feature_locations(feature, locations);
798 }
799 }
800 }
801
802 if !disabled_used.is_empty() {
803 println!("{DIM}Used but NOT Enabled:{RESET}");
804 for feature in &disabled_used {
805 if let Some(locations) = self.codebase_usage.cfg_features.get(feature) {
806 println!(" {DIM}{feature}:{RESET}");
807 self.print_feature_locations(feature, locations);
808 }
809 }
810 }
811 }
812
813 if !self.codebase_usage.runtime_detections.is_empty() {
814 let detections: Vec<String> = self
815 .codebase_usage
816 .runtime_detections
817 .keys()
818 .cloned()
819 .collect();
820 println!("{BLUE}Runtime detections:{RESET} {}", detections.join(", "));
821 }
822 }
823
824 #[must_use]
825 pub fn get_summary(&self) -> PlatformSummary {
826 let has_mismatches =
827 self.code_features.compile_time_features.iter().any(|f| {
828 self.code_features.runtime_detected_features.get(f.as_str()) == Some(&false)
829 });
830
831 PlatformSummary {
832 platform: format!("{} on {}", self.hardware.vendor, self.hardware.architecture),
833 cpu: self.hardware.cpu_model.clone(),
834 target: self.llvm_config.target_triple.clone(),
835 target_cpu: self.llvm_config.target_cpu.clone(),
836 has_feature_mismatches: has_mismatches,
837 }
838 }
839
840 #[must_use]
842 pub fn get_feature_suffix(&self) -> String {
843 let mut suffix_parts = Vec::new();
844
845 #[cfg(feature = "rayon")]
847 suffix_parts.push("mt");
848 #[cfg(not(feature = "rayon"))]
849 suffix_parts.push("st");
850
851 #[cfg(target_arch = "x86_64")]
853 {
854 suffix_parts.push("x86");
855 #[cfg(target_feature = "gfni")]
857 suffix_parts.push("gfni");
858 #[cfg(target_feature = "avx512f")]
859 suffix_parts.push("avx512");
860 #[cfg(all(not(target_feature = "avx512f"), target_feature = "avx2"))]
861 suffix_parts.push("avx2");
862 }
863
864 #[cfg(target_arch = "aarch64")]
865 {
866 suffix_parts.push("arm64");
867 #[cfg(all(target_feature = "neon", target_feature = "aes"))]
869 suffix_parts.push("neon_aes");
870 #[cfg(all(target_feature = "neon", not(target_feature = "aes")))]
871 suffix_parts.push("neon");
872 }
873
874 suffix_parts.join("_")
875 }
876}
877
878pub struct PlatformSummary {
879 pub platform: String,
880 pub cpu: String,
881 pub target: String,
882 pub target_cpu: String,
883 pub has_feature_mismatches: bool,
884}
885
886#[cfg(test)]
887mod tests {
888 use super::*;
889
890 #[test]
891 fn test_platform_diagnostics() {
892 let diag = PlatformDiagnostics::gather();
893 diag.print();
894
895 let summary = diag.get_summary();
897 assert!(!summary.platform.is_empty());
898 assert!(!summary.cpu.is_empty());
899 assert!(!summary.target.is_empty());
900 }
901
902 #[test]
903 fn test_sanity() {
904 let diag = PlatformDiagnostics::gather();
906
907 assert!(!diag.hardware.cpu_model.is_empty(), "CPU model should not be empty");
909 assert!(!diag.hardware.vendor.is_empty(), "Vendor should not be empty");
910 assert!(diag.hardware.core_count >= 1, "Should have at least 1 core");
911 assert!(
912 config::KNOWN_ARCHITECTURES.contains(&diag.hardware.architecture),
913 "Architecture should be a known value"
914 );
915
916 assert!(!diag.os_runtime.kernel_version.is_empty(), "Kernel version should not be empty");
918 assert!(config::KNOWN_OS.contains(&diag.os_runtime.os), "OS should be a known value");
919
920 assert!(!diag.llvm_config.target_triple.is_empty(), "Target triple should not be empty");
922 assert!(!diag.llvm_config.target_cpu.is_empty(), "Target CPU should not be empty");
923
924 assert!(
927 diag.code_features.compile_time_features.is_empty()
928 || diag
929 .code_features
930 .compile_time_features
931 .iter()
932 .all(|f| !f.is_empty()),
933 "All feature names should be non-empty"
934 );
935
936 let summary = diag.get_summary();
938 assert!(!summary.platform.is_empty(), "Summary platform should not be empty");
939 assert!(!summary.cpu.is_empty(), "Summary CPU should not be empty");
940 assert!(!summary.target.is_empty(), "Summary target should not be empty");
941 assert!(!summary.target_cpu.is_empty(), "Summary target CPU should not be empty");
942
943 let _output = std::panic::catch_unwind(|| {
946 diag.print();
947 });
948 assert!(_output.is_ok(), "print() should not panic");
949 }
950
951 #[test]
952 fn test_detect_vendor() {
953 assert_eq!(PlatformDiagnostics::detect_vendor("Apple M1 Pro"), "Apple");
954 assert_eq!(PlatformDiagnostics::detect_vendor("Intel Core i7"), "Intel");
955 assert_eq!(PlatformDiagnostics::detect_vendor("AMD Ryzen 9"), "AMD");
956 assert_eq!(PlatformDiagnostics::detect_vendor("AWS Graviton3"), "AWS");
957 assert_eq!(PlatformDiagnostics::detect_vendor("Ampere Altra"), "Ampere");
958 assert_eq!(PlatformDiagnostics::detect_vendor("Unknown CPU"), "Generic");
959 }
960}