Skip to main content

fearless_simd/
macros.rs

1// Copyright 2024 the Fearless_SIMD Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Macros publicly exported
5
6/// Access the applicable [`Simd`] for a given `level`, and perform an operation using it.
7///
8/// This macro is the root of how any explicitly written SIMD functions in this crate are
9/// called from a non-SIMD context.
10///
11/// The first parameter to the macro is the [`Level`].
12/// You should prefer to construct a [`Level`] once and pass it around, rather than
13/// frequently calling [`Level::new()`].
14/// On x86, the first call to `Level::new` detects which target features are available and caches
15/// the result. Passing the resulting value around avoids even the cached atomic load.
16///
17/// The code of the operation will be repeated literally several times in the output, so you should prefer
18/// to keep this code small (as it will be type-checked, etc. for each supported SIMD level on your target).
19/// In most cases, it should be a single call to a function which is generic over `Simd` implementations,
20/// as seen in [the examples](#examples).
21/// For clarity, it will only be executed once per execution of `dispatch`.
22///
23/// To guarantee target-feature-specific code generation, functions called within the operation
24/// should either establish their own context with [`Simd::vectorize`], use the
25/// [`#[simd]`](https://docs.rs/fearless_simd_macros/latest/fearless_simd_macros/attr.simd.html)
26/// attribute, or be `#[inline(always)]` so that they are incorporated into this operation.
27///
28/// Note that as an implementation detail of this macro, the operation will be executed inside a closure.
29/// This is what enables the target features to be enabled for the code inside the operation.
30/// A consequence of this is that early `return` and `?` will not work as expected.
31/// Note that in cases where you use `dispatch` to call a single function (which we expect to be the
32/// majority of cases), you can use `?` on the return value of dispatch instead.
33/// To emulate early return, you can use [`ControlFlow`](core::ops::ControlFlow) instead.
34///
35/// # Example
36///
37/// ```rust
38/// use fearless_simd::{Level, Simd, dispatch};
39///
40/// #[inline(always)]
41/// fn sigmoid<S: Simd>(simd: S, x: &[f32], out: &mut [f32]) { /* ... */ }
42///
43/// let level = Level::new();
44///
45/// dispatch!(level, simd => sigmoid(simd, &[/*...*/], &mut [/*...*/]));
46/// ```
47///
48/// [`Level`]: crate::Level
49/// [`Level::new()`]: crate::Level::new
50/// [`Simd`]: crate::Simd
51/// [`Simd::vectorize`]: crate::Simd::vectorize
52#[macro_export]
53macro_rules! dispatch {
54    ($level:expr, $simd:pat => $op:expr) => {{
55        $crate::__fearless_simd_dispatch_inner!($level, $simd => $op)
56    }};
57    (@impl $level:expr, $simd:pat => $op:expr; $forced_fallback_arm:literal) => {{
58        match $crate::Level::__dispatch_target($level) {
59            #[cfg(target_arch = "aarch64")]
60            $crate::Level::Neon(neon) => {
61                $crate::__fearless_simd_dispatch_with_token!(neon, $simd => $op)
62            }
63            #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
64            $crate::Level::WasmSimd128(wasm) => {
65                $crate::__fearless_simd_dispatch_with_token!(wasm, $simd => $op)
66            }
67            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68            $crate::Level::Sse2(sse2) => {
69                $crate::__fearless_simd_dispatch_dispatch_sse2!(sse2, $simd => $op)
70            }
71            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
72            $crate::Level::Sse4_2(sse4_2) => {
73                $crate::__fearless_simd_dispatch_dispatch_sse4_2!(sse4_2, $simd => $op)
74            }
75            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
76            $crate::Level::Avx2(avx2) => {
77                $crate::__fearless_simd_dispatch_dispatch_avx2!(avx2, $simd => $op)
78            }
79            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
80            $crate::Level::Avx512(avx512) => {
81                $crate::__fearless_simd_dispatch_dispatch_avx512!(avx512, $simd => $op)
82            }
83            // Keep this predicate in sync with `Level::Fallback` and the fallback module. The
84            // literal carries fearless_simd's feature selection into expansion in a downstream
85            // crate, where `cfg(feature = ...)` would be incorrect.
86            #[cfg(any(
87                $forced_fallback_arm,
88                not(any(
89                    all(target_arch = "aarch64", target_feature = "neon"),
90                    all(
91                        any(target_arch = "x86", target_arch = "x86_64"),
92                        target_feature = "sse2",
93                        target_feature = "fxsr"
94                    ),
95                    all(target_arch = "wasm32", target_feature = "simd128")
96                ))
97            ))]
98            $crate::Level::Fallback(fallback) => {
99                $crate::__fearless_simd_dispatch_with_token!(fallback, $simd => $op)
100            }
101            _ => unreachable!(),
102        }
103    }};
104}
105
106// This macro turns whether `force_support_fallback` is enabled into a boolean literal in
107// `dispatch!`. Exported macro bodies are expanded in the downstream crate, so evaluating the
108// feature cfg directly in `dispatch!` would inspect the wrong crate's features.
109
110/// Implementation detail of [`crate::dispatch`]; this is not public API.
111#[macro_export]
112#[doc(hidden)]
113#[cfg(feature = "force_support_fallback")]
114macro_rules! __fearless_simd_dispatch_inner {
115    ($level:expr, $simd:pat => $op:expr) => {
116        $crate::dispatch!(@impl $level, $simd => $op; true)
117    };
118}
119
120/// Implementation detail of [`crate::dispatch`]; this is not public API.
121#[macro_export]
122#[doc(hidden)]
123#[cfg(not(feature = "force_support_fallback"))]
124macro_rules! __fearless_simd_dispatch_inner {
125    ($level:expr, $simd:pat => $op:expr) => {
126        $crate::dispatch!(@impl $level, $simd => $op; false)
127    };
128}
129
130// The dispatch helpers are split into cfg-selected macro definitions
131// because exported macro bodies are expanded in the downstream crate,
132// so selecting the helper definitions here preserves `fearless_simd`'s
133// dispatch configuration. Helpers for pruned levels expand to `unreachable!()`
134// without mentioning `$op`, so the pruned SIMD body is not typechecked or
135// compiled.
136
137/// Implementation detail of [`crate::dispatch`]; this is not public API.
138#[macro_export]
139#[doc(hidden)]
140macro_rules! __fearless_simd_dispatch_with_token {
141    ($token:expr, $simd:pat => $op:expr) => {{
142        /// Convert the `Simd` value into an `impl Simd`, which enforces that
143        /// it is correctly handled.
144        // TODO: Just make into a `pub` function in fearless_simd itself?
145        #[inline(always)]
146        fn launder<S: $crate::Simd>(x: S) -> impl $crate::Simd {
147            x
148        }
149
150        // $token can be an arbitrary expression,
151        // so bind the result of evaluating it to a variable before use
152        // so that it's evaluated only once
153        let __fearless_simd_token = $token;
154        let $simd = launder(__fearless_simd_token);
155        $crate::Simd::vectorize(
156            __fearless_simd_token,
157            #[inline(always)]
158            || $op,
159        )
160    }};
161}
162
163/// Implementation detail of [`crate::dispatch`]; this is not public API.
164#[macro_export]
165#[doc(hidden)]
166macro_rules! __fearless_simd_dispatch_pruned {
167    ($proof:expr) => {{
168        let __fearless_simd_proof = $proof;
169        let _ = __fearless_simd_proof;
170        unreachable!("this SIMD level was pruned from dispatch")
171    }};
172}
173
174/// Implementation detail of [`crate::dispatch`]; this is not public API.
175#[macro_export]
176#[doc(hidden)]
177#[cfg(not(disable_dispatch_avx512))]
178macro_rules! __fearless_simd_dispatch_dispatch_avx512 {
179    ($avx512:expr, $simd:pat => $op:expr) => {
180        $crate::__fearless_simd_dispatch_with_token!($avx512, $simd => $op)
181    };
182}
183
184/// Implementation detail of [`crate::dispatch`]; this is not public API.
185#[macro_export]
186#[doc(hidden)]
187#[cfg(disable_dispatch_avx512)]
188macro_rules! __fearless_simd_dispatch_dispatch_avx512 {
189    ($avx512:expr, $simd:pat => $op:expr) => {{ $crate::__fearless_simd_dispatch_pruned!($avx512) }};
190}
191
192/// Implementation detail of [`crate::dispatch`]; this is not public API.
193#[macro_export]
194#[doc(hidden)]
195#[cfg(all(
196    not(disable_dispatch_avx2),
197    any(
198        disable_dispatch_avx512,
199        not(all(
200            target_feature = "adx",
201            target_feature = "aes",
202            target_feature = "avx512bitalg",
203            target_feature = "avx512bw",
204            target_feature = "avx512cd",
205            target_feature = "avx512dq",
206            target_feature = "avx512f",
207            target_feature = "avx512ifma",
208            target_feature = "avx512vbmi",
209            target_feature = "avx512vbmi2",
210            target_feature = "avx512vl",
211            target_feature = "avx512vnni",
212            target_feature = "avx512vpopcntdq",
213            target_feature = "bmi1",
214            target_feature = "bmi2",
215            target_feature = "cmpxchg16b",
216            target_feature = "fma",
217            target_feature = "fxsr",
218            target_feature = "gfni",
219            target_feature = "lzcnt",
220            target_feature = "movbe",
221            target_feature = "pclmulqdq",
222            target_feature = "popcnt",
223            target_feature = "rdrand",
224            target_feature = "rdseed",
225            target_feature = "sha",
226            target_feature = "vaes",
227            target_feature = "vpclmulqdq",
228            target_feature = "xsave",
229            target_feature = "xsavec",
230            target_feature = "xsaveopt",
231            target_feature = "xsaves",
232        ))
233    )
234))]
235macro_rules! __fearless_simd_dispatch_dispatch_avx2 {
236    ($avx2:expr, $simd:pat => $op:expr) => {
237        $crate::__fearless_simd_dispatch_with_token!($avx2, $simd => $op)
238    };
239}
240
241/// Implementation detail of [`crate::dispatch`]; this is not public API.
242#[macro_export]
243#[doc(hidden)]
244#[cfg(any(
245    disable_dispatch_avx2,
246    all(
247        not(disable_dispatch_avx512),
248        target_feature = "adx",
249        target_feature = "aes",
250        target_feature = "avx512bitalg",
251        target_feature = "avx512bw",
252        target_feature = "avx512cd",
253        target_feature = "avx512dq",
254        target_feature = "avx512f",
255        target_feature = "avx512ifma",
256        target_feature = "avx512vbmi",
257        target_feature = "avx512vbmi2",
258        target_feature = "avx512vl",
259        target_feature = "avx512vnni",
260        target_feature = "avx512vpopcntdq",
261        target_feature = "bmi1",
262        target_feature = "bmi2",
263        target_feature = "cmpxchg16b",
264        target_feature = "fma",
265        target_feature = "fxsr",
266        target_feature = "gfni",
267        target_feature = "lzcnt",
268        target_feature = "movbe",
269        target_feature = "pclmulqdq",
270        target_feature = "popcnt",
271        target_feature = "rdrand",
272        target_feature = "rdseed",
273        target_feature = "sha",
274        target_feature = "vaes",
275        target_feature = "vpclmulqdq",
276        target_feature = "xsave",
277        target_feature = "xsavec",
278        target_feature = "xsaveopt",
279        target_feature = "xsaves",
280    ),
281))]
282macro_rules! __fearless_simd_dispatch_dispatch_avx2 {
283    ($avx2:expr, $simd:pat => $op:expr) => {{ $crate::__fearless_simd_dispatch_pruned!($avx2) }};
284}
285
286/// Implementation detail of [`crate::dispatch`]; this is not public API.
287#[macro_export]
288#[doc(hidden)]
289#[cfg(all(
290    not(disable_dispatch_sse4_2),
291    any(
292        disable_dispatch_avx2,
293        not(all(
294            target_feature = "avx2",
295            target_feature = "bmi1",
296            target_feature = "bmi2",
297            target_feature = "cmpxchg16b",
298            target_feature = "f16c",
299            target_feature = "fma",
300            target_feature = "fxsr",
301            target_feature = "lzcnt",
302            target_feature = "movbe",
303            target_feature = "popcnt",
304            target_feature = "xsave"
305        ))
306    )
307))]
308macro_rules! __fearless_simd_dispatch_dispatch_sse4_2 {
309    ($sse4_2:expr, $simd:pat => $op:expr) => {
310        $crate::__fearless_simd_dispatch_with_token!($sse4_2, $simd => $op)
311    };
312}
313
314/// Implementation detail of [`crate::dispatch`]; this is not public API.
315#[macro_export]
316#[doc(hidden)]
317#[cfg(any(
318    disable_dispatch_sse4_2,
319    all(
320        not(disable_dispatch_avx2),
321        target_feature = "avx2",
322        target_feature = "bmi1",
323        target_feature = "bmi2",
324        target_feature = "cmpxchg16b",
325        target_feature = "f16c",
326        target_feature = "fma",
327        target_feature = "fxsr",
328        target_feature = "lzcnt",
329        target_feature = "movbe",
330        target_feature = "popcnt",
331        target_feature = "xsave"
332    ),
333))]
334macro_rules! __fearless_simd_dispatch_dispatch_sse4_2 {
335    ($sse4_2:expr, $simd:pat => $op:expr) => {{ $crate::__fearless_simd_dispatch_pruned!($sse4_2) }};
336}
337
338/// Implementation detail of [`crate::dispatch`]; this is not public API.
339#[macro_export]
340#[doc(hidden)]
341#[cfg(all(
342    any(
343        not(disable_dispatch_sse2),
344        all(target_feature = "sse2", target_feature = "fxsr")
345    ),
346    any(
347        disable_dispatch_sse4_2,
348        not(all(
349            target_feature = "fxsr",
350            target_feature = "sse4.2",
351            target_feature = "cmpxchg16b",
352            target_feature = "popcnt",
353        )),
354    )
355))]
356macro_rules! __fearless_simd_dispatch_dispatch_sse2 {
357    ($sse2:expr, $simd:pat => $op:expr) => {
358        $crate::__fearless_simd_dispatch_with_token!($sse2, $simd => $op)
359    };
360}
361
362/// Implementation detail of [`crate::dispatch`]; this is not public API.
363#[macro_export]
364#[doc(hidden)]
365#[cfg(any(
366    all(
367        disable_dispatch_sse2,
368        not(all(target_feature = "sse2", target_feature = "fxsr"))
369    ),
370    all(
371        not(disable_dispatch_sse4_2),
372        target_feature = "fxsr",
373        target_feature = "sse4.2",
374        target_feature = "cmpxchg16b",
375        target_feature = "popcnt",
376    ),
377))]
378macro_rules! __fearless_simd_dispatch_dispatch_sse2 {
379    ($sse2:expr, $simd:pat => $op:expr) => {{ $crate::__fearless_simd_dispatch_pruned!($sse2) }};
380}
381
382#[cfg(test)]
383// This expect also validates that we haven't missed any levels!
384#[expect(
385    unreachable_patterns,
386    reason = "Level is non_exhaustive, but you must be exhaustive within the same crate."
387)]
388mod tests {
389    use crate::{Level, Simd};
390
391    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
392    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
393    enum X86DispatchBackend {
394        Fallback,
395        Sse2,
396        Sse4_2,
397        Avx2,
398        Avx512,
399    }
400
401    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
402    fn x86_dispatch_backend<S: Simd>(_: S) -> X86DispatchBackend {
403        use core::any::TypeId;
404
405        #[cfg(any(
406            not(all(target_feature = "sse2", target_feature = "fxsr")),
407            feature = "force_support_fallback"
408        ))]
409        if TypeId::of::<S>() == TypeId::of::<crate::Fallback>() {
410            return X86DispatchBackend::Fallback;
411        }
412
413        if TypeId::of::<S>() == TypeId::of::<crate::Sse2>() {
414            X86DispatchBackend::Sse2
415        } else if TypeId::of::<S>() == TypeId::of::<crate::Sse4_2>() {
416            X86DispatchBackend::Sse4_2
417        } else if TypeId::of::<S>() == TypeId::of::<crate::Avx2>() {
418            X86DispatchBackend::Avx2
419        } else if TypeId::of::<S>() == TypeId::of::<crate::Avx512>() {
420            X86DispatchBackend::Avx512
421        } else {
422            unreachable!("unexpected x86 dispatch backend")
423        }
424    }
425
426    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
427    fn expected_x86_dispatch_backend(level: Level) -> X86DispatchBackend {
428        if cfg!(not(disable_dispatch_avx512)) && level.as_avx512().is_some() {
429            X86DispatchBackend::Avx512
430        } else if cfg!(not(disable_dispatch_avx2)) && level.as_avx2().is_some() {
431            X86DispatchBackend::Avx2
432        } else if cfg!(not(disable_dispatch_sse4_2)) && level.as_sse4_2().is_some() {
433            X86DispatchBackend::Sse4_2
434        } else if cfg!(any(
435            not(disable_dispatch_sse2),
436            all(target_feature = "sse2", target_feature = "fxsr")
437        )) && level.as_sse2().is_some()
438        {
439            X86DispatchBackend::Sse2
440        } else {
441            X86DispatchBackend::Fallback
442        }
443    }
444
445    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
446    #[allow(dead_code, reason = "Compile test")]
447    fn x86_level_variants_remain_available() {
448        let _ = Level::Sse2(unsafe { crate::Sse2::assume_supported() });
449        let _ = Level::Sse4_2(unsafe { crate::Sse4_2::assume_supported() });
450        let _ = Level::Avx2(unsafe { crate::Avx2::assume_supported() });
451        let _ = Level::Avx512(unsafe { crate::Avx512::assume_supported() });
452    }
453
454    #[allow(dead_code, reason = "Compile test")]
455    fn dispatch_generic() {
456        fn generic<S: Simd, T>(_: S, x: T) -> T {
457            x
458        }
459        dispatch!(Level::new(), simd => generic::<_, ()>(simd, ()));
460    }
461
462    #[allow(dead_code, reason = "Compile test")]
463    fn dispatch_value() {
464        fn make_fn<S: Simd>() -> impl FnOnce(S) {
465            |_| ()
466        }
467        dispatch!(Level::new(), simd => (make_fn())(simd));
468    }
469
470    #[test]
471    fn dispatch_output() {
472        assert_eq!(42, dispatch!(Level::new(), _simd => 42));
473    }
474
475    #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
476    #[test]
477    fn dispatch_respects_x86_multiversion_features() {
478        let level = Level::new();
479        let actual = dispatch!(level, simd => x86_dispatch_backend(simd));
480
481        assert_eq!(actual, expected_x86_dispatch_backend(level));
482    }
483
484    #[cfg(all(
485        any(target_arch = "x86", target_arch = "x86_64"),
486        target_feature = "sse2",
487        not(target_feature = "fxsr")
488    ))]
489    #[test]
490    fn x86_baseline_does_not_use_sse2_without_fxsr() {
491        assert!(Level::baseline().is_fallback());
492
493        let actual = dispatch!(Level::Fallback(crate::Fallback::new()), simd =>
494            x86_dispatch_backend(simd)
495        );
496
497        assert_eq!(actual, X86DispatchBackend::Fallback);
498    }
499
500    #[cfg(all(
501        feature = "std",
502        any(target_arch = "x86", target_arch = "x86_64"),
503        any(
504            disable_dispatch_sse2,
505            disable_dispatch_sse4_2,
506            disable_dispatch_avx2,
507            disable_dispatch_avx512
508        )
509    ))]
510    #[test]
511    fn disabled_x86_multiversioning_does_not_filter_token_access() {
512        let level = Level::new();
513
514        // If dispatch disabling accidentally removed token access, these method calls stop compiling.
515        #[cfg(disable_dispatch_sse2)]
516        let _ = level.as_sse2().is_some();
517
518        #[cfg(disable_dispatch_sse4_2)]
519        let _ = level.as_sse4_2().is_some();
520
521        #[cfg(disable_dispatch_avx2)]
522        let _ = level.as_avx2().is_some();
523
524        #[cfg(disable_dispatch_avx512)]
525        let _ = level.as_avx512().is_some();
526    }
527
528    #[cfg(all(
529        feature = "std",
530        any(target_arch = "x86", target_arch = "x86_64"),
531        disable_dispatch_sse2,
532        target_feature = "sse2",
533        target_feature = "fxsr"
534    ))]
535    #[test]
536    fn ambient_sse2_is_the_terminal_backend_when_dispatch_is_disabled() {
537        let actual = dispatch!(Level::new(), simd => x86_dispatch_backend(simd));
538
539        assert_ne!(actual, X86DispatchBackend::Fallback);
540    }
541
542    /// Mostly useful with `RUSTFLAGS='-C target-cpu=x86-64-v3'` and higher, doesn't do much otherwise
543    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
544    #[test]
545    fn high_baseline_dispatches_lower_tokens() {
546        if let Some(sse2) = Level::baseline().as_sse2() {
547            let actual = dispatch!(sse2.level(), simd => x86_dispatch_backend(simd));
548
549            assert_eq!(actual, expected_x86_dispatch_backend(Level::baseline()));
550        }
551
552        if let Some(sse4_2) = Level::baseline().as_sse4_2() {
553            let actual = dispatch!(sse4_2.level(), simd => x86_dispatch_backend(simd));
554
555            assert_eq!(actual, expected_x86_dispatch_backend(Level::baseline()));
556        }
557    }
558
559    #[cfg(all(
560        not(feature = "force_support_fallback"),
561        any(target_arch = "x86", target_arch = "x86_64"),
562        not(all(target_feature = "sse2", target_feature = "fxsr"))
563    ))]
564    #[test]
565    fn fallback_dispatches_as_baseline() {
566        let actual = dispatch!(Level::Fallback(crate::Fallback::new()), simd =>
567            x86_dispatch_backend(simd)
568        );
569
570        assert_eq!(actual, expected_x86_dispatch_backend(Level::baseline()));
571    }
572
573    mod no_import_simd {
574        /// We should be able to use [`dispatch`] in a scope which doesn't import anything.
575        #[test]
576        fn dispatch_with_no_imports() {
577            let res = dispatch!(crate::Level::new(), _ => 1 + 2);
578            assert_eq!(res, 3);
579        }
580    }
581}