Skip to main content

frozen_core/
isa.rs

1//! A minimal and `no_std` best available ISA extension detection for x86-64 and AArch64 systems
2
3#[cfg(target_arch = "x86_64")]
4use core::arch::x86_64;
5
6/// Available ISA extensions on `x86_64` systems
7#[repr(u8)]
8#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9#[cfg(target_arch = "x86_64")]
10pub enum ISAExtension {
11    /// SSE2 (baseline on x86_64)
12    SSE2,
13
14    /// SSSE3
15    SSSE3,
16
17    /// SSE4.2
18    SSE4_2,
19
20    /// AVX2
21    AVX2,
22}
23
24/// Available ISA extensions on `aarch64` systems
25#[repr(u8)]
26#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27#[cfg(target_arch = "aarch64")]
28pub enum ISAExtension {
29    /// NEON (baseline on AArch64)
30    NEON,
31}
32
33/// Detects the highest available vector extension supported on `aarch64` systems
34///
35/// ## Example
36///
37/// ```
38/// use frozen_core::isa::{detect_best_isa_extension, ISAExtension};
39///
40/// let isa = detect_best_isa_extension();
41/// assert_eq!(isa, ISAExtension::NEON);
42/// ```
43#[inline(always)]
44#[cfg(target_arch = "aarch64")]
45pub fn detect_best_isa_extension() -> ISAExtension {
46    // NOTE:
47    //
48    // NEON is part of the mandatory AArch64 baseline ISA
49    //
50    // The Armv8-A architecture reference manual and AAPCS64 ABI specify Advanced SIMD (NEON) as a required baseline
51    // feature, so every conforming AArch64 CPU is guaranteed to implement it
52    //
53    // Therefore, we can safely use NEON as the default backend without runtime feature detection
54    //
55    // Ref -> https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
56    ISAExtension::NEON
57}
58
59/// Detects the highest available vector extension supported on `x86_64` systems
60///
61/// ## Example
62///
63/// ```
64/// use frozen_core::isa::{detect_best_isa_extension, ISAExtension};
65///
66/// let isa = detect_best_isa_extension();
67/// assert!(isa >= ISAExtension::SSE2);
68/// ```
69#[inline(always)]
70#[cfg(target_arch = "x86_64")]
71pub fn detect_best_isa_extension() -> ISAExtension {
72    // NOTE:
73    //
74    // `__cpuid` was marked unsafe prior to Rust 1.87
75    //
76    // An `unsafe` block is required to maintain compatibility with our MSRV (1.86.0)
77    #[allow(unused_unsafe)]
78    let cpuid1 = unsafe { x86_64::__cpuid(1) };
79
80    let has_ssse3 = (cpuid1.ecx & (1 << 9)) != 0;
81    let has_sse4_2 = (cpuid1.ecx & (1 << 0x14)) != 0;
82
83    let osxsave = (cpuid1.ecx & (1 << 0x1B)) != 0;
84    if osxsave {
85        let xcr0 = unsafe { x86_64::_xgetbv(0) };
86        let xmm_ymm_enabled = (xcr0 & 0b110) == 0b110;
87
88        if xmm_ymm_enabled {
89            // NOTE:
90            //
91            // `__cpuid_count` was marked unsafe prior to Rust 1.87
92            //
93            // An `unsafe` block is required to maintain compatibility with our MSRV (1.86.0)
94            #[allow(unused_unsafe)]
95            let cpuid7 = unsafe { x86_64::__cpuid_count(7, 0) };
96
97            if (cpuid7.ebx & (1 << 5)) != 0 {
98                return ISAExtension::AVX2;
99            }
100        }
101    }
102
103    if has_sse4_2 {
104        return ISAExtension::SSE4_2;
105    }
106
107    if has_ssse3 {
108        return ISAExtension::SSSE3;
109    }
110
111    // NOTE:
112    //
113    // SSE2 is part of the mandatory x86_64 baseline ISA
114    //
115    // The x86_64 System V ABI specifies SSE2 as a required baseline feature, so every conforming x86_64 CPU is
116    // guaranteed to implement it
117    //
118    // Therefore, we can safely use SSE2 as the default backend without runtime feature detection
119    //
120    // Ref -> https://gitlab.com/x86-psABIs/x86-64-ABI/-/blob/master/x86-64-ABI/low-level-sys-info.tex
121    ISAExtension::SSE2
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    mod enum_invariants {
129        use super::*;
130        use core::mem::{align_of, size_of};
131
132        #[test]
133        fn ok_enum_representation_and_layout() {
134            assert_eq!(size_of::<ISAExtension>(), 1);
135            assert_eq!(align_of::<ISAExtension>(), 1);
136        }
137
138        #[test]
139        #[cfg(target_arch = "x86_64")]
140        fn ok_extension_monotonic_ordering() {
141            assert!(ISAExtension::SSE2 < ISAExtension::SSSE3);
142            assert!(ISAExtension::SSSE3 < ISAExtension::SSE4_2);
143            assert!(ISAExtension::SSE4_2 < ISAExtension::AVX2);
144        }
145
146        #[test]
147        fn ok_hashing_and_equality() {
148            use std::collections::hash_map::DefaultHasher;
149            use std::hash::{Hash, Hasher};
150
151            fn hash_val<T: Hash>(val: &T) -> u64 {
152                let mut hasher = DefaultHasher::new();
153                val.hash(&mut hasher);
154                hasher.finish()
155            }
156
157            let a = detect_best_isa_extension();
158            let b = detect_best_isa_extension();
159            assert_eq!(a, b);
160            assert_eq!(hash_val(&a), hash_val(&b));
161        }
162    }
163
164    mod detection {
165        use super::*;
166
167        #[test]
168        fn ok_detection_is_deterministic_and_idempotent() {
169            let first = detect_best_isa_extension();
170            for _ in 0..0x64 {
171                assert_eq!(detect_best_isa_extension(), first);
172            }
173        }
174
175        #[test]
176        fn ok_detection_across_threads() {
177            use std::thread;
178
179            let expected = detect_best_isa_extension();
180            let handles: Vec<_> = (0..8)
181                .map(|_| {
182                    thread::spawn(move || {
183                        for _ in 0..0x32 {
184                            assert_eq!(detect_best_isa_extension(), expected);
185                        }
186                    })
187                })
188                .collect();
189
190            for handle in handles {
191                handle.join().unwrap();
192            }
193        }
194
195        #[test]
196        #[cfg(target_arch = "x86_64")]
197        fn ok_meets_x86_64_baseline() {
198            let detected = detect_best_isa_extension();
199            assert!(detected >= ISAExtension::SSE2);
200        }
201
202        #[test]
203        #[cfg(target_arch = "aarch64")]
204        fn ok_meets_aarch64_baseline() {
205            let detected = detect_best_isa_extension();
206            assert_eq!(detected, ISAExtension::NEON);
207        }
208
209        #[test]
210        #[cfg(target_arch = "x86_64")]
211        fn ok_cross_validate_with_std_feature_detection() {
212            let detected = detect_best_isa_extension();
213
214            assert!(std::is_x86_feature_detected!("sse2"));
215
216            match detected {
217                ISAExtension::AVX2 => {
218                    assert!(std::is_x86_feature_detected!("avx2"));
219                    assert!(std::is_x86_feature_detected!("sse4.2"));
220                    assert!(std::is_x86_feature_detected!("ssse3"));
221                }
222                ISAExtension::SSE4_2 => {
223                    assert!(std::is_x86_feature_detected!("sse4.2"));
224                    assert!(std::is_x86_feature_detected!("ssse3"));
225                }
226                ISAExtension::SSSE3 => {
227                    assert!(std::is_x86_feature_detected!("ssse3"));
228                }
229                ISAExtension::SSE2 => {}
230            }
231        }
232
233        #[test]
234        #[cfg(target_arch = "aarch64")]
235        fn ok_cross_validate_with_std_feature_detection() {
236            assert!(std::arch::is_aarch64_feature_detected!("neon"));
237        }
238    }
239
240    #[allow(unsafe_op_in_unsafe_fn)]
241    mod execution_smoke {
242        use super::*;
243
244        #[test]
245        #[cfg(target_arch = "x86_64")]
246        fn ok_execute_detected_isa_instructions() {
247            let detected = detect_best_isa_extension();
248
249            #[target_feature(enable = "sse2")]
250            unsafe fn exec_sse2() {
251                let a = core::arch::x86_64::_mm_set1_epi32(0x2A);
252                let b = core::arch::x86_64::_mm_set1_epi32(0x0A);
253                let c = core::arch::x86_64::_mm_add_epi32(a, b);
254
255                let mut out = [0i32; 4];
256                unsafe {
257                    core::arch::x86_64::_mm_storeu_si128(out.as_mut_ptr() as *mut _, c);
258                }
259                assert_eq!(out, [0x34; 4]);
260            }
261
262            #[target_feature(enable = "ssse3")]
263            unsafe fn exec_ssse3() {
264                let val = core::arch::x86_64::_mm_set1_epi32(0x01020304);
265                let mask = core::arch::x86_64::_mm_setzero_si128();
266                let res = core::arch::x86_64::_mm_shuffle_epi8(val, mask);
267
268                let mut out = [0u8; 0x10];
269                unsafe {
270                    core::arch::x86_64::_mm_storeu_si128(out.as_mut_ptr() as *mut _, res);
271                }
272                assert_eq!(out[0], 0x04);
273            }
274
275            #[target_feature(enable = "sse4.2")]
276            unsafe fn exec_sse4_2() {
277                let crc = core::arch::x86_64::_mm_crc32_u64(!0, 0x123456789ABCDEF0);
278                assert_ne!(crc, 0);
279            }
280
281            #[target_feature(enable = "avx2")]
282            unsafe fn exec_avx2() {
283                let a = core::arch::x86_64::_mm256_set1_epi32(0x64);
284                let b = core::arch::x86_64::_mm256_set1_epi32(0xC8);
285                let c = core::arch::x86_64::_mm256_add_epi32(a, b);
286
287                let mut out = [0i32; 8];
288                unsafe {
289                    core::arch::x86_64::_mm256_storeu_si256(out.as_mut_ptr() as *mut _, c);
290                }
291                assert_eq!(out, [0x12C; 8]);
292            }
293
294            unsafe {
295                if detected >= ISAExtension::SSE2 {
296                    exec_sse2();
297                }
298
299                if detected >= ISAExtension::SSSE3 {
300                    exec_ssse3();
301                }
302
303                if detected >= ISAExtension::SSE4_2 {
304                    exec_sse4_2();
305                }
306
307                if detected >= ISAExtension::AVX2 {
308                    exec_avx2();
309                }
310            }
311        }
312
313        #[test]
314        #[cfg(target_arch = "aarch64")]
315        fn ok_execute_detected_isa_instructions() {
316            let detected = detect_best_isa_extension();
317            assert_eq!(detected, ISAExtension::NEON);
318
319            #[target_feature(enable = "neon")]
320            unsafe fn exec_neon() {
321                let v = core::arch::aarch64::vdupq_n_u8(0x42);
322                let mut out = [0u8; 0x10];
323
324                unsafe {
325                    core::arch::aarch64::vst1q_u8(out.as_mut_ptr(), v);
326                }
327                assert_eq!(out, [0x42; 0x10]);
328            }
329
330            unsafe {
331                exec_neon();
332            }
333        }
334    }
335}