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: 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///     fn add_i32x8(avx2: Avx2, a: __m256i, b: __m256i) -> __m256i {
31///         _mm256_add_epi32(a, b)
32///     }
33/// );
34///
35/// # fn main() {
36/// #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
37/// if let Some(avx2) = fearless_simd::Level::new().as_avx2() {
38///     let a: i32x8<_> = [1, 2, 3, 4, 5, 6, 7, 8].simd_into(avx2);
39///     let b: i32x8<_> = [10, 20, 30, 40, 50, 60, 70, 80].simd_into(avx2);
40///     let sum: i32x8<_> = add_i32x8(avx2, a.into(), b.into()).simd_into(avx2);
41///
42///     assert_eq!(<[i32; 8]>::from(sum), [11, 22, 33, 44, 55, 66, 77, 88]);
43/// }
44/// # }
45/// ```
46///
47/// See the [sRGB example] for an end-to-end use of kernel macros.
48///
49/// [sRGB example]: https://github.com/linebender/fearless_simd/blob/main/fearless_simd/examples/srgb.rs
50///
51/// ## Limitations
52///
53/// The macro only accepts a single plain, safe, non-generic function item with simple named parameters.
54/// However, the body of the function can be as complex as you like.
55///
56/// The SIMD token type must be written as a bare supported name:
57/// literally `Neon`, `WasmSimd128`, `Sse4_2`, `Avx2`, or `Avx512`. No paths or aliases.
58///
59/// For soundness, this macro only accepts safe functions.
60///
61/// ```compile_fail
62/// fearless_simd::kernel!(
63///     unsafe fn should_not_compile(avx2: Avx2) {}
64/// );
65#[macro_export]
66macro_rules! kernel {
67    (
68        $(#[$meta:meta])*
69        $vis:vis fn $name:ident(
70            $token:ident : $token_ty:ident $(, $arg:ident : $arg_ty:ty)* $(,)?
71        ) $(-> $ret:ty)? {
72            $($kernel_body:tt)*
73        }
74    ) => {
75        $crate::__fearless_simd_kernel_dispatch! {
76            $token_ty,
77            $(#[$meta])*
78            $vis fn $name(
79                $token $(, $arg: $arg_ty)*
80            ) $(-> $ret)? {
81                $($kernel_body)*
82            }
83        }
84    };
85
86    (
87        $(#[$meta:meta])*
88        $vis:vis fn $name:ident(
89            $token:ident : $token_ty:ty $(, $arg:ident : $arg_ty:ty)* $(,)?
90        ) $(-> $ret:ty)? {
91            $($kernel_body:tt)*
92        }
93    ) => {
94        compile_error!(concat!(
95            "fearless_simd::kernel! expects its SIMD token argument type to be written as ",
96            "one of `Neon`, `WasmSimd128`, `Sse4_2`, `Avx2`, or `Avx512`; got `",
97            stringify!($token_ty),
98            "`",
99        ));
100    };
101}
102
103#[doc(hidden)]
104#[macro_export]
105macro_rules! __fearless_simd_kernel_dispatch {
106    (
107        Neon,
108        $($body:tt)*
109    ) => {
110        $crate::__fearless_simd_kernel_impl! {
111            @cfg target_arch = "aarch64";
112            @token_ty $crate::Neon;
113            @kernel_attrs #[target_feature(enable = "neon")];
114            $($body)*
115        }
116    };
117
118    (
119        WasmSimd128,
120        $($body:tt)*
121    ) => {
122        $crate::__fearless_simd_kernel_impl! {
123            @cfg all(target_arch = "wasm32", target_feature = "simd128");
124            @token_ty $crate::WasmSimd128;
125            @kernel_attrs;
126            $($body)*
127        }
128    };
129
130    (
131        Sse4_2,
132        $($body:tt)*
133    ) => {
134        $crate::__fearless_simd_kernel_impl! {
135            @cfg any(target_arch = "x86", target_arch = "x86_64");
136            @token_ty $crate::Sse4_2;
137            @kernel_attrs #[target_feature(enable = "sse4.2,cmpxchg16b,popcnt")];
138            $($body)*
139        }
140    };
141
142    (
143        Avx2,
144        $($body:tt)*
145    ) => {
146        $crate::__fearless_simd_kernel_impl! {
147            @cfg any(target_arch = "x86", target_arch = "x86_64");
148            @token_ty $crate::Avx2;
149            @kernel_attrs #[target_feature(
150                enable = "avx2,bmi1,bmi2,cmpxchg16b,f16c,fma,lzcnt,movbe,popcnt,xsave"
151            )];
152            $($body)*
153        }
154    };
155
156    (
157        Avx512,
158        $($body:tt)*
159    ) => {
160        $crate::__fearless_simd_kernel_impl! {
161            @cfg any(target_arch = "x86", target_arch = "x86_64");
162            @token_ty $crate::Avx512;
163            @kernel_attrs #[target_feature(
164                enable = "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"
165            )];
166            $($body)*
167        }
168    };
169
170    (
171        $token_ty:ident,
172        $($body:tt)*
173    ) => {
174        compile_error!(concat!(
175            "fearless_simd::kernel! expects its SIMD token argument type to be written as ",
176            "one of `Neon`, `WasmSimd128`, `Sse4_2`, `Avx2`, or `Avx512`; got `",
177            stringify!($token_ty),
178            "`",
179        ));
180    };
181}
182
183#[doc(hidden)]
184#[macro_export]
185macro_rules! __fearless_simd_kernel_impl {
186    (
187        @cfg $cfg:meta;
188        @token_ty $token_ty:ty;
189        @kernel_attrs $(#[$kernel_attr:meta])*;
190        $(#[$meta:meta])*
191        $vis:vis fn $name:ident(
192            $token:ident $(, $arg:ident : $arg_ty:ty)* $(,)?
193        ) $(-> $ret:ty)? {
194            $($kernel_body:tt)*
195        }
196    ) => {
197        #[cfg($cfg)]
198        $(#[$meta])*
199        $vis fn $name(
200            $token: $token_ty $(, $arg: $arg_ty)*
201        ) $(-> $ret)? {
202            #[inline] // can't use `#[inline(always)]` with target features
203            $(#[$kernel_attr])*
204            fn __fearless_simd_kernel(
205                $token: $token_ty $(, $arg: $arg_ty)*
206            ) $(-> $ret)? {
207                let _ = $token;
208                $($kernel_body)*
209            }
210
211            // SAFETY: the SIMD token proves that the required target features are available.
212            #[allow(unused_unsafe, reason = "for WASM which has no target feature requirements and is safe to call")]
213            unsafe { __fearless_simd_kernel($token $(, $arg)*) }
214        }
215    };
216}
217
218#[cfg(test)]
219mod tests {
220    #[cfg(any(
221        target_arch = "aarch64",
222        target_arch = "x86",
223        target_arch = "x86_64",
224        all(target_arch = "wasm32", target_feature = "simd128")
225    ))]
226    use crate::prelude::*;
227
228    #[cfg(target_arch = "aarch64")]
229    use core::arch::aarch64::{float32x4_t, vaddq_f32};
230    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
231    use core::arch::wasm32::{f32x4_add, v128};
232    #[cfg(target_arch = "x86")]
233    use core::arch::x86::{__m256i, __m512i, _mm256_add_epi32, _mm512_add_epi32};
234    #[cfg(target_arch = "x86_64")]
235    use core::arch::x86_64::{__m256i, __m512i, _mm256_add_epi32, _mm512_add_epi32};
236
237    crate::kernel!(
238        fn add_f32x4_neon(neon: Neon, a: float32x4_t, b: float32x4_t) -> float32x4_t {
239            vaddq_f32(a, b)
240        }
241    );
242
243    crate::kernel!(
244        fn add_f32x4_wasm(wasm: WasmSimd128, a: v128, b: v128) -> v128 {
245            f32x4_add(a, b)
246        }
247    );
248
249    crate::kernel!(
250        fn add_i32x8_avx2(avx2: Avx2, a: __m256i, b: __m256i) -> __m256i {
251            _mm256_add_epi32(a, b)
252        }
253    );
254
255    crate::kernel! {
256        fn add_i32x16_avx512(avx512: Avx512, a: __m512i, b: __m512i) -> __m512i {
257            _mm512_add_epi32(a, b)
258        }
259    }
260
261    #[cfg(target_arch = "aarch64")]
262    #[test]
263    fn kernel_instantiates_for_neon() {
264        let Some(neon) = crate::Level::new().as_neon() else {
265            return;
266        };
267
268        let a: crate::f32x4<_> = [1.0, 2.0, 3.0, 4.0].simd_into(neon);
269        let b: crate::f32x4<_> = [10.0, 20.0, 30.0, 40.0].simd_into(neon);
270        let sum: crate::f32x4<_> = add_f32x4_neon(neon, a.into(), b.into()).simd_into(neon);
271
272        assert_eq!(
273            <[f32; 4]>::from(sum),
274            [11.0, 22.0, 33.0, 44.0],
275            "`kernel!` should instantiate a working NEON kernel"
276        );
277    }
278
279    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
280    #[test]
281    fn kernel_instantiates_for_wasm_simd128() {
282        let wasm = crate::Level::new()
283            .as_wasm_simd128()
284            .expect("WASM SIMD128 should be available when +simd128 is enabled");
285
286        let a: crate::f32x4<_> = [1.0, 2.0, 3.0, 4.0].simd_into(wasm);
287        let b: crate::f32x4<_> = [10.0, 20.0, 30.0, 40.0].simd_into(wasm);
288        let sum: crate::f32x4<_> = add_f32x4_wasm(wasm, a.into(), b.into()).simd_into(wasm);
289
290        assert_eq!(
291            <[f32; 4]>::from(sum),
292            [11.0, 22.0, 33.0, 44.0],
293            "`kernel!` should instantiate a working WASM SIMD128 kernel"
294        );
295    }
296
297    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
298    #[test]
299    fn kernel_instantiates_for_avx2() {
300        let Some(avx2) = crate::Level::new().as_avx2() else {
301            return;
302        };
303
304        let a: crate::i32x8<_> = [1, 2, 3, 4, 5, 6, 7, 8].simd_into(avx2);
305        let b: crate::i32x8<_> = [10, 20, 30, 40, 50, 60, 70, 80].simd_into(avx2);
306        let sum: crate::i32x8<_> = add_i32x8_avx2(avx2, a.into(), b.into()).simd_into(avx2);
307
308        assert_eq!(
309            <[i32; 8]>::from(sum),
310            [11, 22, 33, 44, 55, 66, 77, 88],
311            "`kernel!` should instantiate a working AVX2 kernel"
312        );
313    }
314
315    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
316    #[test]
317    fn kernel_instantiates_for_avx512() {
318        let Some(avx512) = crate::Level::new().as_avx512() else {
319            return;
320        };
321
322        let a: crate::i32x16<_> =
323            [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].simd_into(avx512);
324        let b: crate::i32x16<_> = [
325            10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
326        ]
327        .simd_into(avx512);
328        let sum: crate::i32x16<_> = add_i32x16_avx512(avx512, a.into(), b.into()).simd_into(avx512);
329
330        assert_eq!(
331            <[i32; 16]>::from(sum),
332            [
333                11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 132, 143, 154, 165, 176
334            ],
335            "`kernel!` should instantiate a working AVX-512 kernel"
336        );
337    }
338
339    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
340    #[test]
341    fn x86_kernel_functions_are_not_multiversion_gated() {
342        fn accept_avx2_kernel(_: fn(crate::Avx2, __m256i, __m256i) -> __m256i) {}
343        fn accept_avx512_kernel(_: fn(crate::Avx512, __m512i, __m512i) -> __m512i) {}
344
345        accept_avx2_kernel(add_i32x8_avx2);
346        accept_avx512_kernel(add_i32x16_avx512);
347    }
348}