Skip to main content

binius_utils/
platform_diagnostics.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3use std::{collections::BTreeMap, sync::OnceLock};
4
5use regex::Regex;
6
7// Build-time constants from environment variables
8const BUILD_TARGET: &str = env!("BUILD_TARGET");
9const BUILD_RUSTFLAGS: &str = env!("BUILD_RUSTFLAGS");
10const COMPILE_TIME_FEATURES_STR: &str = env!("COMPILE_TIME_FEATURES");
11
12// Lazy-initialized regex patterns for codebase scanning
13static CFG_REGEX: OnceLock<Regex> = OnceLock::new();
14static DETECT_REGEX: OnceLock<Regex> = OnceLock::new();
15
16/// Creates a regex pattern that matches Rust `#[cfg(target_feature = "...")]` attributes.
17///
18/// This pattern is used to scan Rust source files and extract CPU features that are
19/// conditionally compiled based on the target platform's capabilities.
20///
21/// # Pattern Details
22/// - Matches: `target_feature = "feature_name"`
23/// - Captures: The feature name (without quotes)
24/// - Handles: Variable whitespace around `=`
25///
26/// # Example Matches
27/// - `#[cfg(target_feature = "neon")]`
28/// - `#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]`
29fn cfg_feature_regex() -> &'static Regex {
30	CFG_REGEX.get_or_init(|| {
31		Regex::new(r#"target_feature\s*=\s*"([^"]+)""#)
32			.expect("Failed to compile cfg feature regex")
33	})
34}
35
36/// Creates a regex pattern that matches Rust feature detection macro calls.
37///
38/// This pattern is used to find runtime CPU feature detection in the codebase,
39/// such as `is_x86_feature_detected!("avx")` or `is_aarch64_feature_detected!("neon")`.
40///
41/// # Pattern Details
42/// - Matches: `is_*_feature_detected!("feature_name")`
43/// - Captures: The feature name (without quotes)
44/// - Handles: Any architecture prefix (x86, aarch64, etc.)
45///
46/// # Example Matches
47/// - `is_x86_feature_detected!("avx2")`
48/// - `is_aarch64_feature_detected!("neon")`
49/// - `if is_x86_feature_detected!("gfni") { ... }`
50fn runtime_detection_regex() -> &'static Regex {
51	DETECT_REGEX.get_or_init(|| {
52		Regex::new(r#"is_\w+_feature_detected!\s*\(\s*"([^"]+)"\s*\)"#)
53			.expect("Failed to compile runtime detection regex")
54	})
55}
56
57// Configuration constants
58mod config {
59	// CPU vendor detection strings
60	pub const VENDOR_PATTERNS: &[(&str, &str)] = &[
61		("apple", "Apple"),
62		("graviton", "AWS"),
63		("ampere", "Ampere"),
64		("intel", "Intel"),
65		("amd", "AMD"),
66	];
67	pub const VENDOR_GENERIC: &str = "Generic";
68
69	// Architecture names (used for testing)
70	#[cfg(test)]
71	pub const KNOWN_ARCHITECTURES: &[&str] =
72		&["x86_64", "aarch64", "arm", "riscv64", "wasm32", "wasm64"];
73
74	// Operating systems (used for testing)
75	#[cfg(test)]
76	pub const KNOWN_OS: &[&str] = &[
77		"linux", "macos", "windows", "freebsd", "openbsd", "netbsd", "android", "ios",
78	];
79
80	// Features to categorize as SIMD (for display purposes)
81	// Note: Features not in these lists will be categorized as "Other"
82	pub const SIMD_FEATURES: &[&str] = &[
83		"neon", "sve", "sve2", "dotprod", "fp16", "bf16", "i8mm", "f32mm", "f64mm", "fcma",
84	];
85
86	// Features to categorize as Crypto (for display purposes)
87	pub const CRYPTO_FEATURES: &[&str] = &["aes", "sha2", "sha3", "crc", "pmuv3"];
88
89	// Display settings
90	pub const MAX_DIRS_TO_SHOW: usize = 5;
91	pub const MAX_FILES_IN_DIR: usize = 10;
92
93	// Default values
94	pub const UNKNOWN_CPU: &str = "Unknown CPU";
95	pub const UNKNOWN_VERSION: &str = "unknown";
96
97	// Directory names to skip
98	pub const SKIP_DIRS: &[&str] = &["target", ".git", "node_modules"];
99	pub const RUST_FILE_EXT: &str = "rs";
100	pub const ARCH_DIR_NAME: &str = "arch";
101
102	// CPU target strategies
103	pub const CPU_TARGET_NATIVE: &str = "native";
104	pub const CPU_TARGET_GENERIC: &str = "generic";
105}
106
107pub struct PlatformDiagnostics {
108	hardware: HardwareInfo,
109	os_runtime: OSRuntimeInfo,
110	llvm_config: LLVMConfig,
111	code_features: CodeFeatures,
112	codebase_usage: CodebaseUsage,
113}
114
115#[derive(Debug)]
116struct HardwareInfo {
117	cpu_model: String,
118	architecture: &'static str,
119	vendor: String,
120	core_count: usize,
121}
122
123#[derive(Debug)]
124struct OSRuntimeInfo {
125	os: &'static str,
126	kernel_version: String,
127	runtime_features: BTreeMap<&'static str, bool>,
128}
129
130#[derive(Debug)]
131struct LLVMConfig {
132	target_triple: String,
133	target_cpu: String,
134}
135
136#[derive(Debug)]
137struct CodeFeatures {
138	compile_time_features: Vec<String>,
139}
140
141#[derive(Debug)]
142struct CodebaseUsage {
143	cfg_features: BTreeMap<String, Vec<String>>, // feature -> files using it
144	runtime_detections: BTreeMap<String, Vec<String>>, // feature -> files using runtime detection
145	arch_modules: Vec<String>,                   // architecture-specific modules found
146}
147
148// ANSI color codes
149const 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// Platform detection helper functions
160#[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// Runtime feature detection functions
181#[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	// Note: We can't use a loop here because the macro requires literal strings
187	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	// Note: We can't use a loop here because the macro requires literal strings
216	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				// For x86_64
271				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				// For ARM
275				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		// Parse target-cpu from RUSTFLAGS
336		// Handles both "-C target-cpu=native" and "-Ctarget-cpu=native" formats
337		let mut target_cpu = config::CPU_TARGET_GENERIC.to_string();
338
339		// Try to find target-cpu in RUSTFLAGS
340		for (i, part) in BUILD_RUSTFLAGS.split_whitespace().enumerate() {
341			if part == "-C" {
342				// Check next part for "target-cpu=value"
343				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				// Handle "-Ctarget-cpu=value" (no space)
351				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		CodeFeatures {
372			compile_time_features,
373		}
374	}
375
376	fn scan_codebase_usage() -> CodebaseUsage {
377		let mut cfg_features = BTreeMap::new();
378		let mut runtime_detections = BTreeMap::new();
379		let mut arch_modules = Vec::new();
380
381		// Try to find the workspace root
382		let workspace_root = std::env::var("CARGO_MANIFEST_DIR").ok().and_then(|dir| {
383			let path = std::path::Path::new(&dir);
384			// Walk up to find workspace root (has Cargo.toml with [workspace])
385			let mut current = Some(path);
386			while let Some(p) = current {
387				let cargo_toml = p.join("Cargo.toml");
388				if cargo_toml.exists()
389					&& let Ok(content) = std::fs::read_to_string(&cargo_toml)
390					&& content.contains("[workspace]")
391				{
392					return p.to_str().map(std::string::ToString::to_string);
393				}
394				current = p.parent();
395			}
396			None
397		});
398
399		if let Some(root) = workspace_root {
400			// Get regex patterns for scanning
401			let cfg_regex = cfg_feature_regex();
402			let detect_regex = runtime_detection_regex();
403
404			// Scan for arch modules and features
405			Self::scan_directory_with_regex(
406				std::path::Path::new(&root),
407				&mut cfg_features,
408				&mut runtime_detections,
409				&mut arch_modules,
410				&root,
411				cfg_regex,
412				detect_regex,
413			);
414
415			arch_modules.sort();
416			arch_modules.dedup();
417		}
418
419		CodebaseUsage {
420			cfg_features,
421			runtime_detections,
422			arch_modules,
423		}
424	}
425
426	fn scan_directory_with_regex(
427		dir: &std::path::Path,
428		cfg_features: &mut BTreeMap<String, Vec<String>>,
429		runtime_detections: &mut BTreeMap<String, Vec<String>>,
430		arch_modules: &mut Vec<String>,
431		root: &str,
432		cfg_regex: &Regex,
433		detect_regex: &Regex,
434	) {
435		// Skip common non-source directories
436		if let Some(name) = dir.file_name().and_then(|n| n.to_str())
437			&& (config::SKIP_DIRS.contains(&name) || name.starts_with('.'))
438		{
439			return;
440		}
441
442		if let Ok(entries) = std::fs::read_dir(dir) {
443			for entry in entries.flatten() {
444				let path = entry.path();
445
446				if path.is_dir() {
447					// Check if this is an arch module
448					if path.file_name() == Some(std::ffi::OsStr::new(config::ARCH_DIR_NAME)) {
449						// List subdirectories as arch modules
450						if let Ok(arch_entries) = std::fs::read_dir(&path) {
451							for arch_entry in arch_entries.flatten() {
452								if arch_entry.path().is_dir()
453									&& let Some(name) = arch_entry.file_name().to_str()
454								{
455									arch_modules.push(name.to_string());
456								}
457							}
458						}
459					}
460
461					// Recurse into subdirectory
462					Self::scan_directory_with_regex(
463						&path,
464						cfg_features,
465						runtime_detections,
466						arch_modules,
467						root,
468						cfg_regex,
469						detect_regex,
470					);
471				} else if path.extension() == Some(std::ffi::OsStr::new(config::RUST_FILE_EXT)) {
472					// Scan Rust file for features
473					if let Ok(content) = std::fs::read_to_string(&path) {
474						let relative_path = path
475							.strip_prefix(root)
476							.unwrap_or(&path)
477							.to_string_lossy()
478							.to_string();
479
480						// Find all cfg features
481						for cap in cfg_regex.captures_iter(&content) {
482							if let Some(feature_match) = cap.get(1) {
483								let feature = feature_match.as_str();
484								// Skip invalid feature names
485								if !feature.is_empty() && !feature.contains('.') {
486									cfg_features
487										.entry(feature.to_string())
488										.or_default()
489										.push(relative_path.clone());
490								}
491							}
492						}
493
494						// Find all runtime detections
495						for cap in detect_regex.captures_iter(&content) {
496							if let Some(feature_match) = cap.get(1) {
497								let feature = feature_match.as_str();
498								// Skip invalid feature names and generic placeholders
499								if !feature.is_empty()
500									&& !feature.contains('.') && feature != "feature"
501								{
502									runtime_detections
503										.entry(feature.to_string())
504										.or_default()
505										.push(relative_path.clone());
506								}
507							}
508						}
509					}
510				}
511			}
512		}
513	}
514
515	pub fn print(&self) {
516		println!("\n{BOLD}Platform Feature Report{RESET}\n");
517
518		// 1. Hardware
519		self.print_hardware();
520		println!();
521
522		// 2. OS/Runtime
523		self.print_os_runtime();
524		println!();
525
526		// 3. Compilation Target (LLVM)
527		self.print_llvm();
528		println!();
529
530		// 4. Available CPU Instructions
531		self.print_available_instructions();
532		println!();
533
534		// 5. Codebase Usage
535		self.print_codebase_usage();
536	}
537
538	fn print_hardware(&self) {
539		println!(
540			"{BOLD}{CYAN}Hardware:{RESET} {} {} ({} cores)",
541			self.hardware.vendor, self.hardware.architecture, self.hardware.core_count
542		);
543		println!("{CYAN}CPU:{RESET} {}", self.hardware.cpu_model);
544
545		match self.hardware.vendor.as_str() {
546			"Apple" => {
547				println!(
548					"{CYAN}Features:{RESET} {GREEN}✓{RESET}AMX, {GREEN}✓{RESET}Neural Engine, {GREEN}✓{RESET}P+E cores, {RED}✗{RESET}SVE/SVE2, {GREEN}✓{RESET}NEON"
549				);
550			}
551			"AWS" => {
552				println!(
553					"{CYAN}Features:{RESET} {GREEN}✓{RESET}SVE-256bit, {GREEN}✓{RESET}Server memory, {GREEN}✓{RESET}Large cache, {RED}✗{RESET}AMX, {GREEN}✓{RESET}NEON"
554				);
555			}
556			_ => {
557				println!(
558					"{CYAN}Features:{RESET} {YELLOW}?{RESET}Vendor-specific, {GREEN}✓{RESET}NEON, {YELLOW}?{RESET}Crypto"
559				);
560			}
561		}
562	}
563
564	fn print_os_runtime(&self) {
565		println!(
566			"{BOLD}{CYAN}OS/Runtime:{RESET} {} (kernel {})",
567			self.os_runtime.os, self.os_runtime.kernel_version
568		);
569
570		// Group features by status
571		let detected: Vec<&str> = self
572			.os_runtime
573			.runtime_features
574			.iter()
575			.filter(|(_, v)| **v)
576			.map(|(k, _)| *k)
577			.collect();
578		let not_found: Vec<&str> = self
579			.os_runtime
580			.runtime_features
581			.iter()
582			.filter(|(_, v)| !**v)
583			.map(|(k, _)| *k)
584			.collect();
585
586		if !detected.is_empty() {
587			println!("{GREEN}Detected:{RESET} {}", detected.join(", "));
588		}
589		if !not_found.is_empty() {
590			println!("{DIM}Not available:{RESET} {}", not_found.join(", "));
591		}
592	}
593
594	fn print_llvm(&self) {
595		println!("{BOLD}{CYAN}Compilation Target:{RESET}");
596		println!("{CYAN}Triple:{RESET} {}", self.llvm_config.target_triple);
597		println!("{CYAN}CPU:{RESET} {}", self.llvm_config.target_cpu);
598
599		match self.llvm_config.target_cpu.as_str() {
600			config::CPU_TARGET_NATIVE => {
601				println!(
602					"{CYAN}Strategy:{RESET} {YELLOW}Native{RESET} - Optimized for this specific CPU"
603				);
604				println!("{DIM}         Binary only runs on CPUs with same features{RESET}");
605			}
606			config::CPU_TARGET_GENERIC => {
607				println!(
608					"{CYAN}Strategy:{RESET} {GREEN}Generic{RESET} - Portable across all {} CPUs",
609					if self.llvm_config.target_triple.contains("aarch64") {
610						"ARM64"
611					} else if self.llvm_config.target_triple.contains("x86_64") {
612						"x86-64"
613					} else {
614						"target"
615					}
616				);
617				println!(
618					"{DIM}         Uses explicit features but no CPU-specific scheduling{RESET}"
619				);
620			}
621			cpu if cpu.starts_with("apple-") => {
622				println!(
623					"{CYAN}Strategy:{RESET} {MAGENTA}Apple Silicon{RESET} - Optimized for {cpu}"
624				);
625				println!("{DIM}         Enables AMX, disables SVE{RESET}");
626			}
627			cpu if cpu.contains("neoverse") => {
628				println!("{CYAN}Strategy:{RESET} {BLUE}Server ARM{RESET} - Optimized for {cpu}");
629				println!("{DIM}         Enables SVE, optimized for cloud workloads{RESET}");
630			}
631			_ => {
632				println!("{CYAN}Strategy:{RESET} Custom CPU target");
633			}
634		}
635	}
636
637	fn print_available_instructions(&self) {
638		println!("{BOLD}{CYAN}Available CPU Instructions:{RESET}");
639
640		// Group features by category
641		let mut simd_features = Vec::new();
642		let mut crypto_features = Vec::new();
643		let mut arch_features = Vec::new();
644
645		for feature in &self.code_features.compile_time_features {
646			if config::SIMD_FEATURES.contains(&feature.as_str()) {
647				simd_features.push(feature.as_str());
648			} else if config::CRYPTO_FEATURES.contains(&feature.as_str()) {
649				crypto_features.push(feature.as_str());
650			} else if !feature.starts_with("v8.") && feature != "vh" {
651				arch_features.push(feature.as_str());
652			}
653		}
654
655		println!(
656			"{CYAN}Total:{RESET} {} CPU features available to compiler",
657			self.code_features.compile_time_features.len()
658		);
659
660		if !simd_features.is_empty() {
661			simd_features.sort_unstable();
662			println!("  {GREEN}SIMD:{RESET} {}", simd_features.join(", "));
663		}
664		if !crypto_features.is_empty() {
665			crypto_features.sort_unstable();
666			println!("  {GREEN}Crypto:{RESET} {}", crypto_features.join(", "));
667		}
668		if !arch_features.is_empty() {
669			arch_features.sort_unstable();
670			// Always show the features, but wrap if too many
671			if arch_features.len() <= 6 {
672				println!("  {GREEN}Other:{RESET} {}", arch_features.join(", "));
673			} else {
674				// Show in multiple lines for readability
675				println!("  {GREEN}Other:{RESET}");
676				for chunk in arch_features.chunks(8) {
677					println!("    {}", chunk.join(", "));
678				}
679			}
680		}
681
682		// Show important missing features
683		#[cfg(target_arch = "aarch64")]
684		{
685			let important_missing = vec!["sve", "sve2"]
686				.into_iter()
687				.filter(|f| {
688					!self
689						.code_features
690						.compile_time_features
691						.iter()
692						.any(|feature| feature == f)
693				})
694				.collect::<Vec<_>>();
695			if !important_missing.is_empty() {
696				println!(
697					"  {DIM}Not available:{RESET} {} (code paths requiring these are excluded)",
698					important_missing.join(", ")
699				);
700			}
701		}
702	}
703
704	fn print_feature_locations(&self, _feature: &str, locations: &[String]) {
705		// Group files by directory
706		let mut by_dir: BTreeMap<String, Vec<String>> = BTreeMap::new();
707		for loc in locations {
708			if let Some(slash_pos) = loc.rfind('/') {
709				let dir = loc[..slash_pos].to_string();
710				let file = loc[slash_pos + 1..].to_string();
711				let files = by_dir.entry(dir).or_default();
712				if !files.contains(&file) {
713					files.push(file);
714				}
715			} else {
716				let files = by_dir.entry(String::new()).or_default();
717				if !files.contains(loc) {
718					files.push(loc.clone());
719				}
720			}
721		}
722
723		let mut shown = 0;
724		for (dir_count, (dir, files)) in by_dir.iter().enumerate() {
725			if dir_count >= config::MAX_DIRS_TO_SHOW && by_dir.len() > config::MAX_DIRS_TO_SHOW {
726				println!("    ... in {} more files", locations.len() - shown);
727				break;
728			}
729
730			if files.len() == 1 {
731				println!("    {}/{}", dir, files[0]);
732				shown += 1;
733			} else if files.len() <= config::MAX_FILES_IN_DIR {
734				// List all files if 10 or fewer
735				println!("    {}/: {}", dir, files.join(", "));
736				shown += files.len();
737			} else {
738				// Show first 10 files and indicate there are more
739				let first_10: Vec<_> = files
740					.iter()
741					.take(config::MAX_FILES_IN_DIR)
742					.cloned()
743					.collect();
744				println!(
745					"    {}/: {} (and {} more)",
746					dir,
747					first_10.join(", "),
748					files.len() - config::MAX_FILES_IN_DIR
749				);
750				shown += files.len();
751			}
752		}
753	}
754
755	fn print_codebase_usage(&self) {
756		// Always show the codebase section header
757		println!("{BOLD}{CYAN}Codebase Analysis:{RESET}");
758
759		if self.codebase_usage.cfg_features.is_empty()
760			&& self.codebase_usage.runtime_detections.is_empty()
761			&& self.codebase_usage.arch_modules.is_empty()
762		{
763			println!("{DIM}  No feature usage detected{RESET}");
764			return;
765		}
766
767		// Show arch modules first
768		if !self.codebase_usage.arch_modules.is_empty() {
769			println!(
770				"{MAGENTA}Arch modules:{RESET} {}",
771				self.codebase_usage.arch_modules.join(", ")
772			);
773		}
774
775		if !self.codebase_usage.cfg_features.is_empty() {
776			// Check which used features are enabled vs disabled
777			let mut enabled_used = Vec::new();
778			let mut disabled_used = Vec::new();
779
780			for feature in self.codebase_usage.cfg_features.keys() {
781				// Check if feature is enabled at compile time
782				if self.code_features.compile_time_features.contains(feature) {
783					enabled_used.push(feature.clone());
784				} else {
785					disabled_used.push(feature.clone());
786				}
787			}
788
789			if !enabled_used.is_empty() {
790				println!("{GREEN}Used & Enabled:{RESET}");
791				for feature in &enabled_used {
792					if let Some(locations) = self.codebase_usage.cfg_features.get(feature) {
793						println!("  {GREEN}{feature}:{RESET}");
794						self.print_feature_locations(feature, locations);
795					}
796				}
797			}
798
799			if !disabled_used.is_empty() {
800				println!("{DIM}Used but NOT Enabled:{RESET}");
801				for feature in &disabled_used {
802					if let Some(locations) = self.codebase_usage.cfg_features.get(feature) {
803						println!("  {DIM}{feature}:{RESET}");
804						self.print_feature_locations(feature, locations);
805					}
806				}
807			}
808		}
809
810		if !self.codebase_usage.runtime_detections.is_empty() {
811			let detections: Vec<String> = self
812				.codebase_usage
813				.runtime_detections
814				.keys()
815				.cloned()
816				.collect();
817			println!("{BLUE}Runtime detections:{RESET} {}", detections.join(", "));
818		}
819	}
820
821	/// Generate a feature suffix for benchmark names based on platform diagnostics
822	#[must_use]
823	pub fn get_feature_suffix(&self) -> String {
824		let mut suffix_parts = Vec::new();
825
826		// Threading - check how many threads rayon is actually using
827		if crate::rayon::current_num_threads() > 1 {
828			suffix_parts.push("mt");
829		} else {
830			suffix_parts.push("st");
831		}
832
833		// Architecture
834		#[cfg(target_arch = "x86_64")]
835		{
836			suffix_parts.push("x86");
837			// Add key features based on compile-time features
838			#[cfg(target_feature = "gfni")]
839			suffix_parts.push("gfni");
840			#[cfg(target_feature = "avx512f")]
841			suffix_parts.push("avx512");
842			#[cfg(all(not(target_feature = "avx512f"), target_feature = "avx2"))]
843			suffix_parts.push("avx2");
844		}
845
846		#[cfg(target_arch = "aarch64")]
847		{
848			suffix_parts.push("arm64");
849			// Check for NEON and AES
850			#[cfg(all(target_feature = "neon", target_feature = "aes"))]
851			suffix_parts.push("neon_aes");
852			#[cfg(all(target_feature = "neon", not(target_feature = "aes")))]
853			suffix_parts.push("neon");
854		}
855
856		suffix_parts.join("_")
857	}
858}
859
860#[cfg(test)]
861mod tests {
862	use super::*;
863
864	#[test]
865	fn test_platform_diagnostics() {
866		let diag = PlatformDiagnostics::gather();
867		diag.print();
868	}
869
870	#[test]
871	fn test_sanity() {
872		// Test that PlatformDiagnostics can be created without panicking
873		let diag = PlatformDiagnostics::gather();
874
875		// Test hardware info
876		assert!(!diag.hardware.cpu_model.is_empty(), "CPU model should not be empty");
877		assert!(!diag.hardware.vendor.is_empty(), "Vendor should not be empty");
878		assert!(diag.hardware.core_count >= 1, "Should have at least 1 core");
879		assert!(
880			config::KNOWN_ARCHITECTURES.contains(&diag.hardware.architecture),
881			"Architecture should be a known value"
882		);
883
884		// Test OS runtime info
885		assert!(!diag.os_runtime.kernel_version.is_empty(), "Kernel version should not be empty");
886		assert!(config::KNOWN_OS.contains(&diag.os_runtime.os), "OS should be a known value");
887
888		// Test LLVM config
889		assert!(!diag.llvm_config.target_triple.is_empty(), "Target triple should not be empty");
890		assert!(!diag.llvm_config.target_cpu.is_empty(), "Target CPU should not be empty");
891
892		// Test code features
893		// Compile-time features can be empty on some platforms
894		assert!(
895			diag.code_features.compile_time_features.is_empty()
896				|| diag
897					.code_features
898					.compile_time_features
899					.iter()
900					.all(|f| !f.is_empty()),
901			"All feature names should be non-empty"
902		);
903
904		// Test that print() doesn't panic
905		// Redirect output to avoid cluttering test output
906		let _output = std::panic::catch_unwind(|| {
907			diag.print();
908		});
909		assert!(_output.is_ok(), "print() should not panic");
910	}
911
912	#[test]
913	fn test_detect_vendor() {
914		assert_eq!(PlatformDiagnostics::detect_vendor("Apple M1 Pro"), "Apple");
915		assert_eq!(PlatformDiagnostics::detect_vendor("Intel Core i7"), "Intel");
916		assert_eq!(PlatformDiagnostics::detect_vendor("AMD Ryzen 9"), "AMD");
917		assert_eq!(PlatformDiagnostics::detect_vendor("AWS Graviton3"), "AWS");
918		assert_eq!(PlatformDiagnostics::detect_vendor("Ampere Altra"), "Ampere");
919		assert_eq!(PlatformDiagnostics::detect_vendor("Unknown CPU"), "Generic");
920	}
921}