1#[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#[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#[doc(hidden)]
198#[macro_export]
199macro_rules! __fearless_simd_dispatch {
200 ($($argument_type:ident => $argument:ident),* $(,)?) => {{
201 #[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 #[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 level if level.is_fallback() => f($($argument),*),
269 _ => $crate::Simd::vectorize(
272 simd,
273 #[inline(always)]
274 move || f($($argument),*),
275 ),
276 }
277 }
278 }
279 __FearlessDispatch
280 }};
281}
282
283#[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 unsafe { entry(proof, $($argument,)* f) }
315 }};
316}
317
318#[doc(hidden)]
339#[macro_export]
340macro_rules! __fearless_simd_kernel_impl {
341 (
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] fn __fearless_simd_kernel(
363 $token: $crate::$level $(, $arg: $arg_ty)*
364 ) $(-> $ret)? {
365 let _ = $token;
366 $($kernel_body)*
367 }
368 }
369
370 #[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}