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,
23 AvxFma,
26 Avx2,
31 Avx512,
32 Avx512FP16,
33 Lsx,
34 Lasx,
35}
36
37impl fmt::Display for SimdSupport {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 let name = match self {
42 Self::None => "none",
43 Self::Neon => "neon",
44 Self::Sse => "sse",
45 Self::Avx => "avx",
46 Self::AvxFma => "avx_fma",
47 Self::Avx2 => "avx2",
48 Self::Avx512 => "avx512",
49 Self::Avx512FP16 => "avx512_fp16",
50 Self::Lsx => "lsx",
51 Self::Lasx => "lasx",
52 };
53 f.write_str(name)
54 }
55}
56
57#[derive(Debug, Clone)]
64pub struct SimdInfo {
65 pub tier: SimdSupport,
67 pub target_arch: &'static str,
69 pub host_features: Vec<&'static str>,
73}
74
75pub fn simd_info() -> SimdInfo {
91 SimdInfo {
92 tier: *SIMD_SUPPORT,
93 target_arch: std::env::consts::ARCH,
94 host_features: detect_host_features(),
95 }
96}
97
98#[cfg(target_arch = "x86_64")]
99fn detect_host_features() -> Vec<&'static str> {
100 let mut features = Vec::with_capacity(17);
104 if is_x86_feature_detected!("sse2") {
105 features.push("sse2");
106 }
107 if is_x86_feature_detected!("sse3") {
108 features.push("sse3");
109 }
110 if is_x86_feature_detected!("ssse3") {
111 features.push("ssse3");
112 }
113 if is_x86_feature_detected!("sse4.1") {
114 features.push("sse4.1");
115 }
116 if is_x86_feature_detected!("sse4.2") {
117 features.push("sse4.2");
118 }
119 if is_x86_feature_detected!("popcnt") {
120 features.push("popcnt");
121 }
122 if is_x86_feature_detected!("avx") {
123 features.push("avx");
124 }
125 if is_x86_feature_detected!("avx2") {
126 features.push("avx2");
127 }
128 if is_x86_feature_detected!("fma") {
129 features.push("fma");
130 }
131 if is_x86_feature_detected!("f16c") {
132 features.push("f16c");
133 }
134 if is_x86_feature_detected!("bmi1") {
135 features.push("bmi1");
136 }
137 if is_x86_feature_detected!("bmi2") {
138 features.push("bmi2");
139 }
140 if is_x86_feature_detected!("avx512f") {
141 features.push("avx512f");
142 }
143 if is_x86_feature_detected!("avx512bw") {
144 features.push("avx512bw");
145 }
146 if is_x86_feature_detected!("avx512cd") {
147 features.push("avx512cd");
148 }
149 if is_x86_feature_detected!("avx512dq") {
150 features.push("avx512dq");
151 }
152 if is_x86_feature_detected!("avx512vl") {
153 features.push("avx512vl");
154 }
155 features
156}
157
158#[cfg(not(target_arch = "x86_64"))]
159fn detect_host_features() -> Vec<&'static str> {
160 Vec::new()
161}
162
163pub static SIMD_SUPPORT: LazyLock<SimdSupport> = LazyLock::new(|| {
165 #[cfg(all(target_arch = "aarch64", any(target_os = "ios", target_os = "tvos")))]
166 {
167 SimdSupport::Neon
169 }
170 #[cfg(all(
171 target_arch = "aarch64",
172 not(any(target_os = "ios", target_os = "tvos"))
173 ))]
174 {
175 if aarch64::has_neon_f16_support() {
176 SimdSupport::Neon
177 } else {
178 SimdSupport::None
179 }
180 }
181 #[cfg(target_arch = "x86_64")]
182 {
183 if x86::has_avx512() {
184 if x86::has_avx512_f16_support() {
185 SimdSupport::Avx512FP16
186 } else {
187 SimdSupport::Avx512
188 }
189 } else if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
190 SimdSupport::Avx2
196 } else if is_x86_feature_detected!("avx") && is_x86_feature_detected!("fma") {
197 SimdSupport::AvxFma
199 } else if is_x86_feature_detected!("avx") {
200 SimdSupport::Avx
203 } else {
204 SimdSupport::None
205 }
206 }
207 #[cfg(target_arch = "loongarch64")]
208 {
209 if loongarch64::has_lasx_support() {
210 SimdSupport::Lasx
211 } else if loongarch64::has_lsx_support() {
212 SimdSupport::Lsx
213 } else {
214 SimdSupport::None
215 }
216 }
217 #[cfg(not(any(
218 target_arch = "aarch64",
219 target_arch = "x86_64",
220 target_arch = "loongarch64"
221 )))]
222 {
223 SimdSupport::None
224 }
225});
226
227#[cfg(target_arch = "x86_64")]
228mod x86 {
229 use core::arch::x86_64::__cpuid;
230
231 #[inline]
232 fn check_flag(x: usize, position: u32) -> bool {
233 x & (1 << position) != 0
234 }
235
236 pub fn has_avx512_f16_support() -> bool {
237 if !has_avx512() {
239 return false;
240 }
241
242 #[allow(unused_unsafe)]
247 let ext_cpuid_result = unsafe { __cpuid(7) };
248 check_flag(ext_cpuid_result.edx as usize, 23)
249 }
250
251 pub fn has_avx512() -> bool {
252 is_x86_feature_detected!("avx512f")
253 }
254}
255
256#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
261mod aarch64 {
262 pub fn has_neon_f16_support() -> bool {
263 true
265 }
266}
267
268#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
269mod aarch64 {
270 pub fn has_neon_f16_support() -> bool {
271 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
273 flags & libc::HWCAP_FPHP != 0
274 }
275}
276
277#[cfg(all(target_arch = "aarch64", target_os = "windows"))]
278mod aarch64 {
279 pub fn has_neon_f16_support() -> bool {
280 false
282 }
283}
284
285#[cfg(target_arch = "loongarch64")]
286mod loongarch64 {
287 pub fn has_lsx_support() -> bool {
288 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
290 flags & libc::HWCAP_LOONGARCH_LSX != 0
291 }
292 pub fn has_lasx_support() -> bool {
293 let flags = unsafe { libc::getauxval(libc::AT_HWCAP) };
295 flags & libc::HWCAP_LOONGARCH_LASX != 0
296 }
297}
298
299#[cfg(all(target_arch = "aarch64", target_os = "android"))]
300mod aarch64 {
301 pub fn has_neon_f16_support() -> bool {
302 false
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use rstest::rstest;
310
311 #[test]
312 fn simd_info_exposes_tier() {
313 let info = simd_info();
314 assert_eq!(info.target_arch, std::env::consts::ARCH);
315 assert_eq!(info.tier, *SIMD_SUPPORT);
317 }
318
319 #[cfg(target_arch = "x86_64")]
320 #[test]
321 fn simd_info_features_include_baseline() {
322 let info = simd_info();
323 assert!(info.host_features.contains(&"sse2"));
326 }
327
328 #[cfg(not(target_arch = "x86_64"))]
329 #[test]
330 fn simd_info_features_empty_off_x86_64() {
331 let info = simd_info();
332 assert!(info.host_features.is_empty());
333 }
334
335 #[cfg(target_arch = "x86_64")]
341 #[test]
342 fn avx_fma_tiers_are_only_selected_when_fma_is_detected() {
343 if matches!(*SIMD_SUPPORT, SimdSupport::Avx2 | SimdSupport::AvxFma) {
344 assert!(
345 is_x86_feature_detected!("fma"),
346 "tier {} dispatches to avx,fma kernels but the host has no FMA",
347 *SIMD_SUPPORT
348 );
349 }
350 }
351
352 #[rstest]
353 #[case::none(SimdSupport::None, "none")]
354 #[case::neon(SimdSupport::Neon, "neon")]
355 #[case::sse(SimdSupport::Sse, "sse")]
356 #[case::avx(SimdSupport::Avx, "avx")]
357 #[case::avx_fma(SimdSupport::AvxFma, "avx_fma")]
358 #[case::avx2(SimdSupport::Avx2, "avx2")]
359 #[case::avx512(SimdSupport::Avx512, "avx512")]
360 #[case::avx512_fp16(SimdSupport::Avx512FP16, "avx512_fp16")]
361 #[case::lsx(SimdSupport::Lsx, "lsx")]
362 #[case::lasx(SimdSupport::Lasx, "lasx")]
363 fn simd_support_display_matches_lowercase_convention(
364 #[case] tier: SimdSupport,
365 #[case] expected: &str,
366 ) {
367 assert_eq!(tier.to_string(), expected);
368 }
369}