Skip to main content

fearless_simd/
kernel_macros.rs

1// Copyright 2025 the Fearless_SIMD Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4/// Creates a context where you can safely call intrinsics
5/// available at the SIMD level named by the function's first argument.
6///
7/// This is useful if the portable abstractions are not enough, and you need to
8/// use platform-specific intrinsics for parts of the computation.
9///
10/// The first argument must be a SIMD token written as `token: Neon`,
11/// `token: WasmSimd128`, `token: Sse2`, `token: Sse4_2`, `token: Avx2`, or `token: Avx512`.
12///
13/// For levels with runtime-detected target features, the macro runs your body
14/// inside an inner function annotated with the appropriate `#[target_feature]`
15/// attributes. That makes platform-specific intrinsics from `core::arch` or
16/// `std::arch` safe to call in the body, as long as they do not have safety
17/// requirements beyond those target features.
18///
19/// ## Example
20///
21/// ```rust
22/// # #[allow(unused_imports)]
23/// use fearless_simd::{i32x8, prelude::*};
24/// #[cfg(target_arch = "x86")]
25/// use std::arch::x86::{__m256i, _mm256_add_epi32};
26/// #[cfg(target_arch = "x86_64")]
27/// use std::arch::x86_64::{__m256i, _mm256_add_epi32};
28///
29/// fearless_simd::kernel!(
30///     #[inline]
31///     fn add_i32x8(avx2: Avx2, a: __m256i, b: __m256i) -> __m256i {
32///         _mm256_add_epi32(a, b)
33///     }
34/// );
35///
36/// # fn main() {
37/// #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
38/// if let Some(avx2) = fearless_simd::Level::new().as_avx2() {
39///     let a: i32x8<_> = [1, 2, 3, 4, 5, 6, 7, 8].simd_into(avx2);
40///     let b: i32x8<_> = [10, 20, 30, 40, 50, 60, 70, 80].simd_into(avx2);
41///     let sum: i32x8<_> = add_i32x8(avx2, a.into(), b.into()).simd_into(avx2);
42///
43///     assert_eq!(<[i32; 8]>::from(sum), [11, 22, 33, 44, 55, 66, 77, 88]);
44/// }
45/// # }
46/// ```
47///
48/// See the [sRGB example] for an end-to-end use of kernel macros.
49///
50/// [sRGB example]: https://github.com/linebender/fearless_simd/blob/main/fearless_simd/examples/srgb.rs
51///
52/// ## Limitations
53///
54/// The macro only accepts a single plain, safe, non-generic function item with simple named parameters.
55/// However, the body of the function can be as complex as you like.
56///
57/// The SIMD token type must be written as a bare supported name:
58/// literally `Neon`, `WasmSimd128`, `Sse2`, `Sse4_2`, `Avx2`, or `Avx512`. No paths or aliases.
59///
60/// For soundness, this macro only accepts safe functions.
61///
62/// ```compile_fail
63/// fearless_simd::kernel!(
64///     unsafe fn should_not_compile(avx2: Avx2) {}
65/// );
66/// ```
67#[macro_export]
68macro_rules! kernel {
69    (
70        $(#[$meta:meta])*
71        $vis:vis fn $name:ident(
72            $token:ident : $token_ty:ident $(, $arg:ident : $arg_ty:ty)* $(,)?
73        ) $(-> $ret:ty)? {
74            $($kernel_body:tt)*
75        }
76    ) => {
77        $crate::__fearless_simd_kernel_impl! {
78            @level $token_ty;
79            $(#[$meta])*
80            $vis fn $name(
81                $token $(, $arg: $arg_ty)*
82            ) $(-> $ret)? {
83                $($kernel_body)*
84            }
85        }
86    };
87
88    (
89        $(#[$meta:meta])*
90        $vis:vis fn $name:ident(
91            $token:ident : $token_ty:ty $(, $arg:ident : $arg_ty:ty)* $(,)?
92        ) $(-> $ret:ty)? {
93            $($kernel_body:tt)*
94        }
95    ) => {
96        compile_error!(concat!(
97            "fearless_simd::kernel! expects its SIMD token argument type to be written as ",
98            "one of `Neon`, `WasmSimd128`, `Sse2`, `Sse4_2`, `Avx2`, or `Avx512`; got `",
99            stringify!($token_ty),
100            "`",
101        ));
102    };
103}
104
105#[doc(hidden)]
106#[macro_export]
107macro_rules! __fearless_simd_kernel_cfg {
108    (Neon, $item:item) => {
109        #[cfg(target_arch = "aarch64")]
110        $item
111    };
112
113    (WasmSimd128, $item:item) => {
114        #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
115        $item
116    };
117
118    (Sse2, $item:item) => {
119        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
120        $item
121    };
122
123    (Sse4_2, $item:item) => {
124        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
125        $item
126    };
127
128    (Avx2, $item:item) => {
129        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
130        $item
131    };
132
133    (Avx512, $item:item) => {
134        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
135        $item
136    };
137
138    ($level:ident, $item:item) => {
139        compile_error!(concat!(
140            "fearless_simd::kernel! expects its SIMD token argument type to be written as ",
141            "one of `Neon`, `WasmSimd128`, `Sse2`, `Sse4_2`, `Avx2`, or `Avx512`; got `",
142            stringify!($level),
143            "`",
144        ));
145    };
146}
147
148// Internal interface for `kernel!` and `__fearless_simd_dispatch_entry!`, which backs
149// the argument-taking `#[simd]` expansion in `fearless_simd_macros`.
150#[doc(hidden)]
151#[macro_export]
152macro_rules! __fearless_simd_kernel_target_fn {
153    (Neon, $item:item) => {
154        #[target_feature(enable = "neon")]
155        $item
156    };
157
158    (WasmSimd128, $item:item) => {
159        $item
160    };
161
162    (Sse2, $item:item) => {
163        #[target_feature(enable = "fxsr,sse,sse2")]
164        $item
165    };
166
167    (Sse4_2, $item:item) => {
168        #[target_feature(enable = "fxsr,sse4.2,cmpxchg16b,popcnt")]
169        $item
170    };
171
172    (Avx2, $item:item) => {
173        #[target_feature(
174            enable = "fxsr,avx2,bmi1,bmi2,cmpxchg16b,f16c,fma,lzcnt,movbe,popcnt,xsave"
175        )]
176        $item
177    };
178
179    (Avx512, $item:item) => {
180        #[target_feature(
181            enable = "fxsr,adx,aes,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,bmi1,bmi2,cmpxchg16b,fma,gfni,lzcnt,movbe,pclmulqdq,popcnt,rdrand,rdseed,sha,vaes,vpclmulqdq,xsave,xsavec,xsaveopt,xsaves"
182        )]
183        $item
184    };
185}
186
187// Internal interface for `#[simd]` in `fearless_simd_macros`. Each pair names a
188// fresh generic argument type and its corresponding function parameter. Keep
189// this protocol compatible with that consumer, including the `.call()` method.
190//
191// This exported macro must be safe to invoke directly. It only accepts names,
192// never caller-supplied token types, bounds, attributes, or code for an unsafe
193// context. All safety-critical library paths use `$crate`: even if the proc
194// macro's `fearless_simd` path resolves to a re-export, dispatch requires genuine
195// proof tokens from the library that defines the target-feature helper. The
196// user's closure is passed to the resulting safe method outside this macro.
197#[doc(hidden)]
198#[macro_export]
199macro_rules! __fearless_simd_dispatch {
200    ($($argument_type:ident => $argument:ident),* $(,)?) => {{
201        // Scope generated items to the callee expression, away from the user
202        // body. Item names do not have local-variable hygiene. Use a method on
203        // a zero-sized receiver: returning a generic function item from this
204        // block loses the expected FnOnce signature during closure input
205        // inference. Method lookup retains it without adding a machine argument.
206        // Suppress all Clippy groups on generated helpers only. The caller's
207        // function and body closure remain outside these lint scopes.
208        #[allow(
209            clippy::all,
210            clippy::pedantic,
211            clippy::nursery,
212            clippy::restriction,
213            clippy::cargo,
214            reason = "SIMD dispatch helpers are generated implementation details"
215        )]
216        struct __FearlessDispatch;
217        #[allow(
218            clippy::all,
219            clippy::pedantic,
220            clippy::nursery,
221            clippy::restriction,
222            clippy::cargo,
223            reason = "SIMD dispatch helpers are generated implementation details"
224        )]
225        impl __FearlessDispatch {
226            // Do not force inlining: a large body may be shared by several
227            // callers. Even an out-of-line dispatcher keeps separate arguments.
228            #[inline]
229            fn call<S: $crate::Simd, $($argument_type,)* F, R>(
230                self, simd: S, $($argument: $argument_type,)* f: F,
231            ) -> R
232            where F: ::core::ops::FnOnce($($argument_type),*) -> R {
233                match $crate::Simd::level(simd) {
234                    #[cfg(target_arch = "aarch64")]
235                    $crate::Level::Neon(proof) => {
236                        $crate::__fearless_simd_dispatch_entry! {
237                            Neon, proof, f; $($argument_type => $argument),*
238                        }
239                    }
240                    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
241                    $crate::Level::Sse2(proof) => {
242                        $crate::__fearless_simd_dispatch_entry! {
243                            Sse2, proof, f; $($argument_type => $argument),*
244                        }
245                    }
246                    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
247                    $crate::Level::Sse4_2(proof) => {
248                        $crate::__fearless_simd_dispatch_entry! {
249                            Sse4_2, proof, f; $($argument_type => $argument),*
250                        }
251                    }
252                    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
253                    $crate::Level::Avx2(proof) => {
254                        $crate::__fearless_simd_dispatch_entry! {
255                            Avx2, proof, f; $($argument_type => $argument),*
256                        }
257                    }
258                    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
259                    $crate::Level::Avx512(proof) => {
260                        $crate::__fearless_simd_dispatch_entry! {
261                            Avx512, proof, f; $($argument_type => $argument),*
262                        }
263                    }
264                    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
265                    $crate::Level::WasmSimd128(_) => f($($argument),*),
266                    // This public predicate works even when the library omits
267                    // Level::Fallback. Do not inspect downstream Cargo features.
268                    level if level.is_fallback() => f($($argument),*),
269                    // Level is non_exhaustive. Future backends still get their
270                    // own feature context until this macro learns about them.
271                    _ => $crate::Simd::vectorize(
272                        simd,
273                        #[inline(always)]
274                        move || f($($argument),*),
275                    ),
276                }
277            }
278        }
279        __FearlessDispatch
280    }};
281}
282
283// Generate and call one backend entry for `__fearless_simd_dispatch!`.
284// Keep even direct invocations safe: the selected level determines both
285// the genuine proof type and its target features. Bind inputs outside the
286// unsafe block: even an identifier could name a mutable or extern static.
287#[doc(hidden)]
288#[macro_export]
289macro_rules! __fearless_simd_dispatch_entry {
290    ($level:ident, $proof:ident, $f:ident; $($argument_type:ident => $argument:ident),*) => {{
291        $crate::__fearless_simd_kernel_target_fn! {
292            $level,
293            #[allow(
294                clippy::all,
295                clippy::pedantic,
296                clippy::nursery,
297                clippy::restriction,
298                clippy::cargo,
299                reason = "SIMD dispatch helpers are generated implementation details"
300            )]
301            #[inline]
302            fn entry<$($argument_type,)* F, R>(
303                _: $crate::$level, $($argument: $argument_type,)* f: F,
304            ) -> R
305            where F: ::core::ops::FnOnce($($argument_type),*) -> R {
306                f($($argument),*)
307            }
308        }
309        let proof = $proof;
310        $(let $argument = $argument;)*
311        let f = $f;
312        // SAFETY: the genuine proof token establishes all the target features
313        // attached by the library's helper for this same level.
314        unsafe { entry(proof, $($argument,)* f) }
315    }};
316}
317
318/// The implementation protocol must not accept caller-supplied token types or
319/// target-feature annotations.
320///
321/// ```compile_fail
322/// fearless_simd::__fearless_simd_kernel_impl! {
323///     @cfg any();
324///     @token_ty ();
325///     @kernel_attrs;
326///     fn arbitrary(token) {}
327/// }
328/// ```
329///
330/// Only the six audited SIMD levels may select the expansion helpers.
331///
332/// ```compile_fail
333/// fearless_simd::__fearless_simd_kernel_impl! {
334///     @level Fallback;
335///     fn arbitrary(token) {}
336/// }
337/// ```
338#[doc(hidden)]
339#[macro_export]
340macro_rules! __fearless_simd_kernel_impl {
341    // The only `unsafe` call in the expansion lives here. Its token type and
342    // target-feature wrapper are both derived from the same SIMD level. The
343    // two helper macros above accept only the fixed, audited levels.
344    (
345        @level $level:ident;
346        $(#[$meta:meta])*
347        $vis:vis fn $name:ident(
348            $token:ident $(, $arg:ident : $arg_ty:ty)* $(,)?
349        ) $(-> $ret:ty)? {
350            $($kernel_body:tt)*
351        }
352    ) => {
353        $crate::__fearless_simd_kernel_cfg! {
354            $level,
355            $(#[$meta])*
356            $vis fn $name(
357                $token: $crate::$level $(, $arg: $arg_ty)*
358            ) $(-> $ret)? {
359                $crate::__fearless_simd_kernel_target_fn! {
360                    $level,
361                    #[inline] // can't use `#[inline(always)]` with target features
362                    fn __fearless_simd_kernel(
363                        $token: $crate::$level $(, $arg: $arg_ty)*
364                    ) $(-> $ret)? {
365                        let _ = $token;
366                        $($kernel_body)*
367                    }
368                }
369
370                // SAFETY: the fixed level mapping gives this inner function exactly the target
371                // features proved by the SIMD token type used by both functions.
372                #[allow(
373                    unused_unsafe,
374                    reason = "WASM has no target feature requirements and is safe to call"
375                )]
376                unsafe { __fearless_simd_kernel($token $(, $arg)*) }
377            }
378        }
379    };
380}
381
382#[cfg(test)]
383mod tests {
384    #[cfg(any(
385        target_arch = "aarch64",
386        target_arch = "x86",
387        target_arch = "x86_64",
388        all(target_arch = "wasm32", target_feature = "simd128")
389    ))]
390    use crate::prelude::*;
391
392    #[cfg(target_arch = "aarch64")]
393    use core::arch::aarch64::{float32x4_t, vaddq_f32};
394    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
395    use core::arch::wasm32::{f32x4_add, v128};
396    #[cfg(target_arch = "x86")]
397    use core::arch::x86::{
398        __m128i, __m256i, __m512i, _mm_add_epi32, _mm256_add_epi32, _mm512_add_epi32,
399    };
400    #[cfg(target_arch = "x86_64")]
401    use core::arch::x86_64::{
402        __m128i, __m256i, __m512i, _mm_add_epi32, _mm256_add_epi32, _mm512_add_epi32,
403    };
404
405    crate::kernel!(
406        fn add_f32x4_neon(neon: Neon, a: float32x4_t, b: float32x4_t) -> float32x4_t {
407            vaddq_f32(a, b)
408        }
409    );
410
411    crate::kernel!(
412        fn add_f32x4_wasm(wasm: WasmSimd128, a: v128, b: v128) -> v128 {
413            f32x4_add(a, b)
414        }
415    );
416
417    crate::kernel!(
418        fn add_i32x4_sse2(sse2: Sse2, a: __m128i, b: __m128i) -> __m128i {
419            _mm_add_epi32(a, b)
420        }
421    );
422
423    crate::kernel!(
424        fn add_i32x8_avx2(avx2: Avx2, a: __m256i, b: __m256i) -> __m256i {
425            _mm256_add_epi32(a, b)
426        }
427    );
428
429    crate::kernel! {
430        fn add_i32x16_avx512(avx512: Avx512, a: __m512i, b: __m512i) -> __m512i {
431            _mm512_add_epi32(a, b)
432        }
433    }
434
435    #[allow(
436        dead_code,
437        reason = "the associated SSE2 kernels are cfg-disabled on non-x86 targets"
438    )]
439    struct AssociatedKernels;
440
441    impl AssociatedKernels {
442        crate::kernel!(
443            fn inherent_sse2(_sse2: Sse2, value: u32) -> u32 {
444                value + 1
445            }
446        );
447    }
448
449    #[allow(
450        dead_code,
451        reason = "the associated SSE2 kernels are cfg-disabled on non-x86 targets"
452    )]
453    trait AssociatedKernelTrait {
454        crate::kernel!(
455            fn trait_sse2(_sse2: Sse2, value: u32) -> u32 {
456                value + 2
457            }
458        );
459    }
460
461    impl AssociatedKernelTrait for AssociatedKernels {}
462
463    #[cfg(target_arch = "aarch64")]
464    #[test]
465    fn kernel_instantiates_for_neon() {
466        let Some(neon) = crate::Level::new().as_neon() else {
467            return;
468        };
469
470        let a: crate::f32x4<_> = [1.0, 2.0, 3.0, 4.0].simd_into(neon);
471        let b: crate::f32x4<_> = [10.0, 20.0, 30.0, 40.0].simd_into(neon);
472        let sum: crate::f32x4<_> = add_f32x4_neon(neon, a.into(), b.into()).simd_into(neon);
473
474        assert_eq!(
475            <[f32; 4]>::from(sum),
476            [11.0, 22.0, 33.0, 44.0],
477            "`kernel!` should instantiate a working NEON kernel"
478        );
479    }
480
481    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
482    #[test]
483    fn kernel_instantiates_for_wasm_simd128() {
484        let wasm = crate::Level::new()
485            .as_wasm_simd128()
486            .expect("WASM SIMD128 should be available when +simd128 is enabled");
487
488        let a: crate::f32x4<_> = [1.0, 2.0, 3.0, 4.0].simd_into(wasm);
489        let b: crate::f32x4<_> = [10.0, 20.0, 30.0, 40.0].simd_into(wasm);
490        let sum: crate::f32x4<_> = add_f32x4_wasm(wasm, a.into(), b.into()).simd_into(wasm);
491
492        assert_eq!(
493            <[f32; 4]>::from(sum),
494            [11.0, 22.0, 33.0, 44.0],
495            "`kernel!` should instantiate a working WASM SIMD128 kernel"
496        );
497    }
498
499    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
500    #[test]
501    fn kernel_instantiates_for_sse2() {
502        let Some(sse2) = crate::Level::new().as_sse2() else {
503            return;
504        };
505
506        let a: crate::i32x4<_> = [1, 2, 3, 4].simd_into(sse2);
507        let b: crate::i32x4<_> = [10, 20, 30, 40].simd_into(sse2);
508        let sum: crate::i32x4<_> = add_i32x4_sse2(sse2, a.into(), b.into()).simd_into(sse2);
509
510        assert_eq!(
511            <[i32; 4]>::from(sum),
512            [11, 22, 33, 44],
513            "`kernel!` should instantiate a working SSE2 kernel"
514        );
515    }
516
517    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
518    #[test]
519    fn kernel_can_define_associated_functions() {
520        let Some(sse2) = crate::Level::new().as_sse2() else {
521            return;
522        };
523
524        assert_eq!(AssociatedKernels::inherent_sse2(sse2, 40), 41);
525        assert_eq!(
526            <AssociatedKernels as AssociatedKernelTrait>::trait_sse2(sse2, 40),
527            42
528        );
529    }
530
531    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
532    #[test]
533    fn kernel_instantiates_for_avx2() {
534        let Some(avx2) = crate::Level::new().as_avx2() else {
535            return;
536        };
537
538        let a: crate::i32x8<_> = [1, 2, 3, 4, 5, 6, 7, 8].simd_into(avx2);
539        let b: crate::i32x8<_> = [10, 20, 30, 40, 50, 60, 70, 80].simd_into(avx2);
540        let sum: crate::i32x8<_> = add_i32x8_avx2(avx2, a.into(), b.into()).simd_into(avx2);
541
542        assert_eq!(
543            <[i32; 8]>::from(sum),
544            [11, 22, 33, 44, 55, 66, 77, 88],
545            "`kernel!` should instantiate a working AVX2 kernel"
546        );
547    }
548
549    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
550    #[test]
551    fn kernel_instantiates_for_avx512() {
552        let Some(avx512) = crate::Level::new().as_avx512() else {
553            return;
554        };
555
556        let a: crate::i32x16<_> =
557            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].simd_into(avx512);
558        let b: crate::i32x16<_> = [
559            10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
560        ]
561        .simd_into(avx512);
562        let sum: crate::i32x16<_> = add_i32x16_avx512(avx512, a.into(), b.into()).simd_into(avx512);
563
564        assert_eq!(
565            <[i32; 16]>::from(sum),
566            [
567                11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132, 143, 154, 165, 176
568            ],
569            "`kernel!` should instantiate a working AVX-512 kernel"
570        );
571    }
572
573    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
574    #[test]
575    fn x86_kernel_functions_are_not_multiversion_gated() {
576        fn accept_avx2_kernel(_: fn(crate::Avx2, __m256i, __m256i) -> __m256i) {}
577        fn accept_avx512_kernel(_: fn(crate::Avx512, __m512i, __m512i) -> __m512i) {}
578
579        accept_avx2_kernel(add_i32x8_avx2);
580        accept_avx512_kernel(add_i32x16_avx512);
581    }
582}