1#[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] $(#[$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 #[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}