clock_hash/
cpuid.rs

1//! CPUID interface for safe CPU feature detection
2//!
3//! This module provides a safe, structured interface to the CPUID instruction
4//! for detecting CPU features and capabilities. It encapsulates the low-level
5//! x86/x86_64 intrinsics with proper error handling and abstraction.
6
7/// CPUID result structure containing the four registers returned by CPUID
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct CpuidResult {
10    /// EAX register
11    pub eax: u32,
12    /// EBX register
13    pub ebx: u32,
14    /// ECX register
15    pub ecx: u32,
16    /// EDX register
17    pub edx: u32,
18}
19
20/// CPU vendor information
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum CpuVendor {
23    /// Intel CPU
24    Intel,
25    /// AMD CPU
26    Amd,
27    /// Other/Unknown vendor
28    Other,
29}
30
31/// CPU feature flags for AVX-512 detection
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Avx512Features {
34    /// AVX-512 Foundation
35    pub avx512f: bool,
36    /// AVX-512 Byte and Word
37    pub avx512bw: bool,
38    /// AVX-512 Vector Length
39    pub avx512vl: bool,
40    /// AVX-512 Doubleword and Quadword
41    pub avx512dq: bool,
42}
43
44/// Execute CPUID instruction with the specified leaf and subleaf
45///
46/// # Arguments
47/// * `leaf` - The CPUID leaf (EAX input)
48/// * `subleaf` - The CPUID subleaf (ECX input), defaults to 0
49///
50/// # Returns
51/// The CPUID result containing EAX, EBX, ECX, EDX registers
52///
53/// # Safety
54/// This function is safe as it only reads CPU state and doesn't modify anything.
55/// However, CPUID behavior can vary between CPU models and virtualization environments.
56#[inline]
57pub fn cpuid(leaf: u32, subleaf: u32) -> CpuidResult {
58    #[cfg(target_arch = "x86_64")]
59    unsafe {
60        let result = core::arch::x86_64::__cpuid_count(leaf, subleaf);
61        CpuidResult {
62            eax: result.eax,
63            ebx: result.ebx,
64            ecx: result.ecx,
65            edx: result.edx,
66        }
67    }
68
69    #[cfg(target_arch = "x86")]
70    unsafe {
71        let result = core::arch::x86::__cpuid_count(leaf, subleaf);
72        CpuidResult {
73            eax: result.eax,
74            ebx: result.ebx,
75            ecx: result.ecx,
76            edx: result.edx,
77        }
78    }
79
80    #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
81    {
82        // Return zero for unsupported architectures
83        CpuidResult {
84            eax: 0,
85            ebx: 0,
86            ecx: 0,
87            edx: 0,
88        }
89    }
90}
91
92/// Get CPU vendor string
93///
94/// # Returns
95/// CPU vendor as a string slice, or None if unsupported
96#[inline]
97pub fn get_vendor_string() -> Option<&'static str> {
98    let result = cpuid(0, 0);
99
100    // Extract vendor string from EBX, EDX, ECX registers
101    let vendor_bytes = [
102        (result.ebx & 0xFF) as u8,
103        ((result.ebx >> 8) & 0xFF) as u8,
104        ((result.ebx >> 16) & 0xFF) as u8,
105        ((result.ebx >> 24) & 0xFF) as u8,
106        (result.edx & 0xFF) as u8,
107        ((result.edx >> 8) & 0xFF) as u8,
108        ((result.edx >> 16) & 0xFF) as u8,
109        ((result.edx >> 24) & 0xFF) as u8,
110        (result.ecx & 0xFF) as u8,
111        ((result.ecx >> 8) & 0xFF) as u8,
112        ((result.ecx >> 16) & 0xFF) as u8,
113        ((result.ecx >> 24) & 0xFF) as u8,
114    ];
115
116    match &vendor_bytes[0..12] {
117        b"GenuineIntel" => Some("intel"),
118        b"AuthenticAMD" => Some("amd"),
119        b"CentaurHauls" => Some("centaur"),
120        b" Shanghai     " => Some("zhaoxin"),
121        b"HygonGenuine" => Some("hygon"),
122        _ => None,
123    }
124}
125
126/// Get CPU vendor as an enum
127///
128/// # Returns
129/// CPU vendor enum value
130#[inline]
131pub fn get_vendor() -> CpuVendor {
132    match get_vendor_string() {
133        Some("intel") => CpuVendor::Intel,
134        Some("amd") => CpuVendor::Amd,
135        _ => CpuVendor::Other,
136    }
137}
138
139/// Get CPU family, model, and stepping information
140///
141/// # Returns
142/// Tuple of (family, model, stepping) or None if unsupported
143#[inline]
144pub fn get_family_model_stepping() -> Option<(u8, u8, u8)> {
145    let result = cpuid(1, 0);
146
147    let family_id = ((result.eax >> 8) & 0xF) as u8;
148    let model = ((result.eax >> 4) & 0xF) as u8;
149    let stepping = (result.eax & 0xF) as u8;
150
151    // Handle extended family/model for newer CPUs
152    let extended_family = ((result.eax >> 20) & 0xFF) as u8;
153    let extended_model = ((result.eax >> 16) & 0xF) as u8;
154
155    let actual_family = if family_id == 0xF {
156        family_id + extended_family
157    } else {
158        family_id
159    };
160
161    let actual_model = if family_id == 0xF || family_id == 0x6 {
162        (extended_model << 4) | model
163    } else {
164        model
165    };
166
167    Some((actual_family, actual_model, stepping))
168}
169
170/// Check if AVX is supported
171///
172/// # Returns
173/// true if AVX is supported, false otherwise
174#[inline]
175pub fn has_avx() -> bool {
176    let result = cpuid(1, 0);
177    (result.ecx & (1 << 28)) != 0
178}
179
180/// Check if AVX2 is supported
181///
182/// # Returns
183/// true if AVX2 is supported, false otherwise
184#[inline]
185pub fn has_avx2() -> bool {
186    if !has_avx() {
187        return false;
188    }
189
190    let result = cpuid(7, 0);
191    (result.ebx & (1 << 5)) != 0
192}
193
194/// Get AVX-512 feature support information
195///
196/// # Returns
197/// Avx512Features struct with individual feature flags
198#[inline]
199pub fn get_avx512_features() -> Avx512Features {
200    let result = cpuid(7, 0);
201
202    Avx512Features {
203        avx512f: (result.ebx & (1 << 16)) != 0,
204        avx512bw: (result.ebx & (1 << 30)) != 0,
205        avx512vl: (result.ebx & (1 << 31)) != 0,
206        avx512dq: (result.ebx & (1 << 17)) != 0,
207    }
208}
209
210/// Check if all essential AVX-512 features are supported
211///
212/// # Returns
213/// true if AVX-512 F, BW, VL, and DQ are all supported, false otherwise
214#[inline]
215pub fn has_avx512_essential() -> bool {
216    let features = get_avx512_features();
217    features.avx512f && features.avx512bw && features.avx512vl && features.avx512dq
218}
219
220/// Check if the system is likely running in a virtualized environment
221///
222/// # Returns
223/// true if virtualization is detected, false otherwise
224#[inline]
225pub fn is_virtualized() -> bool {
226    // Check for hypervisor presence (CPUID leaf 0x40000000)
227    let result = cpuid(0x40000000, 0);
228    result.eax >= 0x40000000
229}
230
231/// Get maximum supported CPUID leaf
232///
233/// # Returns
234/// Maximum CPUID leaf supported by this CPU
235#[inline]
236pub fn get_max_leaf() -> u32 {
237    cpuid(0, 0).eax
238}
239
240/// Check if CPUID is supported (for very old CPUs)
241///
242/// # Returns
243/// true if CPUID is supported, false for very old CPUs
244#[inline]
245pub fn cpuid_supported() -> bool {
246    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
247    {
248        // On x86/x86_64, we assume CPUID is supported
249        // (true for all CPUs since Pentium and later)
250        true
251    }
252
253    #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
254    {
255        false
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_cpuid_basic() {
265        // Test that CPUID doesn't panic
266        let result = cpuid(0, 0);
267        assert!(result.eax > 0 || result.ebx > 0 || result.ecx > 0 || result.edx > 0);
268    }
269
270    #[test]
271    fn test_get_vendor() {
272        // Test that vendor detection works
273        let vendor = get_vendor();
274        // Vendor should be one of the known values
275        let _ = vendor;
276    }
277
278    #[test]
279    fn test_get_family_model_stepping() {
280        // Test that family/model/stepping detection works
281        let info = get_family_model_stepping();
282        // Should return Some on x86/x86_64, None on other architectures
283        #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
284        assert!(info.is_some());
285        #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
286        assert!(info.is_none());
287    }
288
289    #[test]
290    fn test_avx_detection() {
291        // Test AVX detection
292        let has_avx = has_avx();
293        let has_avx2 = has_avx2();
294
295        // AVX2 requires AVX
296        if has_avx2 {
297            assert!(has_avx, "AVX2 requires AVX support");
298        }
299    }
300
301    #[test]
302    fn test_avx512_features() {
303        // Test AVX-512 feature detection
304        let features = get_avx512_features();
305        let has_essential = has_avx512_essential();
306
307        // Essential features should all be true if has_essential is true
308        if has_essential {
309            assert!(
310                features.avx512f && features.avx512bw && features.avx512vl && features.avx512dq
311            );
312        }
313    }
314
315    #[test]
316    fn test_virtualization_detection() {
317        // Test virtualization detection
318        let _is_virtual = is_virtualized();
319        // This can be true or false depending on the environment
320    }
321
322    #[test]
323    fn test_cpuid_stability() {
324        // Test that CPUID results are stable across multiple calls
325        let result1 = cpuid(0, 0);
326        let result2 = cpuid(0, 0);
327        assert_eq!(result1, result2, "CPUID results should be stable");
328    }
329}