1#![allow(clippy::items_after_statements)]
47
48#[cfg(feature = "bench-internals")]
53pub mod scalar;
54#[cfg(not(feature = "bench-internals"))]
55pub(crate) mod scalar;
56
57#[cfg(test)]
58mod proptest;
59
60#[allow(
66 clippy::cast_possible_wrap,
67 clippy::cast_ptr_alignment,
68 clippy::incompatible_msrv,
69 clippy::inline_always,
70 clippy::if_not_else,
71 clippy::ptr_as_ptr,
72 clippy::too_many_lines,
73 clippy::unreadable_literal,
74 clippy::verbose_bit_mask,
75 clippy::wildcard_imports
76)]
77#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
78pub(crate) mod x86;
79
80#[cfg(target_arch = "aarch64")]
81pub(crate) mod arm;
82
83pub(crate) mod wasm;
84
85#[cfg(all(feature = "bench-internals", feature = "std"))]
92pub mod bench {
93 use super::ranges_overlap;
94
95 type DirectAxpyFn = unsafe fn(c: u8, x: &[u8], y: &mut [u8]);
96
97 #[derive(Clone, Copy)]
100 pub struct DirectAxpyTier {
101 name: &'static str,
102 function: DirectAxpyFn,
103 }
104
105 impl DirectAxpyTier {
106 #[must_use]
108 pub fn name(self) -> &'static str {
109 self.name
110 }
111
112 pub fn axpy(self, c: u8, x: &[u8], y: &mut [u8]) {
117 assert_eq!(
118 x.len(),
119 y.len(),
120 "rlnc_simdx::kernel::bench::DirectAxpyTier::axpy: length mismatch"
121 );
122 assert!(
123 !ranges_overlap(x.as_ptr(), x.len(), y.as_ptr(), y.len()),
124 "rlnc_simdx::kernel::bench::DirectAxpyTier::axpy: overlapping buffers are not allowed"
125 );
126 unsafe { (self.function)(c, x, y) };
130 }
131 }
132
133 #[must_use]
135 pub fn available_axpy_tiers() -> Vec<DirectAxpyTier> {
136 let mut tiers = Vec::new();
137
138 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
139 {
140 use super::x86;
141
142 if std::is_x86_feature_detected!("gfni")
143 && std::is_x86_feature_detected!("avx512f")
144 && std::is_x86_feature_detected!("avx512bw")
145 {
146 tiers.push(DirectAxpyTier {
147 name: "gfni+avx512",
148 function: x86::gfni_avx512::axpy_gfni_avx512 as DirectAxpyFn,
149 });
150 }
151 if std::is_x86_feature_detected!("gfni") && std::is_x86_feature_detected!("avx2") {
152 tiers.push(DirectAxpyTier {
153 name: "gfni+avx2",
154 function: x86::gfni_avx2::axpy_gfni_avx2 as DirectAxpyFn,
155 });
156 }
157 if std::is_x86_feature_detected!("gfni") && std::is_x86_feature_detected!("sse4.2") {
158 tiers.push(DirectAxpyTier {
159 name: "gfni+sse4.2",
160 function: x86::gfni_sse::axpy_gfni_sse as DirectAxpyFn,
161 });
162 }
163 if std::is_x86_feature_detected!("avx512f")
164 && std::is_x86_feature_detected!("avx512bw")
165 && std::is_x86_feature_detected!("ssse3")
166 {
167 tiers.push(DirectAxpyTier {
168 name: "avx512+ssse3",
169 function: x86::avx512_ssse3::axpy_avx512_ssse3 as DirectAxpyFn,
170 });
171 }
172 if std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("ssse3") {
173 tiers.push(DirectAxpyTier {
174 name: "avx2+ssse3",
175 function: x86::avx2_ssse3::axpy_avx2_ssse3 as DirectAxpyFn,
176 });
177 }
178 if std::is_x86_feature_detected!("ssse3") {
179 tiers.push(DirectAxpyTier {
180 name: "ssse3",
181 function: x86::ssse3::axpy_ssse3 as DirectAxpyFn,
182 });
183 }
184 }
185
186 #[cfg(target_arch = "aarch64")]
187 tiers.push(DirectAxpyTier {
188 name: "neon",
189 function: super::arm::neon::axpy_neon as DirectAxpyFn,
190 });
191
192 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
193 tiers.push(DirectAxpyTier {
194 name: "wasm-simd128",
195 function: super::wasm::simd128::axpy_wasm as DirectAxpyFn,
196 });
197
198 tiers
199 }
200}
201
202#[cfg(feature = "std")]
208pub(crate) type AxpyFn = unsafe fn(c: u8, x: &[u8], y: &mut [u8]);
209#[cfg(feature = "std")]
211pub(crate) type ScaleFn = unsafe fn(c: u8, x: &[u8], y: &mut [u8]);
212#[cfg(feature = "std")]
214pub(crate) type ScaleInplaceFn = unsafe fn(c: u8, y: &mut [u8]);
215#[cfg(feature = "std")]
217pub(crate) type AxpyMultiFn = unsafe fn(coeffs: &[u8], sources: &[&[u8]], y: &mut [u8]);
218#[cfg(feature = "std")]
220pub(crate) type DotFn = unsafe fn(a: &[u8], b: &[u8]) -> u8;
221
222#[cfg(feature = "std")]
224pub(crate) struct KernelSet {
225 pub(crate) axpy: AxpyFn,
227 pub(crate) scale: ScaleFn,
229 pub(crate) scale_inplace: ScaleInplaceFn,
231 pub(crate) axpy_multi: Option<AxpyMultiFn>,
233 pub(crate) dot: Option<DotFn>,
235 pub(crate) name: &'static str,
237}
238
239#[cfg(feature = "std")]
244mod runtime {
245 #[cfg(target_arch = "aarch64")]
246 use super::arm;
247 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
248 use super::wasm;
249 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
250 use super::x86;
251 #[cfg(not(any(
252 target_arch = "aarch64",
253 all(target_family = "wasm", target_feature = "simd128")
254 )))]
255 use super::{scalar_axpy_wrapper, scalar_scale_inplace_wrapper, scalar_scale_wrapper};
256 use super::{AxpyFn, KernelSet, ScaleFn, ScaleInplaceFn};
257 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
258 use super::{AxpyMultiFn, DotFn};
259 use std::sync::OnceLock;
260
261 static KERNEL: OnceLock<KernelSet> = OnceLock::new();
262
263 pub(crate) fn get() -> &'static KernelSet {
265 KERNEL.get_or_init(detect)
266 }
267
268 #[allow(clippy::too_many_lines)]
270 fn detect() -> KernelSet {
271 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
273 {
274 if is_x86_feature_detected!("gfni")
276 && is_x86_feature_detected!("avx512f")
277 && is_x86_feature_detected!("avx512bw")
278 {
279 return KernelSet {
280 axpy: x86::gfni_avx512::axpy_gfni_avx512 as AxpyFn,
281 scale: x86::gfni_avx512::scale_gfni_avx512 as ScaleFn,
282 scale_inplace: x86::gfni_avx512::scale_inplace_gfni_avx512 as ScaleInplaceFn,
283 axpy_multi: Some(x86::gfni_avx512::axpy_multi_gfni_avx512 as AxpyMultiFn),
284 dot: Some(x86::gfni_avx512::dot_gfni_avx512 as DotFn),
285 name: "gfni+avx512 (tier1)",
286 };
287 }
288
289 if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx2") {
291 return KernelSet {
292 axpy: x86::gfni_avx2::axpy_gfni_avx2 as AxpyFn,
293 scale: x86::gfni_avx2::scale_gfni_avx2 as ScaleFn,
294 scale_inplace: x86::gfni_avx2::scale_inplace_gfni_avx2 as ScaleInplaceFn,
295 axpy_multi: Some(x86::gfni_avx2::axpy_multi_gfni_avx2 as AxpyMultiFn),
296 dot: Some(x86::gfni_avx2::dot_gfni_avx2 as DotFn),
297 name: "gfni+avx2 (tier2)",
298 };
299 }
300
301 if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("sse4.2") {
303 return KernelSet {
304 axpy: x86::gfni_sse::axpy_gfni_sse as AxpyFn,
305 scale: x86::gfni_sse::scale_gfni_sse as ScaleFn,
306 scale_inplace: x86::gfni_sse::scale_inplace_gfni_sse as ScaleInplaceFn,
307 axpy_multi: Some(x86::gfni_sse::axpy_multi_gfni_sse as AxpyMultiFn),
308 dot: Some(x86::gfni_sse::dot_gfni_sse as DotFn),
309 name: "gfni+sse4.2 (tier3)",
310 };
311 }
312
313 if is_x86_feature_detected!("avx512f")
315 && is_x86_feature_detected!("avx512bw")
316 && is_x86_feature_detected!("ssse3")
317 {
318 return KernelSet {
319 axpy: x86::avx512_ssse3::axpy_avx512_ssse3 as AxpyFn,
320 scale: x86::avx512_ssse3::scale_avx512_ssse3 as ScaleFn,
321 scale_inplace: x86::avx512_ssse3::scale_inplace_avx512_ssse3 as ScaleInplaceFn,
322 axpy_multi: None,
323 dot: None,
324 name: "avx512+ssse3 (tier4)",
325 };
326 }
327
328 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("ssse3") {
330 return KernelSet {
331 axpy: x86::avx2_ssse3::axpy_avx2_ssse3 as AxpyFn,
332 scale: x86::avx2_ssse3::scale_avx2_ssse3 as ScaleFn,
333 scale_inplace: x86::avx2_ssse3::scale_inplace_avx2_ssse3 as ScaleInplaceFn,
334 axpy_multi: None,
335 dot: None,
336 name: "avx2+ssse3 (tier5)",
337 };
338 }
339
340 if is_x86_feature_detected!("ssse3") {
342 return KernelSet {
343 axpy: x86::ssse3::axpy_ssse3 as AxpyFn,
344 scale: x86::ssse3::scale_ssse3 as ScaleFn,
345 scale_inplace: x86::ssse3::scale_inplace_ssse3 as ScaleInplaceFn,
346 axpy_multi: None,
347 dot: None,
348 name: "ssse3 (tier6)",
349 };
350 }
351 }
352
353 #[cfg(target_arch = "aarch64")]
355 {
356 return KernelSet {
358 axpy: arm::neon::axpy_neon as AxpyFn,
359 scale: arm::neon::scale_neon as ScaleFn,
360 scale_inplace: arm::neon::scale_inplace_neon as ScaleInplaceFn,
361 axpy_multi: None,
362 dot: None,
363 name: "neon (tier7)",
364 };
365 }
366
367 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
371 {
372 return KernelSet {
373 axpy: wasm::simd128::axpy_wasm as AxpyFn,
374 scale: wasm::simd128::scale_wasm as ScaleFn,
375 scale_inplace: wasm::simd128::scale_inplace_wasm as ScaleInplaceFn,
376 axpy_multi: None,
377 dot: None,
378 name: "wasm-simd128 (tier8)",
379 };
380 }
381
382 #[cfg(not(any(
386 target_arch = "aarch64",
387 all(target_family = "wasm", target_feature = "simd128")
388 )))]
389 KernelSet {
390 axpy: scalar_axpy_wrapper as AxpyFn,
391 scale: scalar_scale_wrapper as ScaleFn,
392 scale_inplace: scalar_scale_inplace_wrapper as ScaleInplaceFn,
393 axpy_multi: None,
394 dot: None,
395 name: "scalar (tier9)",
396 }
397 }
398}
399
400#[cfg(all(
405 feature = "std",
406 not(any(
407 target_arch = "aarch64",
408 all(target_family = "wasm", target_feature = "simd128")
409 ))
410))]
411unsafe fn scalar_axpy_wrapper(c: u8, x: &[u8], y: &mut [u8]) {
412 scalar::axpy(c, x, y);
413}
414
415#[cfg(all(
416 feature = "std",
417 not(any(
418 target_arch = "aarch64",
419 all(target_family = "wasm", target_feature = "simd128")
420 ))
421))]
422unsafe fn scalar_scale_wrapper(c: u8, x: &[u8], y: &mut [u8]) {
423 scalar::scale(c, x, y);
424}
425
426#[cfg(all(
427 feature = "std",
428 not(any(
429 target_arch = "aarch64",
430 all(target_family = "wasm", target_feature = "simd128")
431 ))
432))]
433unsafe fn scalar_scale_inplace_wrapper(c: u8, y: &mut [u8]) {
434 scalar::scale_inplace(c, y);
435}
436
437#[inline]
457pub fn axpy(c: u8, x: &[u8], y: &mut [u8]) {
458 assert_eq!(
459 x.len(),
460 y.len(),
461 "rlnc_simdx::kernel::axpy: length mismatch"
462 );
463 assert!(
464 !ranges_overlap(x.as_ptr(), x.len(), y.as_ptr(), y.len()),
465 "rlnc_simdx::kernel::axpy: overlapping buffers are not allowed"
466 );
467 unsafe { axpy_unchecked(c, x, y) }
469}
470
471#[inline]
475pub(crate) unsafe fn axpy_unchecked(c: u8, x: &[u8], y: &mut [u8]) {
476 debug_assert_eq!(x.len(), y.len());
477 #[cfg(feature = "std")]
478 (runtime::get().axpy)(c, x, y);
479
480 #[cfg(not(feature = "std"))]
481 axpy_static(c, x, y)
482}
483
484#[inline]
495pub fn scale(c: u8, x: &[u8], y: &mut [u8]) {
496 assert_eq!(
497 x.len(),
498 y.len(),
499 "rlnc_simdx::kernel::scale: length mismatch"
500 );
501 assert!(
502 !ranges_overlap(x.as_ptr(), x.len(), y.as_ptr(), y.len()),
503 "rlnc_simdx::kernel::scale: overlapping buffers are not allowed; use scale_inplace for in-place"
504 );
505 #[cfg(feature = "std")]
506 unsafe {
507 (runtime::get().scale)(c, x, y);
508 }
509
510 #[cfg(not(feature = "std"))]
511 scale_static(c, x, y)
512}
513
514#[inline]
520pub fn scale_inplace(c: u8, y: &mut [u8]) {
521 #[cfg(feature = "std")]
522 unsafe {
524 (runtime::get().scale_inplace)(c, y);
525 }
526
527 #[cfg(not(feature = "std"))]
528 scale_inplace_static(c, y)
529}
530
531#[inline]
546pub fn axpy_multi(coeffs: &[u8], sources: &[&[u8]], y: &mut [u8]) {
547 assert_eq!(
548 coeffs.len(),
549 sources.len(),
550 "axpy_multi: coeffs/sources len"
551 );
552
553 for (i, src) in sources.iter().enumerate() {
556 assert_eq!(
557 src.len(),
558 y.len(),
559 "axpy_multi: source[{i}] length mismatch"
560 );
561 }
562 for (i, src) in sources.iter().enumerate() {
563 assert!(
564 !ranges_overlap(src.as_ptr(), src.len(), y.as_ptr(), y.len()),
565 "axpy_multi: source[{i}] overlaps destination"
566 );
567 }
568
569 #[cfg(feature = "std")]
570 let axpy_kernel = {
571 let kernels = runtime::get();
572 if y.len() >= 32 * 1024 && coeffs.len() >= 8 {
576 if let Some(fused) = kernels.axpy_multi {
577 unsafe { fused(coeffs, sources, y) };
580 return;
581 }
582 }
583 kernels.axpy
584 };
585
586 #[cfg(not(feature = "std"))]
587 if y.len() >= 32 * 1024 && coeffs.len() >= 8 && axpy_multi_static(coeffs, sources, y) {
588 return;
589 }
590
591 const BLOCK: usize = 4096;
594 let n = y.len();
595 let mut off = 0usize;
596 while off < n {
597 let end = (off + BLOCK).min(n);
598 for (i, &c) in coeffs.iter().enumerate() {
599 if c != 0 {
600 let source_block = &sources[i][off..end];
601 let destination_block = &mut y[off..end];
602
603 #[cfg(feature = "std")]
604 unsafe {
609 axpy_kernel(c, source_block, destination_block);
610 }
611
612 #[cfg(not(feature = "std"))]
613 axpy_static(c, source_block, destination_block);
614 }
615 }
616 off = end;
617 }
618}
619
620#[inline]
623fn ranges_overlap(a: *const u8, a_len: usize, b: *const u8, b_len: usize) -> bool {
624 if a_len == 0 || b_len == 0 {
625 return false;
626 }
627 let a0 = a as usize;
628 let b0 = b as usize;
629 let a1 = a0 + a_len;
630 let b1 = b0 + b_len;
631 a0 < b1 && b0 < a1
632}
633
634#[inline]
639#[must_use]
640pub fn dot(a: &[u8], b: &[u8]) -> u8 {
641 assert_eq!(a.len(), b.len(), "rlnc_simdx::kernel::dot: length mismatch");
642 #[cfg(feature = "std")]
643 if let Some(dot_kernel) = runtime::get().dot {
644 return unsafe { dot_kernel(a, b) };
646 }
647 #[cfg(not(feature = "std"))]
648 if let Some(result) = dot_static(a, b) {
649 return result;
650 }
651 scalar::dot(a, b)
652}
653
654#[must_use]
656pub fn active_kernel_name() -> &'static str {
657 #[cfg(feature = "std")]
658 {
659 runtime::get().name
660 }
661
662 #[cfg(not(feature = "std"))]
663 {
664 active_kernel_name_static()
665 }
666}
667
668#[cfg(not(feature = "std"))]
674#[inline]
675fn axpy_static(c: u8, x: &[u8], y: &mut [u8]) {
676 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
678 return unsafe { wasm::simd128::axpy_wasm(c, x, y) };
680
681 #[cfg(target_arch = "aarch64")]
683 return unsafe { arm::neon::axpy_neon(c, x, y) };
684
685 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
687 {
688 #[cfg(all(
689 target_feature = "gfni",
690 target_feature = "avx512f",
691 target_feature = "avx512bw"
692 ))]
693 return unsafe { x86::gfni_avx512::axpy_gfni_avx512(c, x, y) };
694 #[cfg(all(
695 target_feature = "gfni",
696 target_feature = "avx2",
697 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
698 ))]
699 return unsafe { x86::gfni_avx2::axpy_gfni_avx2(c, x, y) };
700 #[cfg(all(
701 target_feature = "gfni",
702 target_feature = "sse4.2",
703 not(target_feature = "avx2")
704 ))]
705 return unsafe { x86::gfni_sse::axpy_gfni_sse(c, x, y) };
706 #[cfg(all(
707 target_feature = "avx512f",
708 target_feature = "avx512bw",
709 target_feature = "ssse3",
710 not(target_feature = "gfni")
711 ))]
712 return unsafe { x86::avx512_ssse3::axpy_avx512_ssse3(c, x, y) };
713 #[cfg(all(
714 target_feature = "avx2",
715 target_feature = "ssse3",
716 not(target_feature = "gfni"),
717 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
718 ))]
719 return unsafe { x86::avx2_ssse3::axpy_avx2_ssse3(c, x, y) };
720 #[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
721 return unsafe { x86::ssse3::axpy_ssse3(c, x, y) };
722 }
723
724 #[cfg(not(any(
726 target_arch = "aarch64",
727 all(target_family = "wasm", target_feature = "simd128")
728 )))]
729 scalar::axpy(c, x, y)
730}
731
732#[cfg(not(feature = "std"))]
733#[inline]
734fn scale_static(c: u8, x: &[u8], y: &mut [u8]) {
735 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
736 return unsafe { wasm::simd128::scale_wasm(c, x, y) };
738
739 #[cfg(target_arch = "aarch64")]
740 return unsafe { arm::neon::scale_neon(c, x, y) };
741
742 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
743 {
744 #[cfg(all(
745 target_feature = "gfni",
746 target_feature = "avx512f",
747 target_feature = "avx512bw"
748 ))]
749 return unsafe { x86::gfni_avx512::scale_gfni_avx512(c, x, y) };
750 #[cfg(all(
751 target_feature = "gfni",
752 target_feature = "avx2",
753 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
754 ))]
755 return unsafe { x86::gfni_avx2::scale_gfni_avx2(c, x, y) };
756 #[cfg(all(
757 target_feature = "gfni",
758 target_feature = "sse4.2",
759 not(target_feature = "avx2")
760 ))]
761 return unsafe { x86::gfni_sse::scale_gfni_sse(c, x, y) };
762 #[cfg(all(
763 target_feature = "avx512f",
764 target_feature = "avx512bw",
765 target_feature = "ssse3",
766 not(target_feature = "gfni")
767 ))]
768 return unsafe { x86::avx512_ssse3::scale_avx512_ssse3(c, x, y) };
769 #[cfg(all(
770 target_feature = "avx2",
771 target_feature = "ssse3",
772 not(target_feature = "gfni"),
773 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
774 ))]
775 return unsafe { x86::avx2_ssse3::scale_avx2_ssse3(c, x, y) };
776 #[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
777 return unsafe { x86::ssse3::scale_ssse3(c, x, y) };
778 }
779
780 #[cfg(not(any(
781 target_arch = "aarch64",
782 all(target_family = "wasm", target_feature = "simd128")
783 )))]
784 scalar::scale(c, x, y)
785}
786
787#[cfg(not(feature = "std"))]
788#[inline]
789fn scale_inplace_static(c: u8, y: &mut [u8]) {
790 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
791 return unsafe { wasm::simd128::scale_inplace_wasm(c, y) };
793
794 #[cfg(target_arch = "aarch64")]
795 return unsafe { arm::neon::scale_inplace_neon(c, y) };
796
797 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
798 {
799 #[cfg(all(
800 target_feature = "gfni",
801 target_feature = "avx512f",
802 target_feature = "avx512bw"
803 ))]
804 return unsafe { x86::gfni_avx512::scale_inplace_gfni_avx512(c, y) };
805 #[cfg(all(
806 target_feature = "gfni",
807 target_feature = "avx2",
808 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
809 ))]
810 return unsafe { x86::gfni_avx2::scale_inplace_gfni_avx2(c, y) };
811 #[cfg(all(
812 target_feature = "gfni",
813 target_feature = "sse4.2",
814 not(target_feature = "avx2")
815 ))]
816 return unsafe { x86::gfni_sse::scale_inplace_gfni_sse(c, y) };
817 #[cfg(all(
818 target_feature = "avx512f",
819 target_feature = "avx512bw",
820 target_feature = "ssse3",
821 not(target_feature = "gfni")
822 ))]
823 return unsafe { x86::avx512_ssse3::scale_inplace_avx512_ssse3(c, y) };
824 #[cfg(all(
825 target_feature = "avx2",
826 target_feature = "ssse3",
827 not(target_feature = "gfni"),
828 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
829 ))]
830 return unsafe { x86::avx2_ssse3::scale_inplace_avx2_ssse3(c, y) };
831 #[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
832 return unsafe { x86::ssse3::scale_inplace_ssse3(c, y) };
833 }
834
835 #[cfg(not(any(
836 target_arch = "aarch64",
837 all(target_family = "wasm", target_feature = "simd128")
838 )))]
839 scalar::scale_inplace(c, y)
840}
841
842#[cfg(not(feature = "std"))]
843fn active_kernel_name_static() -> &'static str {
844 #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
845 return "wasm-simd128 (tier8)";
846 #[cfg(target_arch = "aarch64")]
847 return "neon (tier7)";
848 #[cfg(all(
849 target_feature = "gfni",
850 target_feature = "avx512f",
851 target_feature = "avx512bw"
852 ))]
853 return "gfni+avx512 (tier1)";
854 #[cfg(all(
855 target_feature = "gfni",
856 target_feature = "avx2",
857 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
858 ))]
859 return "gfni+avx2 (tier2)";
860 #[cfg(all(
861 target_feature = "gfni",
862 target_feature = "sse4.2",
863 not(target_feature = "avx2")
864 ))]
865 return "gfni+sse4.2 (tier3)";
866 #[cfg(all(
867 target_feature = "avx512f",
868 target_feature = "avx512bw",
869 target_feature = "ssse3",
870 not(target_feature = "gfni")
871 ))]
872 return "avx512+ssse3 (tier4)";
873 #[cfg(all(
874 target_feature = "avx2",
875 target_feature = "ssse3",
876 not(target_feature = "gfni"),
877 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
878 ))]
879 return "avx2+ssse3 (tier5)";
880 #[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
881 return "ssse3 (tier6)";
882 #[cfg(not(any(
883 target_arch = "aarch64",
884 all(target_family = "wasm", target_feature = "simd128")
885 )))]
886 return "scalar (tier9)";
887}
888
889#[cfg(not(feature = "std"))]
890#[inline]
891fn axpy_multi_static(coeffs: &[u8], sources: &[&[u8]], y: &mut [u8]) -> bool {
892 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
893 {
894 #[cfg(all(
895 target_feature = "gfni",
896 target_feature = "avx512f",
897 target_feature = "avx512bw"
898 ))]
899 unsafe {
900 x86::gfni_avx512::axpy_multi_gfni_avx512(coeffs, sources, y);
901 return true;
902 }
903 #[cfg(all(
904 target_feature = "gfni",
905 target_feature = "avx2",
906 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
907 ))]
908 unsafe {
909 x86::gfni_avx2::axpy_multi_gfni_avx2(coeffs, sources, y);
910 return true;
911 }
912 #[cfg(all(
913 target_feature = "gfni",
914 target_feature = "sse4.2",
915 not(target_feature = "avx2")
916 ))]
917 unsafe {
918 x86::gfni_sse::axpy_multi_gfni_sse(coeffs, sources, y);
919 return true;
920 }
921 }
922 let _ = (coeffs, sources, y);
923 false
924}
925
926#[cfg(not(feature = "std"))]
927#[inline]
928fn dot_static(a: &[u8], b: &[u8]) -> Option<u8> {
929 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
930 {
931 #[cfg(all(
932 target_feature = "gfni",
933 target_feature = "avx512f",
934 target_feature = "avx512bw"
935 ))]
936 return Some(unsafe { x86::gfni_avx512::dot_gfni_avx512(a, b) });
937 #[cfg(all(
938 target_feature = "gfni",
939 target_feature = "avx2",
940 not(all(target_feature = "avx512f", target_feature = "avx512bw"))
941 ))]
942 return Some(unsafe { x86::gfni_avx2::dot_gfni_avx2(a, b) });
943 #[cfg(all(
944 target_feature = "gfni",
945 target_feature = "sse4.2",
946 not(target_feature = "avx2")
947 ))]
948 return Some(unsafe { x86::gfni_sse::dot_gfni_sse(a, b) });
949 }
950 let _ = (a, b);
951 None
952}
953
954#[cfg(test)]
959mod tests {
960 use super::*;
961
962 #[test]
963 fn axpy_round_trip() {
964 let c = 0x53u8;
965 let x: Vec<u8> = (0u8..128).collect();
966 let mut y = vec![0u8; 128];
967 axpy(c, &x, &mut y);
968 axpy(c, &x, &mut y);
969 assert_eq!(y, vec![0u8; 128]);
970 }
971
972 #[test]
973 fn scale_axpy_consistency() {
974 let c = 0xC7u8;
975 let x: Vec<u8> = (1u8..=64).collect();
976 let mut y_scale = vec![0u8; 64];
977 let mut y_axpy = vec![0u8; 64];
978 scale(c, &x, &mut y_scale);
979 axpy(c, &x, &mut y_axpy);
980 assert_eq!(y_scale, y_axpy);
981 }
982
983 #[test]
984 fn dispatched_dot_matches_scalar_across_vector_tails() {
985 for len in [0usize, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 1025] {
986 let a: Vec<u8> = (0..len)
987 .map(|index| (index as u8).wrapping_mul(17).wrapping_add(1))
988 .collect();
989 let b: Vec<u8> = (0..len)
990 .map(|index| (index as u8).wrapping_mul(29).wrapping_add(3))
991 .collect();
992 assert_eq!(dot(&a, &b), scalar::dot(&a, &b), "len={len}");
993 }
994 }
995
996 #[test]
997 fn kernel_name_not_empty() {
998 let name = active_kernel_name();
999 assert!(!name.is_empty());
1000 println!("Active kernel: {name}");
1001 }
1002
1003 #[test]
1004 #[cfg(feature = "std")]
1005 fn runtime_dispatch_selects_best() {
1006 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
1008 {
1009 let name = active_kernel_name();
1010 if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx512f") {
1011 assert!(name.contains("tier1"), "Expected tier1, got: {name}");
1012 } else if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx2") {
1013 assert!(name.contains("tier2"), "Expected tier2, got: {name}");
1014 } else if is_x86_feature_detected!("ssse3") {
1015 assert!(
1016 name.contains("tier3")
1017 || name.contains("tier4")
1018 || name.contains("tier5")
1019 || name.contains("tier6"),
1020 "Expected SSSE3-tier, got: {name}"
1021 );
1022 }
1023 }
1024
1025 #[cfg(target_arch = "aarch64")]
1026 assert_eq!(active_kernel_name(), "neon (tier7)");
1027
1028 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
1029 assert!(!active_kernel_name().is_empty());
1030 }
1031}