1use std::fmt;
5use std::sync::LazyLock;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum SimdSupport {
14 None,
15 Neon,
16 Sse,
17 Avx,
20 AvxFma,
23 Avx2,
28 Avx512,
29 Avx512FP16,
30 Lsx,
31 Lasx,
32}
33
34impl fmt::Display for SimdSupport {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 let name = match self {
39 Self::None => "none",
40 Self::Neon => "neon",
41 Self::Sse => "sse",
42 Self::Avx => "avx",
43 Self::AvxFma => "avx_fma",
44 Self::Avx2 => "avx2",
45 Self::Avx512 => "avx512",
46 Self::Avx512FP16 => "avx512_fp16",
47 Self::Lsx => "lsx",
48 Self::Lasx => "lasx",
49 };
50 f.write_str(name)
51 }
52}
53
54#[derive(Debug, Clone)]
61pub struct SimdInfo {
62 pub tier: SimdSupport,
64 pub target_arch: &'static str,
66 pub host_features: Vec<&'static str>,
70}
71
72pub fn simd_info() -> SimdInfo {
88 SimdInfo {
89 tier: *SIMD_SUPPORT,
90 target_arch: std::env::consts::ARCH,
91 host_features: detect_host_features(),
92 }
93}
94
95#[cfg(target_arch = "x86_64")]
96fn detect_host_features() -> Vec<&'static str> {
97 let mut features = Vec::with_capacity(17);
101 if is_x86_feature_detected!("sse2") {
102 features.push("sse2");
103 }
104 if is_x86_feature_detected!("sse3") {
105 features.push("sse3");
106 }
107 if is_x86_feature_detected!("ssse3") {
108 features.push("ssse3");
109 }
110 if is_x86_feature_detected!("sse4.1") {
111 features.push("sse4.1");
112 }
113 if is_x86_feature_detected!("sse4.2") {
114 features.push("sse4.2");
115 }
116 if is_x86_feature_detected!("popcnt") {
117 features.push("popcnt");
118 }
119 if is_x86_feature_detected!("avx") {
120 features.push("avx");
121 }
122 if is_x86_feature_detected!("avx2") {
123 features.push("avx2");
124 }
125 if is_x86_feature_detected!("fma") {
126 features.push("fma");
127 }
128 if is_x86_feature_detected!("f16c") {
129 features.push("f16c");
130 }
131 if is_x86_feature_detected!("bmi1") {
132 features.push("bmi1");
133 }
134 if is_x86_feature_detected!("bmi2") {
135 features.push("bmi2");
136 }
137 if is_x86_feature_detected!("avx512f") {
138 features.push("avx512f");
139 }
140 if is_x86_feature_detected!("avx512bw") {
141 features.push("avx512bw");
142 }
143 if is_x86_feature_detected!("avx512cd") {
144 features.push("avx512cd");
145 }
146 if is_x86_feature_detected!("avx512dq") {
147 features.push("avx512dq");
148 }
149 if is_x86_feature_detected!("avx512vl") {
150 features.push("avx512vl");
151 }
152 features
153}
154
155#[cfg(not(target_arch = "x86_64"))]
156fn detect_host_features() -> Vec<&'static str> {
157 Vec::new()
158}
159
160pub static SIMD_SUPPORT: LazyLock<SimdSupport> = LazyLock::new(|| {
162 #[cfg(all(target_arch = "aarch64", any(target_os = "ios", target_os = "tvos")))]
163 {
164 SimdSupport::Neon
166 }
167 #[cfg(all(
168 target_arch = "aarch64",
169 not(any(target_os = "ios", target_os = "tvos"))
170 ))]
171 {
172 if aarch64::has_neon_f16_support() {
173 SimdSupport::Neon
174 } else {
175 SimdSupport::None
176 }
177 }
178 #[cfg(target_arch = "x86_64")]
179 {
180 if x86::has_avx512() {
181 if x86::has_avx512_f16_support() {
182 SimdSupport::Avx512FP16
183 } else {
184 SimdSupport::Avx512
185 }
186 } else if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
187 SimdSupport::Avx2
193 } else if is_x86_feature_detected!("avx") && is_x86_feature_detected!("fma") {
194 SimdSupport::AvxFma
196 } else if is_x86_feature_detected!("avx") {
197 SimdSupport::Avx
199 } else {
200 SimdSupport::None
201 }
202 }
203 #[cfg(target_arch = "loongarch64")]
204 {
205 if loongarch64::has_lasx_support() {
206 SimdSupport::Lasx
207 } else if loongarch64::has_lsx_support() {
208 SimdSupport::Lsx
209 } else {
210 SimdSupport::None
211 }
212 }
213});
214
215#[cfg(target_arch = "x86_64")]
216mod x86 {
217 use core::arch::x86_64::__cpuid;
218
219 #[inline]
220 fn check_flag(x: usize, position: u32) -> bool {
221 x & (1 << position) != 0
222 }
223
224 pub fn has_avx512_f16_support() -> bool {
225 if !has_avx512() {
227 return false;
228 }
229
230 #[allow(unused_unsafe)]
235 let ext_cpuid_result = unsafe { __cpuid(7) };
236 check_flag(ext_cpuid_result.edx as usize, 23)
237 }
238
239 pub fn has_avx512() -> bool {
240 is_x86_feature_detected!("avx512f")
241 }
242}
243
244#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
249mod aarch64 {
250 pub fn has_neon_f16_support() -> bool {
251 true
253 }
254}
255
256#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
257mod aarch64 {
258 pub fn has_neon_f16_support() -> bool {
259 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
261 flags & libc::HWCAP_FPHP != 0
262 }
263}
264
265#[cfg(all(target_arch = "aarch64", target_os = "windows"))]
266mod aarch64 {
267 pub fn has_neon_f16_support() -> bool {
268 false
270 }
271}
272
273#[cfg(target_arch = "loongarch64")]
274mod loongarch64 {
275 pub fn has_lsx_support() -> bool {
276 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
278 flags & libc::HWCAP_LOONGARCH_LSX != 0
279 }
280 pub fn has_lasx_support() -> bool {
281 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
283 flags & libc::HWCAP_LOONGARCH_LASX != 0
284 }
285}
286
287#[cfg(all(target_arch = "aarch64", target_os = "android"))]
288mod aarch64 {
289 pub fn has_neon_f16_support() -> bool {
290 false
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use rstest::rstest;
298
299 #[test]
300 fn simd_info_exposes_tier() {
301 let info = simd_info();
302 assert_eq!(info.target_arch, std::env::consts::ARCH);
303 assert_eq!(info.tier, *SIMD_SUPPORT);
305 }
306
307 #[cfg(target_arch = "x86_64")]
308 #[test]
309 fn simd_info_features_include_baseline() {
310 let info = simd_info();
311 assert!(info.host_features.contains(&"sse2"));
314 }
315
316 #[cfg(not(target_arch = "x86_64"))]
317 #[test]
318 fn simd_info_features_empty_off_x86_64() {
319 let info = simd_info();
320 assert!(info.host_features.is_empty());
321 }
322
323 #[cfg(target_arch = "x86_64")]
329 #[test]
330 fn avx_fma_tiers_are_only_selected_when_fma_is_detected() {
331 if matches!(*SIMD_SUPPORT, SimdSupport::Avx2 | SimdSupport::AvxFma) {
332 assert!(
333 is_x86_feature_detected!("fma"),
334 "tier {} dispatches to avx,fma kernels but the host has no FMA",
335 *SIMD_SUPPORT
336 );
337 }
338 }
339
340 #[rstest]
341 #[case::none(SimdSupport::None, "none")]
342 #[case::neon(SimdSupport::Neon, "neon")]
343 #[case::sse(SimdSupport::Sse, "sse")]
344 #[case::avx(SimdSupport::Avx, "avx")]
345 #[case::avx_fma(SimdSupport::AvxFma, "avx_fma")]
346 #[case::avx2(SimdSupport::Avx2, "avx2")]
347 #[case::avx512(SimdSupport::Avx512, "avx512")]
348 #[case::avx512_fp16(SimdSupport::Avx512FP16, "avx512_fp16")]
349 #[case::lsx(SimdSupport::Lsx, "lsx")]
350 #[case::lasx(SimdSupport::Lasx, "lasx")]
351 fn simd_support_display_matches_lowercase_convention(
352 #[case] tier: SimdSupport,
353 #[case] expected: &str,
354 ) {
355 assert_eq!(tier.to_string(), expected);
356 }
357}