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