utils/
multiversion.rs

1//! Implementation of [`multiversion!`](crate::multiversion!) macro.
2
3use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::str::FromStr;
6use std::sync::OnceLock;
7
8/// Macro to generate multiversioned functions.
9///
10/// This macro generates multiple versions of the provided functions, each optimized for different
11/// hardware, as well as dynamic dispatch functions which select the best version to use at runtime
12/// based on feature support.
13///
14/// This allows using instruction set extensions such as
15/// [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2)
16/// if the binary is run on a compatible processor, without enabling it at compile time, which would
17/// create a binary which can't be run on processors without AVX2 support.
18///
19/// The first three rules are supported:
20///
21/// 1. The first rule matches a dynamic dispatch function, optionally followed by extra private
22///    helper functions. Dynamic dispatch is controlled at runtime by supplying a path to a
23///    [`LazyLock`](std::sync::LazyLock) containing a [`Version`] value in the `dyn_dispatch`
24///    attribute.
25///
26/// 2. The second rule matches library functions without a dynamic dispatch function.
27///
28/// 3. The third rule takes a name of a library function generated using the second rule and
29///    expands to a [`LazyLock`](std::sync::LazyLock) evaluating to the fastest version, unless a
30///    global override is set using [`Version::set_override`]. The return value is intended to be
31///    stored in a `static` item for use in a `dyn_dispatch` attribute.
32///
33/// The first two rules start with a use statement which can be used for wildcard imports of
34/// multiversioned libraries. Each path will be expanded to include the version's name - e.g. the
35/// `avx2` version will expand `use {utils::simd::*};` into `use {utils::simd::avx2::*};`. Other
36/// imports should be outside the macro invocation.
37///
38/// Supported function syntax is limited as this is a declarative macro. Dynamic dispatch functions
39/// are the most limited, supporting only basic arguments and optionally a return type.
40///
41/// Versions enabling additional features require the `unsafe` crate feature to be enabled, as
42/// features are enabled using the
43/// [`target_feature`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute)
44/// attribute, which requires callers to use `unsafe` blocks. A safe fallback version will always
45/// be generated.
46///
47/// See [`crate::md5`] as an example. [`multiversion_test!`](crate::multiversion_test!) can be used
48/// for testing multiversioned libraries.
49#[macro_export]
50macro_rules! multiversion {
51    // One dynamic dispatch function, optionally with extra helper functions.
52    (
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        /// [`multiversion!`] dynamic dispatch implementations.
61        mod $name {
62            #[allow(clippy::allow_attributes, unused_imports)]
63            use {super::*, $crate::multiversion}; // multiversion import needed for rustdoc links
64
65            $crate::multiversion!{
66                use {$($($path::)+*),*};
67                $(#[$m])* pub fn $name($($arg_name: $arg_type),*) $(-> $ret)? $body
68                $($tail)*
69            }
70        }
71
72        /// [`multiversion!`] dynamic dispatch function.
73        #[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    // Library-only definition, without a dynamic dispatch function.
104    (
105        use {$($($path:ident::)+*),*};
106
107        $($tail:tt)+
108    ) => {
109        /// [`multiversion!`] scalar implementation.
110        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        /// [`multiversion!`] array128 implementation.
125        pub mod array128 {
126            #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
127            use {super::*, $($($path::)+array128::*),*};
128
129            $($tail)*
130        }
131
132        /// [`multiversion!`] array256 implementation.
133        pub mod array256 {
134            #[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
135            use {super::*, $($($path::)+array256::*),*};
136
137            $($tail)*
138        }
139
140        /// [`multiversion!`] array4096 implementation.
141        #[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        /// [`multiversion!`] avx2 implementation.
152        #[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        /// [`multiversion!`] avx2x2 implementation.
161        #[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        /// [`multiversion!`] avx2x4 implementation.
170        #[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        /// [`multiversion!`] avx2x8 implementation.
179        #[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        /// [`multiversion!`] neon implementation.
188        #[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        /// [`multiversion!`] neonx2 implementation.
197        #[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        /// [`multiversion!`] neonx4 implementation.
206        #[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        /// [`multiversion!`] neonx8 implementation.
215        #[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    // Microbenchmark for dynamic dispatch
225    (fastest($name:ident())) => {
226        ::std::sync::LazyLock::new(#[cfg_attr(target_family = "wasm", expect(unreachable_code))] || {
227            use $crate::multiversion::Version::*;
228
229            // Instant::now() isn't implemented in WebAssembly, so hardcode implementations
230            #[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                // .inspect(|x| { dbg!(x); })
269                .min_by_key(|x| x.0)
270                .unwrap()
271                .1
272        })
273    };
274
275    // Helper rules to add target_feature to helper functions
276    (@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    // Replace #[inline(always)] which is incompatible with target_feature with normal #[inline]
284    (@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    // Helper rule to pop the first item out of the tt sequence
303    (@one_item $t:meta $item:item $($tail:tt)*) => {
304        $item
305        $crate::multiversion!{@enable $t $($tail)*}
306    };
307}
308
309/// Helper for testing and benchmarking [`multiversion!`] library functions.
310///
311/// The first rule is for testing and creates individual
312/// [`#[test]`](https://doc.rust-lang.org/reference/attributes/testing.html) functions for each
313/// implementation.
314///
315/// The second rule is more general, duplicating the same expression for each implementation and
316/// is useful for benchmarking.
317///
318/// `#[target_feature(...)]` isn't applied to the test code as the feature-specific code should
319/// be elsewhere, inside a [`multiversion!`] macro.
320#[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        /// Versions generated by [`multiversion!`](crate::multiversion!) on this platform.
595        #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
596        pub enum Version {
597            #[default] // First item is the default
598            $(
599                $(#[$m])*
600                $(#[doc = concat!("Requires `", stringify!($supported), "`.")])?
601                $name,
602            )+
603        }
604
605        impl Version {
606            /// Check if this version is supported at runtime.
607            #[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            /// Implementation is case-insensitive.
619            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        /// Runtime generated list of supported versions.
629        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    /// Get the global version override, if any.
664    ///
665    /// This function will return [`Some`] containing the version provided to
666    /// [`Version::set_override`] if it was previously called, or [`None`] otherwise.
667    ///
668    /// All dynamic dispatch implementations should respect this value.
669    pub fn get_override() -> Option<Version> {
670        *OVERRIDE.get_or_init(|| None)
671    }
672
673    /// Set the global version override.
674    ///
675    /// # Panics
676    ///
677    /// This function will panic if any of the following conditions are met:
678    ///
679    /// - The provided version is not [supported](Self::supported).
680    ///
681    /// - This function is called more than once.
682    ///
683    /// - This function is called after calling [`Version::get_override`] (or any multiversioned
684    ///   dynamic dispatch function that respects the global override).
685    pub fn set_override(version: Version) {
686        assert!(version.supported(), "{version:?} is not supported!");
687
688        if OVERRIDE.set(Some(version)).is_err() {
689            // Value returned in Err() is the value passed to set, not the existing value
690            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/// Error type returned when trying to convert an invalid string to a [`Version`].
700#[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 {}