Skip to main content

oxirs_core/
platform.rs

1//! Platform capabilities detection for OxiRS
2//!
3//! This module provides unified platform detection across the OxiRS ecosystem.
4//! All platform-specific code must use this module for capability detection.
5
6use std::sync::OnceLock;
7
8/// Platform capabilities detection result
9#[derive(Debug, Clone)]
10pub struct PlatformCapabilities {
11    /// SIMD support available
12    pub simd_available: bool,
13    /// GPU support available
14    pub gpu_available: bool,
15    /// CUDA support available
16    pub cuda_available: bool,
17    /// OpenCL support available
18    pub opencl_available: bool,
19    /// Metal support available (macOS)
20    pub metal_available: bool,
21    /// AVX2 instructions available
22    pub avx2_available: bool,
23    /// AVX512 instructions available
24    pub avx512_available: bool,
25    /// ARM NEON instructions available
26    pub neon_available: bool,
27    /// Number of CPU cores
28    pub cpu_cores: usize,
29    /// CPU architecture
30    pub arch: String,
31    /// Operating system
32    pub os: String,
33}
34
35// Cache the detected capabilities
36static CAPABILITIES: OnceLock<PlatformCapabilities> = OnceLock::new();
37
38impl PlatformCapabilities {
39    /// Detect platform capabilities
40    pub fn detect() -> &'static PlatformCapabilities {
41        CAPABILITIES.get_or_init(|| {
42            let mut caps = PlatformCapabilities {
43                simd_available: false,
44                gpu_available: false,
45                cuda_available: false,
46                opencl_available: false,
47                metal_available: false,
48                avx2_available: false,
49                avx512_available: false,
50                neon_available: false,
51                cpu_cores: std::thread::available_parallelism()
52                    .map(|n| n.get())
53                    .unwrap_or(1),
54                arch: std::env::consts::ARCH.to_string(),
55                os: std::env::consts::OS.to_string(),
56            };
57
58            // Detect SIMD capabilities
59            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
60            {
61                caps.simd_available = is_x86_feature_detected!("sse2");
62                caps.avx2_available = is_x86_feature_detected!("avx2");
63                caps.avx512_available = is_x86_feature_detected!("avx512f");
64            }
65
66            #[cfg(target_arch = "aarch64")]
67            {
68                caps.simd_available = true; // NEON is mandatory on aarch64
69                caps.neon_available = true;
70            }
71
72            // Detect GPU capabilities
73            caps.gpu_available = Self::detect_gpu();
74
75            // Detect CUDA
76            #[cfg(feature = "cuda")]
77            {
78                caps.cuda_available = Self::detect_cuda();
79            }
80
81            // Detect OpenCL
82            #[cfg(feature = "opencl")]
83            {
84                caps.opencl_available = Self::detect_opencl();
85            }
86
87            // Detect Metal (macOS only)
88            #[cfg(all(target_os = "macos", feature = "metal"))]
89            {
90                caps.metal_available = Self::detect_metal();
91            }
92
93            caps
94        })
95    }
96
97    /// Get a human-readable summary of capabilities
98    pub fn summary(&self) -> String {
99        let mut features = Vec::new();
100
101        if self.simd_available {
102            features.push("SIMD");
103
104            if self.avx2_available {
105                features.push("AVX2");
106            }
107            if self.avx512_available {
108                features.push("AVX512");
109            }
110            if self.neon_available {
111                features.push("NEON");
112            }
113        }
114
115        if self.gpu_available {
116            features.push("GPU");
117
118            if self.cuda_available {
119                features.push("CUDA");
120            }
121            if self.opencl_available {
122                features.push("OpenCL");
123            }
124            if self.metal_available {
125                features.push("Metal");
126            }
127        }
128
129        format!(
130            "{} ({} cores, {})",
131            features.join(", "),
132            self.cpu_cores,
133            self.arch
134        )
135    }
136
137    /// Check if any GPU is available
138    fn detect_gpu() -> bool {
139        // Simple heuristic - check for common GPU environment variables
140        std::env::var("CUDA_VISIBLE_DEVICES").is_ok()
141            || std::env::var("GPU_DEVICE_ORDINAL").is_ok()
142            || std::env::var("ROCR_VISIBLE_DEVICES").is_ok()
143    }
144
145    /// Check if CUDA is available
146    #[cfg(feature = "cuda")]
147    fn detect_cuda() -> bool {
148        // Check for CUDA runtime
149        std::env::var("CUDA_PATH").is_ok()
150            || std::path::Path::new("/usr/local/cuda").exists()
151            || std::path::Path::new("C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA")
152                .exists()
153    }
154
155    #[cfg(not(feature = "cuda"))]
156    #[allow(dead_code)]
157    fn detect_cuda() -> bool {
158        false
159    }
160
161    /// Check if OpenCL is available
162    #[cfg(feature = "opencl")]
163    #[allow(dead_code)]
164    fn detect_opencl() -> bool {
165        // Check for OpenCL libraries
166        #[cfg(target_os = "linux")]
167        {
168            std::path::Path::new("/usr/lib/libOpenCL.so").exists()
169                || std::path::Path::new("/usr/lib64/libOpenCL.so").exists()
170        }
171        #[cfg(target_os = "windows")]
172        {
173            std::path::Path::new("C:\\Windows\\System32\\OpenCL.dll").exists()
174        }
175        #[cfg(target_os = "macos")]
176        {
177            true // OpenCL is included in macOS
178        }
179        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
180        {
181            false
182        }
183    }
184
185    #[cfg(not(feature = "opencl"))]
186    #[allow(dead_code)]
187    fn detect_opencl() -> bool {
188        false
189    }
190
191    /// Check if Metal is available
192    #[cfg(all(target_os = "macos", feature = "metal"))]
193    #[allow(dead_code)]
194    fn detect_metal() -> bool {
195        // Metal is available on all modern macOS systems
196        true
197    }
198
199    #[cfg(not(all(target_os = "macos", feature = "metal")))]
200    #[allow(dead_code)]
201    fn detect_metal() -> bool {
202        false
203    }
204}
205
206/// Auto-optimizer for selecting best implementation based on problem size
207pub struct AutoOptimizer {
208    capabilities: &'static PlatformCapabilities,
209}
210
211impl AutoOptimizer {
212    /// Create a new auto-optimizer
213    pub fn new() -> Self {
214        Self {
215            capabilities: PlatformCapabilities::detect(),
216        }
217    }
218
219    /// Determine if GPU should be used based on problem size
220    pub fn should_use_gpu(&self, problem_size: usize) -> bool {
221        // Use GPU for large problems when available
222        self.capabilities.gpu_available && problem_size > 100_000
223    }
224
225    /// Determine if SIMD should be used based on problem size
226    pub fn should_use_simd(&self, problem_size: usize) -> bool {
227        // Use SIMD for medium to large problems
228        self.capabilities.simd_available && problem_size > 1000
229    }
230
231    /// Determine if parallel processing should be used
232    pub fn should_use_parallel(&self, problem_size: usize) -> bool {
233        // Use parallel processing for large problems on multi-core systems
234        self.capabilities.cpu_cores > 1 && problem_size > 10_000
235    }
236
237    /// Get recommended chunk size for parallel processing
238    pub fn recommended_chunk_size(&self, total_size: usize) -> usize {
239        // Balance between parallelism overhead and work distribution
240        let ideal_chunks = self.capabilities.cpu_cores * 4;
241        let chunk_size = total_size / ideal_chunks;
242
243        // Ensure reasonable chunk size
244        chunk_size.clamp(1000, 100_000)
245    }
246}
247
248impl Default for AutoOptimizer {
249    fn default() -> Self {
250        Self::new()
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_platform_detection() {
260        let caps = PlatformCapabilities::detect();
261
262        // Should have at least 1 CPU core
263        assert!(caps.cpu_cores >= 1);
264
265        // Should have valid architecture
266        assert!(!caps.arch.is_empty());
267
268        // Should have valid OS
269        assert!(!caps.os.is_empty());
270
271        println!("Platform capabilities: {}", caps.summary());
272    }
273
274    #[test]
275    fn test_auto_optimizer() {
276        let optimizer = AutoOptimizer::new();
277
278        // Small problem sizes should not use GPU
279        assert!(!optimizer.should_use_gpu(100));
280
281        // Medium problem sizes might use SIMD
282        let _ = optimizer.should_use_simd(5000);
283
284        // Get chunk size recommendation
285        let chunk_size = optimizer.recommended_chunk_size(1_000_000);
286        assert!(chunk_size > 0);
287    }
288}