1use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::str::FromStr;
6use std::sync::OnceLock;
7
8#[macro_export]
50macro_rules! multiversion {
51 (
53 use {$($($path:ident::)+*),*};
54
55 #[dyn_dispatch = $dispatch:path]
56 $(#[$m:meta])* $v:vis fn $name:ident($($arg_name:ident: $arg_type:ty),*$(,)?) $(-> $ret:ty)? $body:block
57
58 $($tail:tt)*
59 ) => {
60 mod $name {
62 #[allow(clippy::allow_attributes, unused_imports)]
63 use {super::*, $crate::multiversion}; $crate::multiversion!{
66 use {$($($path::)+*),*};
67 $(#[$m])* pub fn $name($($arg_name: $arg_type),*) $(-> $ret)? $body
68 $($tail)*
69 }
70 }
71
72 #[inline]
74 $v fn $name($($arg_name: $arg_type),*) $(-> $ret)? {
75 use $crate::multiversion::Version::*;
76
77 match *$dispatch {
78 Scalar => $name::scalar::$name($($arg_name),*),
79 Array128 => $name::array128::$name($($arg_name),*),
80 Array256 => $name::array256::$name($($arg_name),*),
81 #[cfg(feature = "all-simd")]
82 Array4096 => $name::array4096::$name($($arg_name),*),
83 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
84 AVX2 => unsafe { $name::avx2::$name($($arg_name),*) },
85 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
86 AVX2x2 => unsafe { $name::avx2x2::$name($($arg_name),*) },
87 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
88 AVX2x4 => unsafe { $name::avx2x4::$name($($arg_name),*) },
89 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
90 AVX2x8 => unsafe { $name::avx2x8::$name($($arg_name),*) },
91 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
92 Neon => unsafe { $name::neon::$name($($arg_name),*) },
93 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
94 Neonx2 => unsafe { $name::neonx2::$name($($arg_name),*) },
95 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
96 Neonx4 => unsafe { $name::neonx4::$name($($arg_name),*) },
97 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
98 Neonx8 => unsafe { $name::neonx8::$name($($arg_name),*) },
99 }
100 }
101 };
102
103 (
105 use {$($($path:ident::)+*),*};
106
107 $($tail:tt)+
108 ) => {
109 pub mod scalar {
111 #![allow(
112 clippy::reversed_empty_ranges,
113 clippy::range_plus_one,
114 clippy::modulo_one,
115 clippy::trivially_copy_pass_by_ref
116 )]
117
118 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
119 use {super::*, $($($path::)+scalar::*),*};
120
121 $($tail)*
122 }
123
124 pub mod array128 {
126 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
127 use {super::*, $($($path::)+array128::*),*};
128
129 $($tail)*
130 }
131
132 pub mod array256 {
134 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
135 use {super::*, $($($path::)+array256::*),*};
136
137 $($tail)*
138 }
139
140 #[cfg(feature="all-simd")]
142 pub mod array4096 {
143 #![allow(clippy::large_types_passed_by_value)]
144
145 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
146 use {super::*, $($($path::)+array4096::*),*};
147
148 $($tail)*
149 }
150
151 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
153 pub mod avx2 {
154 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
155 use {super::*, $($($path::)+avx2::*),*};
156
157 $crate::multiversion!{@enable target_feature(enable = "avx2") $($tail)*}
158 }
159
160 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
162 pub mod avx2x2 {
163 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
164 use {super::*, $($($path::)+avx2x2::*),*};
165
166 $crate::multiversion!{@enable target_feature(enable = "avx2") $($tail)*}
167 }
168
169 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
171 pub mod avx2x4 {
172 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
173 use {super::*, $($($path::)+avx2x4::*),*};
174
175 $crate::multiversion!{@enable target_feature(enable = "avx2") $($tail)*}
176 }
177
178 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
180 pub mod avx2x8 {
181 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
182 use {super::*, $($($path::)+avx2x8::*),*};
183
184 $crate::multiversion!{@enable target_feature(enable = "avx2") $($tail)*}
185 }
186
187 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
189 pub mod neon {
190 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
191 use {super::*, $($($path::)+neon::*),*};
192
193 $crate::multiversion!{@enable target_feature(enable = "neon") $($tail)*}
194 }
195
196 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
198 pub mod neonx2 {
199 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
200 use {super::*, $($($path::)+neonx2::*),*};
201
202 $crate::multiversion!{@enable target_feature(enable = "neon") $($tail)*}
203 }
204
205 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
207 pub mod neonx4 {
208 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
209 use {super::*, $($($path::)+neonx4::*),*};
210
211 $crate::multiversion!{@enable target_feature(enable = "neon") $($tail)*}
212 }
213
214 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
216 pub mod neonx8 {
217 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
218 use {super::*, $($($path::)+neonx8::*),*};
219
220 $crate::multiversion!{@enable target_feature(enable = "neon") $($tail)*}
221 }
222 };
223
224 (fastest($name:ident())) => {
226 ::std::sync::LazyLock::new(#[cfg_attr(target_family = "wasm", expect(unreachable_code))] || {
227 use $crate::multiversion::Version::*;
228
229 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
231 return Array256;
232 #[cfg(all(target_family = "wasm"))]
233 return Array128;
234
235 if let Some(version) = $crate::multiversion::Version::get_override() {
236 return version;
237 }
238
239 $crate::multiversion::VERSIONS
240 .iter()
241 .map(|&x| {
242 let start = ::std::time::Instant::now();
243 ::std::hint::black_box(match x {
244 Scalar => scalar::$name(),
245 Array128 => array128::$name(),
246 Array256 => array256::$name(),
247 #[cfg(feature = "all-simd")]
248 Array4096 => array4096::$name(),
249 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
250 AVX2 => unsafe { avx2::$name() },
251 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
252 AVX2x2 => unsafe { avx2x2::$name() },
253 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
254 AVX2x4 => unsafe { avx2x4::$name() },
255 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
256 AVX2x8 => unsafe { avx2x8::$name() },
257 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
258 Neon => unsafe { neon::$name() },
259 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
260 Neonx2 => unsafe { neonx2::$name() },
261 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
262 Neonx4 => unsafe { neonx4::$name() },
263 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
264 Neonx8 => unsafe { neonx8::$name() },
265 });
266 (start.elapsed(), x)
267 })
268 .min_by_key(|x| x.0)
270 .unwrap()
271 .1
272 })
273 };
274
275 (@enable $t:meta) => {};
277 (@enable $t:meta
278 $(#[$m:meta])* $v:vis const $n:ident: $ty:ty = $e:expr; $($tail:tt)*
279 ) => {
280 $(#[$m])* $v const $n: $ty = $e;
281 $crate::multiversion!{@enable $t $($tail)*}
282 };
283 (@enable $t:meta
285 #[inline(always)] $($tail:tt)*
286 ) => {
287 $crate::multiversion!{@enable $t
288 #[inline] $($tail)*
289 }
290 };
291 (@enable $t:meta
292 $(#[$m:meta])* $v:vis fn $($tail:tt)*
293 ) => {
294 $crate::multiversion!{@one_item $t
295 $(#[$m])*
296 #[$t]
297 #[allow(clippy::allow_attributes, clippy::missing_safety_doc)]
298 $v fn $($tail)*
299 }
300 };
301
302 (@one_item $t:meta $item:item $($tail:tt)*) => {
304 $item
305 $crate::multiversion!{@enable $t $($tail)*}
306 };
307}
308
309#[macro_export]
321macro_rules! multiversion_test {
322 (
323 use {$($($path:ident::)+*),*};
324
325 #[test]
326 $(#[$m:meta])* $v:vis fn multiversion() $body:block
327 ) => {
328 #[test]
329 $(#[$m])*
330 fn scalar() {
331 #![allow(
332 clippy::reversed_empty_ranges,
333 clippy::range_plus_one,
334 clippy::modulo_one,
335 clippy::trivially_copy_pass_by_ref
336 )]
337
338 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
339 use {$($($path::)+scalar::*),*};
340
341 $body
342 }
343
344 #[test]
345 $(#[$m])*
346 fn array128() {
347 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
348 use {$($($path::)+array128::*),*};
349
350 $body
351 }
352
353 #[test]
354 $(#[$m])*
355 fn array256() {
356 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
357 use {$($($path::)+array256::*),*};
358
359 $body
360 }
361
362 #[test]
363 #[cfg(feature = "all-simd")]
364 $(#[$m])*
365 fn array4096() {
366 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
367 use {$($($path::)+array4096::*),*};
368
369 $body
370 }
371
372 #[test]
373 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
374 $(#[$m])*
375 fn avx2() {
376 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
377 use {$($($path::)+avx2::*),*};
378
379 if !$crate::multiversion::Version::AVX2.supported() {
380 use std::io::{stdout, Write};
381 let _ = writeln!(&mut stdout(), "warning: skipping test in {}::avx2 due to missing avx2 support", module_path!());
382 return;
383 }
384
385 unsafe { $body }
386 }
387
388 #[test]
389 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
390 $(#[$m])*
391 fn avx2x2() {
392 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
393 use {$($($path::)+avx2x2::*),*};
394
395 if !$crate::multiversion::Version::AVX2x2.supported() {
396 use std::io::{stdout, Write};
397 let _ = writeln!(&mut stdout(), "warning: skipping test in {}::avx2x2 due to missing avx2 support", module_path!());
398 return;
399 }
400
401 unsafe { $body }
402 }
403
404 #[test]
405 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
406 $(#[$m])*
407 fn avx2x4() {
408 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
409 use {$($($path::)+avx2x4::*),*};
410
411 if !$crate::multiversion::Version::AVX2x4.supported() {
412 use std::io::{stdout, Write};
413 let _ = writeln!(&mut stdout(), "warning: skipping test in {}::avx2x4 due to missing avx2 support", module_path!());
414 return;
415 }
416
417 unsafe { $body }
418 }
419
420 #[test]
421 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
422 $(#[$m])*
423 fn avx2x8() {
424 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
425 use {$($($path::)+avx2x8::*),*};
426
427 if !$crate::multiversion::Version::AVX2x8.supported() {
428 use std::io::{stdout, Write};
429 let _ = writeln!(&mut stdout(), "warning: skipping test in {}::avx2x8 due to missing avx2 support", module_path!());
430 return;
431 }
432
433 unsafe { $body }
434 }
435
436 #[test]
437 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
438 $(#[$m])*
439 fn neon() {
440 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
441 use {$($($path::)+neon::*),*};
442
443 unsafe { $body }
444 }
445
446 #[test]
447 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
448 $(#[$m])*
449 fn neonx2() {
450 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
451 use {$($($path::)+neonx2::*),*};
452
453 unsafe { $body }
454 }
455
456 #[test]
457 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
458 $(#[$m])*
459 fn neonx4() {
460 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
461 use {$($($path::)+neonx4::*),*};
462
463 unsafe { $body }
464 }
465
466 #[test]
467 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
468 $(#[$m])*
469 fn neonx8() {
470 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
471 use {$($($path::)+neonx8::*),*};
472
473 unsafe { $body }
474 }
475 };
476
477 (
478 use {$($($path:ident::)+*),*};
479
480 { $($tail:tt)+ }
481 ) => {
482 #[allow(
483 clippy::reversed_empty_ranges,
484 clippy::range_plus_one,
485 clippy::modulo_one,
486 clippy::trivially_copy_pass_by_ref
487 )]
488 {
489 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
490 use {$($($path::)+scalar::*),*};
491
492 $crate::multiversion_test!(@expr { $($tail)+ });
493 }
494
495 {
496 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
497 use {$($($path::)+array128::*),*};
498
499 $crate::multiversion_test!(@expr { $($tail)+ });
500 }
501
502 {
503 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
504 use {$($($path::)+array256::*),*};
505
506 $crate::multiversion_test!(@expr { $($tail)+ });
507 }
508
509 #[cfg(feature = "all-simd")]
510 #[allow(clippy::large_types_passed_by_value)]
511 {
512 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
513 use {$($($path::)+array4096::*),*};
514
515 $crate::multiversion_test!(@expr { $($tail)+ });
516 }
517
518 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
519 if $crate::multiversion::Version::AVX2.supported() {
520 unsafe {
521 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
522 use {$($($path::)+avx2::*),*};
523
524 $crate::multiversion_test!(@expr { $($tail)+ });
525 }
526
527 #[cfg(feature = "all-simd")]
528 unsafe {
529 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
530 use {$($($path::)+avx2x2::*),*};
531
532 $crate::multiversion_test!(@expr { $($tail)+ });
533 }
534
535 #[cfg(feature = "all-simd")]
536 unsafe {
537 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
538 use {$($($path::)+avx2x4::*),*};
539
540 $crate::multiversion_test!(@expr { $($tail)+ });
541 }
542
543 #[cfg(feature = "all-simd")]
544 unsafe {
545 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
546 use {$($($path::)+avx2x8::*),*};
547
548 $crate::multiversion_test!(@expr { $($tail)+ });
549 }
550 }
551
552 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
553 {
554 unsafe {
555 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
556 use {$($($path::)+neon::*),*};
557
558 $crate::multiversion_test!(@expr { $($tail)+ });
559 }
560
561 #[cfg(feature = "all-simd")]
562 unsafe {
563 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
564 use {$($($path::)+neonx2::*),*};
565
566 $crate::multiversion_test!(@expr { $($tail)+ });
567 }
568
569 #[cfg(feature = "all-simd")]
570 unsafe {
571 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
572 use {$($($path::)+neonx4::*),*};
573
574 $crate::multiversion_test!(@expr { $($tail)+ });
575 }
576
577 #[cfg(feature = "all-simd")]
578 unsafe {
579 #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
580 use {$($($path::)+neonx8::*),*};
581
582 $crate::multiversion_test!(@expr { $($tail)+ });
583 }
584 }
585 };
586 (@expr $e:expr) => { $e }
587}
588
589macro_rules! versions_impl {
590 ($($
591 (#[$m:meta])*
592 $name:ident $(if $supported:expr)?,
593 )+) => {
594 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
596 pub enum Version {
597 #[default] $(
599 $(#[$m])*
600 $(#[doc = concat!("Requires `", stringify!($supported), "`.")])?
601 $name,
602 )+
603 }
604
605 impl Version {
606 #[must_use]
608 pub fn supported(self) -> bool {
609 match self {
610 $($(#[$m])* Version::$name => true $(&& $supported)?,)+
611 }
612 }
613 }
614
615 impl FromStr for Version {
616 type Err = UnknownVersion;
617
618 fn from_str(x: &str) -> Result<Self, Self::Err> {
620 $(
621 $(#[$m])*
622 if stringify!($name).eq_ignore_ascii_case(x) { return Ok(Version::$name); }
623 )+
624 Err(UnknownVersion(x.to_string()))
625 }
626 }
627
628 pub static VERSIONS: std::sync::LazyLock<Vec<Version>> = std::sync::LazyLock::new(|| {
630 let mut vec = vec![$($(#[$m])* Version::$name,)+];
631 vec.retain(|i| i.supported());
632 vec
633 });
634 };
635}
636versions_impl! {
637 Scalar,
638 Array128,
639 Array256,
640 #[cfg(feature = "all-simd")]
641 Array4096,
642 #[cfg(all(feature = "unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
643 AVX2 if std::arch::is_x86_feature_detected!("avx2"),
644 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
645 AVX2x2 if std::arch::is_x86_feature_detected!("avx2"),
646 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
647 AVX2x4 if std::arch::is_x86_feature_detected!("avx2"),
648 #[cfg(all(feature = "unsafe", feature = "all-simd", any(target_arch = "x86", target_arch = "x86_64")))]
649 AVX2x8 if std::arch::is_x86_feature_detected!("avx2"),
650 #[cfg(all(feature = "unsafe", target_arch = "aarch64"))]
651 Neon,
652 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
653 Neonx2,
654 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
655 Neonx4,
656 #[cfg(all(feature = "unsafe", feature = "all-simd", target_arch = "aarch64"))]
657 Neonx8,
658}
659
660static OVERRIDE: OnceLock<Option<Version>> = OnceLock::new();
661
662impl Version {
663 pub fn get_override() -> Option<Version> {
670 *OVERRIDE.get_or_init(|| None)
671 }
672
673 pub fn set_override(version: Version) {
686 assert!(version.supported(), "{version:?} is not supported!");
687
688 if OVERRIDE.set(Some(version)).is_err() {
689 if Self::get_override().is_none() {
691 panic!("Version::set_override must be called before get_override");
692 } else {
693 panic!("Version::set_override called more than once");
694 }
695 }
696 }
697}
698
699#[derive(Debug)]
701pub struct UnknownVersion(String);
702
703impl Display for UnknownVersion {
704 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
705 write!(f, "unknown function version: {:#}", self.0)
706 }
707}
708
709impl Error for UnknownVersion {}