1use super::*;
2
3pick! {
4 if #[cfg(target_feature="avx2")] {
5 #[derive(Default, Clone, Copy, PartialEq, Eq)]
12 #[repr(C, align(32))]
13 pub struct u64x4 { pub(crate) avx2: m256i }
14 } else {
15 #[derive(Default, Clone, Copy, PartialEq, Eq)]
22 #[repr(C, align(32))]
23 pub struct u64x4 { pub(crate) a : u64x2, pub(crate) b : u64x2 }
24 }
25}
26
27impl_simd_uint! {
28 unsafe {
29 T = u64,
30 N = 4,
31 Simd = u64x4,
32 IntSimd = i64x4,
33 T_BITS = 64,
34 T_BITS_MUL_2 = 128,
35 [0, 1, 2, 3],
36 optional_type_x86_inner { X86Inner = __m256i },
37 optional_type_arm_inner {},
38 optional_type_wasm_inner {},
39 }
40
41 #[inline]
42 fn not(self) -> Self {
43 pick! {
44 if #[cfg(target_feature="avx2")] {
45 Self { avx2: self.avx2.not() }
46 } else {
47 Self {
48 a : self.a.not(),
49 b : self.b.not(),
50 }
51 }
52 }
53 }
54
55 #[inline]
56 fn add(self, rhs: Self) -> Self::Output {
57 pick! {
58 if #[cfg(target_feature="avx2")] {
59 Self { avx2: add_i64_m256i(self.avx2, rhs.avx2) }
60 } else {
61 Self {
62 a : self.a.add(rhs.a),
63 b : self.b.add(rhs.b),
64 }
65 }
66 }
67 }
68
69 #[inline]
70 fn sub(self, rhs: Self) -> Self::Output {
71 pick! {
72 if #[cfg(target_feature="avx2")] {
73 Self { avx2: sub_i64_m256i(self.avx2, rhs.avx2) }
74 } else {
75 Self {
76 a : self.a.sub(rhs.a),
77 b : self.b.sub(rhs.b),
78 }
79 }
80 }
81 }
82
83 #[inline]
84 fn mul(self, rhs: Self) -> Self::Output {
85 pick! {
86 if #[cfg(target_feature="avx2")] {
87 let arr1: [i64; 4] = cast(self);
88 let arr2: [i64; 4] = cast(rhs);
89 cast([
90 arr1[0].wrapping_mul(arr2[0]),
91 arr1[1].wrapping_mul(arr2[1]),
92 arr1[2].wrapping_mul(arr2[2]),
93 arr1[3].wrapping_mul(arr2[3]),
94 ])
95 } else {
96 Self { a: self.a.mul(rhs.a), b: self.b.mul(rhs.b) }
97 }
98 }
99 }
100
101 #[inline]
102 fn bitand(self, rhs: Self) -> Self::Output {
103 pick! {
104 if #[cfg(target_feature="avx2")] {
105 Self { avx2: bitand_m256i(self.avx2, rhs.avx2) }
106 } else {
107 Self {
108 a : self.a.bitand(rhs.a),
109 b : self.b.bitand(rhs.b),
110 }
111 }
112 }
113 }
114
115 #[inline]
116 fn bitor(self, rhs: Self) -> Self::Output {
117 pick! {
118 if #[cfg(target_feature="avx2")] {
119 Self { avx2: bitor_m256i(self.avx2, rhs.avx2) }
120 } else {
121 Self {
122 a : self.a.bitor(rhs.a),
123 b : self.b.bitor(rhs.b),
124 }
125 }
126 }
127 }
128
129 #[inline]
130 fn bitxor(self, rhs: Self) -> Self::Output {
131 pick! {
132 if #[cfg(target_feature="avx2")] {
133 Self { avx2: bitxor_m256i(self.avx2, rhs.avx2) }
134 } else {
135 Self {
136 a : self.a.bitxor(rhs.a),
137 b : self.b.bitxor(rhs.b),
138 }
139 }
140 }
141 }
142
143 #[inline]
144 fn simd_eq(self, rhs: Self) -> Self::Output {
145 pick! {
146 if #[cfg(target_feature="avx2")] {
147 Self { avx2: cmp_eq_mask_i64_m256i(self.avx2, rhs.avx2) }
148 } else {
149 Self {
150 a : self.a.simd_eq(rhs.a),
151 b : self.b.simd_eq(rhs.b),
152 }
153 }
154 }
155 }
156
157 #[inline]
158 fn simd_ne(self, rhs: Self) -> Self::Output {
159 pick! {
160 if #[cfg(target_feature="avx2")] {
161 !self.simd_eq(rhs)
162 } else {
163 Self {
164 a : self.a.simd_ne(rhs.a),
165 b : self.b.simd_ne(rhs.b),
166 }
167 }
168 }
169 }
170
171 #[inline]
172 fn simd_lt(self, rhs: Self) -> Self::Output {
173 rhs.simd_gt(self)
175 }
176
177 #[inline]
178 fn simd_gt(self, rhs: Self) -> Self::Output {
179 pick! {
180 if #[cfg(target_feature="avx2")] {
181 let highbit = u64x4::splat(1 << 63);
183 Self { avx2: cmp_gt_mask_i64_m256i((self ^ highbit).avx2, (rhs ^ highbit).avx2) }
184 } else {
185 Self {
186 a : self.a.simd_gt(rhs.a),
187 b : self.b.simd_gt(rhs.b),
188 }
189 }
190 }
191 }
192
193 #[inline]
194 fn simd_le(self, rhs: Self) -> Self::Output {
195 pick! {
196 if #[cfg(target_feature="avx2")] {
197 !self.simd_gt(rhs)
198 } else {
199 Self {
200 a : self.a.simd_le(rhs.a),
201 b : self.b.simd_le(rhs.b),
202 }
203 }
204 }
205 }
206
207 #[inline]
208 fn simd_ge(self, rhs: Self) -> Self::Output {
209 pick! {
210 if #[cfg(target_feature="avx2")] {
211 !self.simd_lt(rhs)
212 } else {
213 Self {
214 a : self.a.simd_ge(rhs.a),
215 b : self.b.simd_ge(rhs.b),
216 }
217 }
218 }
219 }
220
221 #[inline]
222 pub fn reduce_add(self) -> u64 {
223 pick! {
224 if #[cfg(all(target_arch="x86_64", target_feature="avx2"))] {
225 let zwxx = shuffle_ai_i64_all_m256i::<0b00_00_11_10>(self.avx2);
226 let xz_yw = add_i64_m256i(zwxx, self.avx2);
227 let yw_xz = shuffle_ai_i64_all_m256i::<0b00_00_00_01>(xz_yw);
228 let sum = add_i64_m256i(xz_yw, yw_xz);
229 extract_i64_from_m256i::<0>(sum).cast_unsigned()
230 } else {
231 let array: [u64; 4] = cast(self);
232 array[0]
233 .wrapping_add(array[1])
234 .wrapping_add(array[2])
235 .wrapping_add(array[3])
236 }
237 }
238 }
239
240 #[inline]
241 pub fn reduce_mul(self) -> u64 {
242 let array: [u64; 4] = cast(self);
243 array[0]
244 .wrapping_mul(array[1])
245 .wrapping_mul(array[2])
246 .wrapping_mul(array[3])
247 }
248
249 #[inline]
250 pub fn bitselect(self, if_one: Self, if_zero: Self) -> Self {
251 pick! {
252 if #[cfg(target_feature="avx2")] {
253 Self {
254 avx2: bitor_m256i(
255 bitand_m256i(if_one.avx2, self.avx2),
256 bitandnot_m256i(self.avx2, if_zero.avx2),
257 ),
258 }
259 } else {
260 Self {
261 a: self.a.bitselect(if_one.a, if_zero.a),
262 b: self.b.bitselect(if_one.b, if_zero.b),
263 }
264 }
265 }
266 }
267
268 #[inline]
269 fn select(self, if_true: Self, if_false: Self) -> Self {
270 pick! {
271 if #[cfg(target_feature="avx2")] {
272 Self { avx2: blend_varying_i8_m256i(if_false.avx2,if_true.avx2,self.avx2) }
273 } else {
274 Self {
275 a : self.a.select(if_true.a, if_false.a),
276 b : self.b.select(if_true.b, if_false.b),
277 }
278 }
279 }
280 }
281
282 #[inline]
283 pub fn to_bitmask(self) -> u32 {
284 pick! {
285 if #[cfg(target_feature="avx2")] {
286 move_mask_m256d(cast(self.avx2)) as u32
288 } else {
289 self.a.to_bitmask() | (self.b.to_bitmask() << 2)
290 }
291 }
292 }
293
294 #[inline]
295 pub fn any(self) -> bool {
296 pick! {
297 if #[cfg(target_feature="avx2")] {
298 move_mask_m256d(cast(self.avx2)) != 0
299 } else {
300 (self.a | self.b).any()
301 }
302 }
303 }
304
305 #[inline]
306 pub fn all(self) -> bool {
307 pick! {
308 if #[cfg(target_feature="avx2")] {
309 move_mask_m256d(cast(self.avx2)) == 0b1111
310 } else {
311 (self.a & self.b).all()
312 }
313 }
314 }
315
316 #[inline]
317 pub fn shuffle(self, indices: u64x4) -> Self {
318 pick! {
319 if #[cfg(all(target_feature="avx512f", target_feature="avx512vl"))] {
320 #[cfg(target_arch = "x86")]
321 use core::arch::x86::_mm256_permutexvar_epi64;
322 #[cfg(target_arch = "x86_64")]
323 use core::arch::x86_64::_mm256_permutexvar_epi64;
324 Self { avx2: m256i(unsafe { _mm256_permutexvar_epi64(indices.avx2.0, self.avx2.0) }) }
326 } else if #[cfg(any(
327 target_feature = "ssse3",
328 all(target_arch = "aarch64", target_feature = "neon"),
329 target_feature = "simd128",
330 ))] {
331 let self_bytes = cast::<u64x4, u8x32>(self);
332 let byte_indices = indices.to_byte_indices();
333
334 cast::<u8x32, u64x4>(self_bytes.shuffle(byte_indices))
335 } else {
336 let self_array = self.to_array();
337 let indices_array = indices.to_array();
338
339 let mut result = [0; 4];
340 for i in 0..4 {
341 let index = indices_array[i] as usize;
342 if index < 4 {
343 result[i] = self_array[index];
344 }
345 }
346
347 Self::new(result)
348 }
349 }
350 }
351
352 #[inline]
353 pub fn shuffle_zeroing(self, indices: u64x4) -> Self {
354 pick! {
355 if #[cfg(any(
356 target_feature = "ssse3",
357 all(target_arch = "aarch64", target_feature = "neon"),
358 target_feature = "simd128",
359 ))] {
360 self.shuffle(indices) & indices.simd_lt(4)
363 } else {
364 self.shuffle(indices)
366 }
367 }
368 }
369
370 #[inline]
371 pub fn shuffle_wrapping(self, indices: u64x4) -> Self {
372 pick! {
373 if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
374 self.shuffle(indices)
376 } else {
377 self.shuffle(indices & 3)
378 }
379 }
380 }
381
382 #[inline]
383 fn shuffle(self: [u64x4; 2], indices: u64x4) -> u64x4 {
384 pick! {
385 if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
386 u64x4 { avx2: shuffle_abv_i64_all_m256i(self[0].avx2, indices.avx2, self[1].avx2) }
387 } else {
388 let self_bytes = cast::<[u64x4; 2], [u8x32; 2]>(self);
389 let byte_indices = indices.to_byte_indices();
390
391 cast::<u8x32, u64x4>(self_bytes.shuffle(byte_indices))
392 }
393 }
394 }
395
396 #[inline]
397 fn shuffle_zeroing(self: [u64x4; 2], indices: u64x4) -> u64x4 {
398 self.shuffle(indices) & indices.simd_lt(8)
401 }
402
403 #[inline]
404 fn shuffle_wrapping(self: [u64x4; 2], indices: u64x4) -> u64x4 {
405 pick! {
406 if #[cfg(all(target_feature = "avx512f", target_feature = "avx512vl"))] {
407 self.shuffle(indices)
409 } else {
410 self.shuffle(indices & 7)
411 }
412 }
413 }
414
415 #[inline]
416 fn shuffle(self: [u64x4; 3], indices: u64x4) -> u64x4 {
417 let self_bytes = cast::<[u64x4; 3], [u8x32; 3]>(self);
418 let byte_indices = indices.to_byte_indices();
419
420 cast::<u8x32, u64x4>(self_bytes.shuffle(byte_indices))
421 }
422
423 #[inline]
424 fn shuffle_zeroing(self: [u64x4; 3], indices: u64x4) -> u64x4 {
425 self.shuffle(indices) & indices.simd_lt(12)
428 }
429
430 #[inline]
431 fn shuffle_wrapping(self: [u64x4; 3], indices: u64x4) -> u64x4 {
432 self.shuffle(indices % 12)
433 }
434
435 #[inline]
436 fn shuffle(self: [u64x4; 4], indices: u64x4) -> u64x4 {
437 let self_bytes = cast::<[u64x4; 4], [u8x32; 4]>(self);
438 let byte_indices = indices.to_byte_indices();
439
440 cast::<u8x32, u64x4>(self_bytes.shuffle(byte_indices))
441 }
442
443 #[inline]
444 fn shuffle_zeroing(self: [u64x4; 4], indices: u64x4) -> u64x4 {
445 self.shuffle(indices) & indices.simd_lt(16)
448 }
449
450 #[inline]
451 fn shuffle_wrapping(self: [u64x4; 4], indices: u64x4) -> u64x4 {
452 self.shuffle(indices & 15)
453 }
454
455 #[inline]
458 pub fn transpose(data: [Self; 4]) -> [Self; 4] {
459 pick! {
460 if #[cfg(target_feature="avx2")] {
461 let a = data[0].cast_signed().unpack_lo(data[2].cast_signed());
464 let b = data[1].cast_signed().unpack_lo(data[3].cast_signed());
465 let c = data[0].cast_signed().unpack_hi(data[2].cast_signed());
466 let d = data[1].cast_signed().unpack_hi(data[3].cast_signed());
467 [
468 a.unpack_lo(b).cast_unsigned(),
469 a.unpack_hi(b).cast_unsigned(),
470 c.unpack_lo(d).cast_unsigned(),
471 c.unpack_hi(d).cast_unsigned(),
472 ]
473 } else {
474 #[inline(always)]
475 fn transpose_column(data: &[u64x4; 4], index: usize) -> u64x4 {
476 u64x4::new([
477 data[0].as_array()[index],
478 data[1].as_array()[index],
479 data[2].as_array()[index],
480 data[3].as_array()[index],
481 ])
482 }
483
484 [
485 transpose_column(&data, 0),
486 transpose_column(&data, 1),
487 transpose_column(&data, 2),
488 transpose_column(&data, 3),
489 ]
490 }
491 }
492 }
493
494 #[inline]
495 fn shl(self, rhs: Self) -> Self::Output {
496 pick! {
497 if #[cfg(target_feature="avx2")] {
498 let shift_by = rhs & Self::splat(63);
500 Self { avx2: shl_each_u64_m256i(self.avx2, shift_by.avx2) }
501 } else {
502 Self {
503 a : self.a.shl(rhs.a),
504 b : self.b.shl(rhs.b),
505 }
506 }
507 }
508 }
509
510 #[inline]
511 fn shl(self, rhs: u32) -> Self::Output {
512 pick! {
513 if #[cfg(target_feature="avx2")] {
514 #[expect(clippy::suspicious_arithmetic_impl)]
516 let shift = cast([rhs as u64 & 63, 0]);
517 Self { avx2: shl_all_u64_m256i(self.avx2, shift) }
518 } else {
519 Self {
520 a : self.a.shl(rhs),
521 b : self.b.shl(rhs),
522 }
523 }
524 }
525 }
526
527 #[inline]
528 fn shr(self, rhs: Self) -> Self::Output {
529 pick! {
530 if #[cfg(target_feature="avx2")] {
531 let shift_by = rhs & Self::splat(63);
533 Self { avx2: shr_each_u64_m256i(self.avx2, shift_by.avx2) }
534 } else {
535 Self {
536 a : self.a.shr(rhs.a),
537 b : self.b.shr(rhs.b),
538 }
539 }
540 }
541 }
542
543 #[inline]
544 fn shr(self, rhs: u32) -> Self::Output {
545 pick! {
546 if #[cfg(target_feature="avx2")] {
547 #[expect(clippy::suspicious_arithmetic_impl)]
549 let shift = cast([rhs as u64 & 63, 0]);
550 Self { avx2: shr_all_u64_m256i(self.avx2, shift) }
551 } else {
552 Self {
553 a : self.a.shr(rhs),
554 b : self.b.shr(rhs),
555 }
556 }
557 }
558 }
559
560 #[inline]
561 pub fn max(self, rhs: Self) -> Self {
562 self.simd_gt(rhs).select(self, rhs)
563 }
564
565 #[inline]
566 pub fn min(self, rhs: Self) -> Self {
567 self.simd_lt(rhs).select(self, rhs)
568 }
569
570 #[inline]
571 pub fn reduce_max(self) -> u64 {
572 let array: [u64; 4] = cast(self);
573 array[0].max(array[1]).max(array[2]).max(array[3])
574 }
575
576 #[inline]
577 pub fn reduce_min(self) -> u64 {
578 let array: [u64; 4] = cast(self);
579 array[0].min(array[1]).min(array[2]).min(array[3])
580 }
581
582 #[inline]
583 pub fn unbounded_shl(self, rhs: Self) -> Self {
584 pick! {
585 if #[cfg(target_feature="avx2")] {
586 Self { avx2: shl_each_u64_m256i(self.avx2, rhs.avx2) }
587 } else {
588 Self {
589 a: self.a.unbounded_shl(rhs.a),
590 b: self.b.unbounded_shl(rhs.b),
591 }
592 }
593 }
594 }
595
596 #[inline]
597 pub fn unbounded_shl_scalar(self, rhs: u32) -> Self {
598 pick! {
599 if #[cfg(target_feature="avx2")] {
600 Self { avx2: shl_all_u64_m256i(self.avx2, cast([rhs as u64, 0])) }
601 } else {
602 Self {
603 a: self.a.unbounded_shl_scalar(rhs),
604 b: self.b.unbounded_shl_scalar(rhs),
605 }
606 }
607 }
608 }
609
610 #[inline]
611 pub fn unbounded_shr(self, rhs: Self) -> Self {
612 pick! {
613 if #[cfg(target_feature="avx2")] {
614 Self { avx2: shr_each_u64_m256i(self.avx2, rhs.avx2) }
615 } else {
616 Self {
617 a: self.a.unbounded_shr(rhs.a),
618 b: self.b.unbounded_shr(rhs.b),
619 }
620 }
621 }
622 }
623
624 #[inline]
625 pub fn unbounded_shr_scalar(self, rhs: u32) -> Self {
626 pick! {
627 if #[cfg(target_feature="avx2")] {
628 Self { avx2: shr_all_u64_m256i(self.avx2, cast([rhs as u64, 0])) }
629 } else {
630 Self {
631 a: self.a.unbounded_shr_scalar(rhs),
632 b: self.b.unbounded_shr_scalar(rhs),
633 }
634 }
635 }
636 }
637
638 #[inline]
639 pub fn saturating_add(self, rhs: Self) -> Self {
640 pick! {
641 if #[cfg(target_feature="avx2")] {
642 let result = self + rhs;
643 let overflow = result.simd_lt(self);
644 result | overflow
646 } else {
647 Self {
648 a: self.a.saturating_add(rhs.a),
649 b: self.b.saturating_add(rhs.b),
650 }
651 }
652 }
653 }
654
655 #[inline]
656 pub fn saturating_sub(self, rhs: Self) -> Self {
657 pick! {
658 if #[cfg(target_feature="avx2")] {
659 let result = self - rhs;
660 let no_overflow = result.simd_le(self);
661 result & no_overflow
663 } else {
664 Self {
665 a: self.a.saturating_sub(rhs.a),
666 b: self.b.saturating_sub(rhs.b),
667 }
668 }
669 }
670 }
671
672 #[inline]
673 pub fn overflowing_mul(self, rhs: Self) -> (Self, Self) {
674 let self_array = self.to_array();
679 let rhs_array = rhs.to_array();
680
681 let result = [
682 self_array[0].overflowing_mul(rhs_array[0]),
683 self_array[1].overflowing_mul(rhs_array[1]),
684 self_array[2].overflowing_mul(rhs_array[2]),
685 self_array[3].overflowing_mul(rhs_array[3]),
686 ];
687 (
688 Self::new([result[0].0, result[1].0, result[2].0, result[3].0]),
689 Self::new([
690 -(result[0].1 as i64) as u64,
691 -(result[1].1 as i64) as u64,
692 -(result[2].1 as i64) as u64,
693 -(result[3].1 as i64) as u64,
694 ]),
695 )
696 }
697
698 optional_fn_widening_mul {
699 }
701
702 #[inline]
703 pub fn mul_keep_low_high(self, rhs: Self) -> (Self, Self) {
704 let self_array = self.to_array();
708 let rhs_array = rhs.to_array();
709
710 let widening_mul = [
711 (self_array[0] as u128).wrapping_mul(rhs_array[0] as u128),
712 (self_array[1] as u128).wrapping_mul(rhs_array[1] as u128),
713 (self_array[2] as u128).wrapping_mul(rhs_array[2] as u128),
714 (self_array[3] as u128).wrapping_mul(rhs_array[3] as u128),
715 ];
716
717 (
718 Self::new([
719 widening_mul[0] as u64,
720 widening_mul[1] as u64,
721 widening_mul[2] as u64,
722 widening_mul[3] as u64,
723 ]),
724 Self::new([
725 (widening_mul[0] >> 64) as u64,
726 (widening_mul[1] >> 64) as u64,
727 (widening_mul[2] >> 64) as u64,
728 (widening_mul[3] >> 64) as u64,
729 ]),
730 )
731 }
732
733 #[inline]
734 pub fn mul_keep_high(self, rhs: Self) -> Self {
735 pick! {
736 if #[cfg(target_feature="avx2")] {
737 let arr1: [u64; 4] = cast(self);
738 let arr2: [u64; 4] = cast(rhs);
739 cast([
740 (arr1[0] as u128 * arr2[0] as u128 >> 64) as u64,
741 (arr1[1] as u128 * arr2[1] as u128 >> 64) as u64,
742 (arr1[2] as u128 * arr2[2] as u128 >> 64) as u64,
743 (arr1[3] as u128 * arr2[3] as u128 >> 64) as u64,
744 ])
745 } else {
746 Self {
747 a: self.a.mul_keep_high(rhs.a),
748 b: self.b.mul_keep_high(rhs.b),
749 }
750 }
751 }
752 }
753
754 optional_fn_deserialize {}
755}
756
757impl u64x4 {
760 #[allow(dead_code)]
769 #[inline]
770 fn to_byte_indices(self) -> u8x32 {
771 let base = self.unbounded_shl_scalar(3);
773 let base = base | base.unbounded_shl_scalar(8);
774 let base = base | base.unbounded_shl_scalar(16);
775 let base = base | base.unbounded_shl_scalar(32);
776
777 const WITHIN_LANE: u64x4 =
781 u64x4::splat(u64::from_ne_bytes([0, 1, 2, 3, 4, 5, 6, 7]));
782
783 cast::<u64x4, u8x32>(base | WITHIN_LANE)
784 }
785
786 #[inline]
789 #[must_use]
790 pub fn unpack_lo(self, b: Self) -> Self {
791 pick! {
792 if #[cfg(target_feature="avx2")] {
793 let [aa, _]: [u64x2; 2] = cast(self);
796 let [ba, _]: [u64x2; 2] = cast(b);
797 cast([aa.unpack_lo(ba), aa.unpack_hi(ba)])
798 } else {
799 Self { a: self.a.unpack_lo(b.a), b: self.a.unpack_hi(b.a) }
800 }
801 }
802 }
803
804 #[inline]
807 #[must_use]
808 pub fn unpack_hi(self, b: Self) -> Self {
809 pick! {
810 if #[cfg(target_feature="avx2")] {
811 let [_, ab]: [u64x2; 2] = cast(self);
814 let [_, bb]: [u64x2; 2] = cast(b);
815 cast([ab.unpack_lo(bb), ab.unpack_hi(bb)])
816 } else {
817 Self { a: self.b.unpack_lo(b.b), b: self.b.unpack_hi(b.b) }
818 }
819 }
820 }
821
822 #[inline]
828 #[must_use]
829 pub(crate) fn mul_masked<const W: u32>(a: Self, b: Self) -> Self {
830 pick! {
831 if #[cfg(target_feature="avx2")] {
832 let (a, b) = if W == 32 {
836 (a, b)
837 } else {
838 let mask = Self::splat(add_mul_operand_mask_u64::<W>());
839 (a & mask, b & mask)
840 };
841
842 Self { avx2: mul_u64_low_bits_m256i(a.avx2, b.avx2) }
843 } else {
844 let mask = add_mul_operand_mask_u64::<W>();
845 let a = a.to_array();
846 let b = b.to_array();
847 Self::new(core::array::from_fn(|i| (a[i] & mask) * (b[i] & mask)))
848 }
849 }
850 }
851
852 #[inline]
855 #[must_use]
856 pub fn add_mul_lo<const W: u32>(self, a: Self, b: Self) -> Self {
857 pick! {
858 if #[cfg(all(target_feature="avx512ifma", target_feature="avx512vl"))] {
859 if W == 52 {
861 return Self {
862 avx2: add_mul_low_u52_m256i(self.avx2, a.avx2, b.avx2),
863 };
864 }
865 }
866 }
867
868 if W <= 32 {
871 let mask = Self::splat(add_mul_operand_mask_u64::<W>());
872 return self + (Self::mul_masked::<W>(a, b) & mask);
873 }
874
875 let acc = self.to_array();
876 let a = a.to_array();
877 let b = b.to_array();
878 Self::new(core::array::from_fn(|i| {
879 add_mul_lo_lane_u64::<W>(acc[i], a[i], b[i])
880 }))
881 }
882
883 #[inline]
886 #[must_use]
887 pub fn add_mul_hi<const W: u32>(self, a: Self, b: Self) -> Self {
888 pick! {
889 if #[cfg(all(target_feature="avx512ifma", target_feature="avx512vl"))] {
890 if W == 52 {
892 return Self {
893 avx2: add_mul_high_u52_m256i(self.avx2, a.avx2, b.avx2),
894 };
895 }
896 }
897 }
898
899 if W <= 32 {
902 return self + (Self::mul_masked::<W>(a, b) >> W);
903 }
904
905 let acc = self.to_array();
906 let a = a.to_array();
907 let b = b.to_array();
908 Self::new(core::array::from_fn(|i| {
909 add_mul_hi_lane_u64::<W>(acc[i], a[i], b[i])
910 }))
911 }
912}