1macro_rules! impl_word {
11 ($name:ident, $prim:ty, $bits:literal) => {
12 #[doc = concat!("A ", $bits, "-bit wrapping integer: the ring ℤ/2^", $bits, "ℤ, where every operation is total and wraps modulo 2^", $bits, ".")]
13 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
14 #[repr(transparent)]
15 pub struct $name(pub $prim);
16
17 impl $name {
18 pub const BITS: u32 = <$prim>::BITS;
20 pub const ZERO: Self = Self(0);
22 pub const ONE: Self = Self(1);
24 pub const MAX: Self = Self(<$prim>::MAX);
26
27 #[inline]
29 pub const fn get(self) -> $prim {
30 self.0
31 }
32
33 #[inline]
35 pub const fn add(self, o: Self) -> Self {
36 Self(self.0.wrapping_add(o.0))
37 }
38
39 #[inline]
41 pub const fn sub(self, o: Self) -> Self {
42 Self(self.0.wrapping_sub(o.0))
43 }
44
45 #[inline]
47 pub const fn mul(self, o: Self) -> Self {
48 Self(self.0.wrapping_mul(o.0))
49 }
50
51 #[inline]
53 pub const fn bitand(self, o: Self) -> Self {
54 Self(self.0 & o.0)
55 }
56
57 #[inline]
59 pub const fn bitor(self, o: Self) -> Self {
60 Self(self.0 | o.0)
61 }
62
63 #[inline]
65 pub const fn bitxor(self, o: Self) -> Self {
66 Self(self.0 ^ o.0)
67 }
68
69 #[inline]
71 pub const fn not(self) -> Self {
72 Self(!self.0)
73 }
74
75 #[inline]
78 pub const fn shl(self, n: u32) -> Self {
79 Self(self.0.wrapping_shl(n))
80 }
81
82 #[inline]
84 pub const fn shr(self, n: u32) -> Self {
85 Self(self.0.wrapping_shr(n))
86 }
87
88 #[inline]
90 pub const fn rotl(self, n: u32) -> Self {
91 Self(self.0.rotate_left(n))
92 }
93
94 #[inline]
96 pub const fn rotr(self, n: u32) -> Self {
97 Self(self.0.rotate_right(n))
98 }
99 }
100
101 impl ::core::ops::Add for $name {
104 type Output = Self;
105 #[inline]
106 fn add(self, o: Self) -> Self { Self(self.0.wrapping_add(o.0)) }
107 }
108 impl ::core::ops::Sub for $name {
109 type Output = Self;
110 #[inline]
111 fn sub(self, o: Self) -> Self { Self(self.0.wrapping_sub(o.0)) }
112 }
113 impl ::core::ops::Mul for $name {
114 type Output = Self;
115 #[inline]
116 fn mul(self, o: Self) -> Self { Self(self.0.wrapping_mul(o.0)) }
117 }
118 impl ::core::ops::BitAnd for $name {
119 type Output = Self;
120 #[inline]
121 fn bitand(self, o: Self) -> Self { Self(self.0 & o.0) }
122 }
123 impl ::core::ops::BitOr for $name {
124 type Output = Self;
125 #[inline]
126 fn bitor(self, o: Self) -> Self { Self(self.0 | o.0) }
127 }
128 impl ::core::ops::BitXor for $name {
129 type Output = Self;
130 #[inline]
131 fn bitxor(self, o: Self) -> Self { Self(self.0 ^ o.0) }
132 }
133 impl ::core::ops::Not for $name {
134 type Output = Self;
135 #[inline]
136 fn not(self) -> Self { Self(!self.0) }
137 }
138 impl ::core::ops::Div for $name {
141 type Output = Self;
142 #[inline]
143 fn div(self, o: Self) -> Self { Self(self.0 / o.0) }
144 }
145 impl ::core::ops::Rem for $name {
146 type Output = Self;
147 #[inline]
148 fn rem(self, o: Self) -> Self { Self(self.0 % o.0) }
149 }
150 impl ::core::ops::Shl<u32> for $name {
151 type Output = Self;
152 #[inline]
153 fn shl(self, n: u32) -> Self { Self(self.0.wrapping_shl(n)) }
154 }
155 impl ::core::ops::Shr<u32> for $name {
156 type Output = Self;
157 #[inline]
158 fn shr(self, n: u32) -> Self { Self(self.0.wrapping_shr(n)) }
159 }
160 impl ::core::fmt::Display for $name {
163 #[inline]
164 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
165 ::core::write!(f, "{}", self.0)
166 }
167 }
168 };
169}
170
171impl_word!(Word8, u8, "8");
172impl_word!(Word16, u16, "16");
173impl_word!(Word32, u32, "32");
174impl_word!(Word64, u64, "64");
175
176#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
181pub enum WordVal {
182 W32(Word32),
184 W64(Word64),
186}
187
188impl WordVal {
189 #[inline]
191 pub const fn width(self) -> u32 {
192 match self {
193 WordVal::W32(_) => 32,
194 WordVal::W64(_) => 64,
195 }
196 }
197
198 #[inline]
201 pub const fn to_u64(self) -> u64 {
202 match self {
203 WordVal::W32(w) => w.0 as u64,
204 WordVal::W64(w) => w.0,
205 }
206 }
207
208 #[inline]
210 pub const fn from_u64(width: u32, bits: u64) -> Option<Self> {
211 match width {
212 32 => Some(WordVal::W32(Word32(bits as u32))),
213 64 => Some(WordVal::W64(Word64(bits))),
214 _ => None,
215 }
216 }
217
218 #[inline]
219 fn zip(
220 self,
221 o: Self,
222 f32: impl FnOnce(Word32, Word32) -> Word32,
223 f64: impl FnOnce(Word64, Word64) -> Word64,
224 ) -> Option<Self> {
225 match (self, o) {
226 (WordVal::W32(a), WordVal::W32(b)) => Some(WordVal::W32(f32(a, b))),
227 (WordVal::W64(a), WordVal::W64(b)) => Some(WordVal::W64(f64(a, b))),
228 _ => None,
229 }
230 }
231
232 #[inline]
234 pub fn add(self, o: Self) -> Option<Self> {
235 self.zip(o, Word32::add, Word64::add)
236 }
237 #[inline]
239 pub fn sub(self, o: Self) -> Option<Self> {
240 self.zip(o, Word32::sub, Word64::sub)
241 }
242 #[inline]
244 pub fn mul(self, o: Self) -> Option<Self> {
245 self.zip(o, Word32::mul, Word64::mul)
246 }
247 #[inline]
249 pub fn bitand(self, o: Self) -> Option<Self> {
250 self.zip(o, Word32::bitand, Word64::bitand)
251 }
252 #[inline]
254 pub fn bitor(self, o: Self) -> Option<Self> {
255 self.zip(o, Word32::bitor, Word64::bitor)
256 }
257 #[inline]
259 pub fn bitxor(self, o: Self) -> Option<Self> {
260 self.zip(o, Word32::bitxor, Word64::bitxor)
261 }
262
263 #[inline]
265 pub const fn not(self) -> Self {
266 match self {
267 WordVal::W32(w) => WordVal::W32(w.not()),
268 WordVal::W64(w) => WordVal::W64(w.not()),
269 }
270 }
271 #[inline]
273 pub const fn shl(self, n: u32) -> Self {
274 match self {
275 WordVal::W32(w) => WordVal::W32(w.shl(n)),
276 WordVal::W64(w) => WordVal::W64(w.shl(n)),
277 }
278 }
279 #[inline]
281 pub const fn shr(self, n: u32) -> Self {
282 match self {
283 WordVal::W32(w) => WordVal::W32(w.shr(n)),
284 WordVal::W64(w) => WordVal::W64(w.shr(n)),
285 }
286 }
287 #[inline]
289 pub const fn rotl(self, n: u32) -> Self {
290 match self {
291 WordVal::W32(w) => WordVal::W32(w.rotl(n)),
292 WordVal::W64(w) => WordVal::W64(w.rotl(n)),
293 }
294 }
295 #[inline]
297 pub const fn rotr(self, n: u32) -> Self {
298 match self {
299 WordVal::W32(w) => WordVal::W32(w.rotr(n)),
300 WordVal::W64(w) => WordVal::W64(w.rotr(n)),
301 }
302 }
303}
304
305impl std::fmt::Display for WordVal {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 write!(f, "{}", self.to_u64())
310 }
311}
312
313#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
324#[repr(C, align(32))]
325pub struct Lanes8Word32(pub [u32; 8]);
326
327impl Lanes8Word32 {
328 pub const LANES: usize = 8;
330
331 #[inline]
333 pub const fn splat(x: u32) -> Self {
334 Self([x; 8])
335 }
336
337 #[inline]
339 pub fn from_words(s: &[Word32]) -> Self {
340 let mut a = [0u32; 8];
341 for (i, w) in s.iter().take(8).enumerate() {
342 a[i] = w.0;
343 }
344 Self(a)
345 }
346
347 #[inline]
349 pub fn to_words(self) -> [Word32; 8] {
350 self.0.map(Word32)
351 }
352
353 #[inline]
355 pub fn lane(self, i: usize) -> Word32 {
356 Word32(self.0[i])
357 }
358
359 #[inline(always)]
363 pub fn bitxor(self, o: Self) -> Self {
364 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
365 unsafe {
366 use std::arch::x86_64::*;
367 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
368 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
369 let mut r = [0u32; 8];
370 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_xor_si256(a, b));
371 return Self(r);
372 }
373 #[allow(unreachable_code)]
374 {
375 let mut r = [0u32; 8];
376 for i in 0..8 {
377 r[i] = self.0[i] ^ o.0[i];
378 }
379 Self(r)
380 }
381 }
382
383 #[cfg(test)]
387 #[inline]
388 fn bitxor_scalar(self, o: Self) -> Self {
389 Self(core::array::from_fn(|i| self.0[i] ^ o.0[i]))
390 }
391 #[cfg(test)]
392 #[inline]
393 fn add_scalar(self, o: Self) -> Self {
394 Self(core::array::from_fn(|i| self.0[i].wrapping_add(o.0[i])))
395 }
396 #[cfg(test)]
397 #[inline]
398 fn sub_scalar(self, o: Self) -> Self {
399 Self(core::array::from_fn(|i| self.0[i].wrapping_sub(o.0[i])))
400 }
401 #[cfg(test)]
402 #[inline]
403 fn rotl_scalar(self, n: u32) -> Self {
404 Self(core::array::from_fn(|i| self.0[i].rotate_left(n)))
405 }
406 #[cfg(test)]
407 #[inline]
408 fn montmul32_scalar(self, b: Self, q: Self, qinv: Self) -> Self {
409 Self(core::array::from_fn(|i| {
410 let p = (self.0[i] as i32 as i64) * (b.0[i] as i32 as i64);
411 let t = (p as i32).wrapping_mul(qinv.0[i] as i32) as i64;
412 (((p - t * (q.0[i] as i32 as i64)) >> 32) as i32) as u32
413 }))
414 }
415
416 #[inline]
419 pub fn bitand(self, o: Self) -> Self {
420 let mut r = [0u32; 8];
421 for i in 0..8 {
422 r[i] = self.0[i] & o.0[i];
423 }
424 Self(r)
425 }
426
427 #[inline]
429 pub fn bitor(self, o: Self) -> Self {
430 let mut r = [0u32; 8];
431 for i in 0..8 {
432 r[i] = self.0[i] | o.0[i];
433 }
434 Self(r)
435 }
436
437 #[inline]
439 pub fn not(self) -> Self {
440 let mut r = [0u32; 8];
441 for i in 0..8 {
442 r[i] = !self.0[i];
443 }
444 Self(r)
445 }
446
447 #[inline(always)]
449 pub fn add(self, o: Self) -> Self {
450 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
451 unsafe {
452 use std::arch::x86_64::*;
453 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
454 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
455 let mut r = [0u32; 8];
456 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_add_epi32(a, b));
457 return Self(r);
458 }
459 #[allow(unreachable_code)]
460 {
461 let mut r = [0u32; 8];
462 for i in 0..8 {
463 r[i] = self.0[i].wrapping_add(o.0[i]);
464 }
465 Self(r)
466 }
467 }
468
469 #[inline(always)]
472 pub fn rotl(self, n: u32) -> Self {
473 let n = n % 32;
474 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
475 unsafe {
476 use std::arch::x86_64::*;
477 let x = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
478 let l = _mm256_sll_epi32(x, _mm_cvtsi32_si128(n as i32));
479 let r_sh = _mm256_srl_epi32(x, _mm_cvtsi32_si128((32 - n) as i32));
480 let mut r = [0u32; 8];
481 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_or_si256(l, r_sh));
482 return Self(r);
483 }
484 #[allow(unreachable_code)]
485 {
486 let mut r = [0u32; 8];
487 for i in 0..8 {
488 r[i] = self.0[i].rotate_left(n);
489 }
490 Self(r)
491 }
492 }
493
494 #[inline(always)]
496 pub fn sub(self, o: Self) -> Self {
497 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
498 unsafe {
499 use std::arch::x86_64::*;
500 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
501 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
502 let mut r = [0u32; 8];
503 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_sub_epi32(a, b));
504 return Self(r);
505 }
506 #[allow(unreachable_code)]
507 {
508 let mut r = [0u32; 8];
509 for i in 0..8 {
510 r[i] = self.0[i].wrapping_sub(o.0[i]);
511 }
512 Self(r)
513 }
514 }
515
516 #[inline(always)]
521 pub fn montmul32(self, b: Self, q: Self, qinv: Self) -> Self {
522 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
523 unsafe {
524 use std::arch::x86_64::*;
525 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
526 let bb = _mm256_loadu_si256(b.0.as_ptr() as *const __m256i);
527 let qv = _mm256_loadu_si256(q.0.as_ptr() as *const __m256i);
528 let qiv = _mm256_loadu_si256(qinv.0.as_ptr() as *const __m256i);
529 let pe = _mm256_mul_epi32(a, bb);
531 let po = _mm256_mul_epi32(_mm256_srli_epi64(a, 32), _mm256_srli_epi64(bb, 32));
532 let te = _mm256_mul_epi32(pe, qiv);
534 let re = _mm256_sub_epi64(pe, _mm256_mul_epi32(te, qv));
535 let to = _mm256_mul_epi32(po, qiv);
536 let ro = _mm256_sub_epi64(po, _mm256_mul_epi32(to, qv));
537 let res = _mm256_blend_epi32(_mm256_srli_epi64(re, 32), ro, 0xAA);
538 let mut r = [0u32; 8];
539 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, res);
540 return Self(r);
541 }
542 #[allow(unreachable_code)]
543 {
544 let qq = q.0[0] as i32 as i64;
545 let qi = qinv.0[0] as i32;
546 let mut r = [0u32; 8];
547 for i in 0..8 {
548 let p = (self.0[i] as i32 as i64) * (b.0[i] as i32 as i64);
549 let t = (p as i32).wrapping_mul(qi) as i64;
550 r[i] = (((p - t * qq) >> 32) as i32) as u32;
551 }
552 Self(r)
553 }
554 }
555
556 #[inline(always)]
560 pub fn ntt_bcast_lo(self, h: usize) -> Self {
561 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
562 unsafe {
563 use std::arch::x86_64::*;
564 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
565 let v = match h {
566 4 => _mm256_permute2x128_si256::<0x00>(a, a),
567 2 => _mm256_shuffle_epi32::<0x44>(a),
568 1 => _mm256_shuffle_epi32::<0xA0>(a),
569 _ => unreachable!("i32 within-vector NTT stride is 4/2/1"),
570 };
571 let mut r = [0u32; 8];
572 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
573 return Self(r);
574 }
575 #[allow(unreachable_code)]
576 Self(core::array::from_fn(|i| self.0[(i / (2 * h)) * (2 * h) + (i % (2 * h)) % h]))
577 }
578
579 #[inline(always)]
582 pub fn ntt_bcast_hi(self, h: usize) -> Self {
583 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
584 unsafe {
585 use std::arch::x86_64::*;
586 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
587 let v = match h {
588 4 => _mm256_permute2x128_si256::<0x11>(a, a),
589 2 => _mm256_shuffle_epi32::<0xEE>(a),
590 1 => _mm256_shuffle_epi32::<0xF5>(a),
591 _ => unreachable!("i32 within-vector NTT stride is 4/2/1"),
592 };
593 let mut r = [0u32; 8];
594 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
595 return Self(r);
596 }
597 #[allow(unreachable_code)]
598 Self(core::array::from_fn(|i| self.0[(i / (2 * h)) * (2 * h) + h + (i % (2 * h)) % h]))
599 }
600
601 #[inline(always)]
604 pub fn ntt_blend(self, o: Self, h: usize) -> Self {
605 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
606 unsafe {
607 use std::arch::x86_64::*;
608 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
609 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
610 let v = match h {
611 4 => _mm256_permute2x128_si256::<0x30>(a, b),
612 2 => _mm256_blend_epi32::<0xCC>(a, b),
613 1 => _mm256_blend_epi32::<0xAA>(a, b),
614 _ => unreachable!("i32 within-vector NTT stride is 4/2/1"),
615 };
616 let mut r = [0u32; 8];
617 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
618 return Self(r);
619 }
620 #[allow(unreachable_code)]
621 Self(core::array::from_fn(|i| if (i % (2 * h)) < h { self.0[i] } else { o.0[i] }))
622 }
623}
624
625impl ::core::ops::BitXor for Lanes8Word32 {
628 type Output = Self;
629 #[inline]
630 fn bitxor(self, o: Self) -> Self {
631 Lanes8Word32::bitxor(self, o)
632 }
633}
634impl ::core::ops::Add for Lanes8Word32 {
635 type Output = Self;
636 #[inline]
637 fn add(self, o: Self) -> Self {
638 Lanes8Word32::add(self, o)
639 }
640}
641impl ::core::ops::Sub for Lanes8Word32 {
642 type Output = Self;
643 #[inline]
644 fn sub(self, o: Self) -> Self {
645 Lanes8Word32::sub(self, o)
646 }
647}
648impl ::core::ops::BitAnd for Lanes8Word32 {
649 type Output = Self;
650 #[inline]
651 fn bitand(self, o: Self) -> Self {
652 Lanes8Word32::bitand(self, o)
653 }
654}
655impl ::core::ops::BitOr for Lanes8Word32 {
656 type Output = Self;
657 #[inline]
658 fn bitor(self, o: Self) -> Self {
659 Lanes8Word32::bitor(self, o)
660 }
661}
662impl ::core::ops::Not for Lanes8Word32 {
663 type Output = Self;
664 #[inline]
665 fn not(self) -> Self {
666 Lanes8Word32::not(self)
667 }
668}
669
670#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
676#[repr(C, align(16))]
677pub struct Lanes4Word32(pub [u32; 4]);
678
679impl Lanes4Word32 {
680 pub const LANES: usize = 4;
682
683 #[inline]
685 pub const fn splat(x: u32) -> Self {
686 Self([x; 4])
687 }
688
689 #[inline]
691 pub fn from_words(s: &[Word32]) -> Self {
692 let mut a = [0u32; 4];
693 for (i, w) in s.iter().take(4).enumerate() {
694 a[i] = w.0;
695 }
696 Self(a)
697 }
698
699 #[inline]
701 pub fn to_words(self) -> [Word32; 4] {
702 self.0.map(Word32)
703 }
704
705 #[inline]
707 pub fn lane(self, i: usize) -> Word32 {
708 Word32(self.0[i])
709 }
710
711 #[inline(always)]
715 pub fn sha1rnds4(self, msg: Self, func: u32) -> Self {
716 #[cfg(all(target_arch = "x86_64", target_feature = "sha"))]
721 unsafe {
722 use core::arch::x86_64::*;
723 let a: __m128i = core::mem::transmute(self.0);
724 let b: __m128i = core::mem::transmute(msg.0);
725 let r = match func & 3 {
726 0 => _mm_sha1rnds4_epu32(a, b, 0),
727 1 => _mm_sha1rnds4_epu32(a, b, 1),
728 2 => _mm_sha1rnds4_epu32(a, b, 2),
729 _ => _mm_sha1rnds4_epu32(a, b, 3),
730 };
731 Self(core::mem::transmute(r))
732 }
733 #[cfg(not(all(target_arch = "x86_64", target_feature = "sha")))]
734 {
735 #[cfg(target_arch = "x86_64")]
736 {
737 if shani_available() {
738 return unsafe { self.sha1rnds4_hw(msg, func) };
739 }
740 }
741 Self(crate::sha_ops::sha1rnds4(self.0, msg.0, func))
742 }
743 }
744 #[inline(always)]
746 pub fn sha1msg1(self, o: Self) -> Self {
747 #[cfg(all(target_arch = "x86_64", target_feature = "sha"))]
748 unsafe {
749 use core::arch::x86_64::*;
750 let a: __m128i = core::mem::transmute(self.0);
751 let b: __m128i = core::mem::transmute(o.0);
752 Self(core::mem::transmute(_mm_sha1msg1_epu32(a, b)))
753 }
754 #[cfg(not(all(target_arch = "x86_64", target_feature = "sha")))]
755 {
756 #[cfg(target_arch = "x86_64")]
757 {
758 if shani_available() {
759 return unsafe { self.sha1msg1_hw(o) };
760 }
761 }
762 Self(crate::sha_ops::sha1msg1(self.0, o.0))
763 }
764 }
765 #[inline(always)]
767 pub fn sha1msg2(self, o: Self) -> Self {
768 #[cfg(all(target_arch = "x86_64", target_feature = "sha"))]
769 unsafe {
770 use core::arch::x86_64::*;
771 let a: __m128i = core::mem::transmute(self.0);
772 let b: __m128i = core::mem::transmute(o.0);
773 Self(core::mem::transmute(_mm_sha1msg2_epu32(a, b)))
774 }
775 #[cfg(not(all(target_arch = "x86_64", target_feature = "sha")))]
776 {
777 #[cfg(target_arch = "x86_64")]
778 {
779 if shani_available() {
780 return unsafe { self.sha1msg2_hw(o) };
781 }
782 }
783 Self(crate::sha_ops::sha1msg2(self.0, o.0))
784 }
785 }
786 #[inline(always)]
788 pub fn sha1nexte(self, o: Self) -> Self {
789 #[cfg(all(target_arch = "x86_64", target_feature = "sha"))]
790 unsafe {
791 use core::arch::x86_64::*;
792 let a: __m128i = core::mem::transmute(self.0);
793 let b: __m128i = core::mem::transmute(o.0);
794 Self(core::mem::transmute(_mm_sha1nexte_epu32(a, b)))
795 }
796 #[cfg(not(all(target_arch = "x86_64", target_feature = "sha")))]
797 {
798 #[cfg(target_arch = "x86_64")]
799 {
800 if shani_available() {
801 return unsafe { self.sha1nexte_hw(o) };
802 }
803 }
804 Self(crate::sha_ops::sha1nexte(self.0, o.0))
805 }
806 }
807
808 #[cfg(all(target_arch = "x86_64", not(target_feature = "sha")))]
809 #[target_feature(enable = "sha,sse2,sse4.1")]
810 unsafe fn sha1rnds4_hw(self, msg: Self, func: u32) -> Self {
811 use std::arch::x86_64::*;
812 let a = _mm_loadu_si128(self.0.as_ptr() as *const __m128i);
813 let b = _mm_loadu_si128(msg.0.as_ptr() as *const __m128i);
814 let r = match func & 3 {
816 0 => _mm_sha1rnds4_epu32(a, b, 0),
817 1 => _mm_sha1rnds4_epu32(a, b, 1),
818 2 => _mm_sha1rnds4_epu32(a, b, 2),
819 _ => _mm_sha1rnds4_epu32(a, b, 3),
820 };
821 let mut out = [0u32; 4];
822 _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, r);
823 Self(out)
824 }
825
826 #[cfg(all(target_arch = "x86_64", not(target_feature = "sha")))]
827 #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
828 unsafe fn sha1msg1_hw(self, o: Self) -> Self {
829 use std::arch::x86_64::*;
830 let a = _mm_loadu_si128(self.0.as_ptr() as *const __m128i);
831 let b = _mm_loadu_si128(o.0.as_ptr() as *const __m128i);
832 let mut out = [0u32; 4];
833 _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, _mm_sha1msg1_epu32(a, b));
834 Self(out)
835 }
836
837 #[cfg(all(target_arch = "x86_64", not(target_feature = "sha")))]
838 #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
839 unsafe fn sha1msg2_hw(self, o: Self) -> Self {
840 use std::arch::x86_64::*;
841 let a = _mm_loadu_si128(self.0.as_ptr() as *const __m128i);
842 let b = _mm_loadu_si128(o.0.as_ptr() as *const __m128i);
843 let mut out = [0u32; 4];
844 _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, _mm_sha1msg2_epu32(a, b));
845 Self(out)
846 }
847
848 #[cfg(all(target_arch = "x86_64", not(target_feature = "sha")))]
849 #[target_feature(enable = "sha,sse2,sse4.1")]
850 unsafe fn sha1nexte_hw(self, o: Self) -> Self {
851 use std::arch::x86_64::*;
852 let a = _mm_loadu_si128(self.0.as_ptr() as *const __m128i);
853 let b = _mm_loadu_si128(o.0.as_ptr() as *const __m128i);
854 let mut out = [0u32; 4];
855 _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, _mm_sha1nexte_epu32(a, b));
856 Self(out)
857 }
858
859 #[inline]
862 pub fn add(self, o: Self) -> Self {
863 let mut out = [0u32; 4];
864 for i in 0..4 {
865 out[i] = self.0[i].wrapping_add(o.0[i]);
866 }
867 Self(out)
868 }
869
870 #[inline]
872 pub fn bitxor(self, o: Self) -> Self {
873 let mut out = [0u32; 4];
874 for i in 0..4 {
875 out[i] = self.0[i] ^ o.0[i];
876 }
877 Self(out)
878 }
879}
880
881impl ::core::ops::Add for Lanes4Word32 {
882 type Output = Self;
883 #[inline]
884 fn add(self, o: Self) -> Self {
885 Lanes4Word32::add(self, o)
886 }
887}
888impl ::core::ops::BitXor for Lanes4Word32 {
889 type Output = Self;
890 #[inline]
891 fn bitxor(self, o: Self) -> Self {
892 Lanes4Word32::bitxor(self, o)
893 }
894}
895
896#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
902#[repr(C, align(16))]
903pub struct Lanes16Word8(pub [u8; 16]);
904
905impl Lanes16Word8 {
906 pub const LANES: usize = 16;
908
909 #[inline]
911 pub const fn splat(x: u8) -> Self {
912 Self([x; 16])
913 }
914
915 #[inline]
917 pub fn from_bytes(s: &[u8]) -> Self {
918 let mut a = [0u8; 16];
919 for (i, b) in s.iter().take(16).enumerate() {
920 a[i] = *b;
921 }
922 Self(a)
923 }
924
925 #[inline]
927 pub fn to_bytes(self) -> [u8; 16] {
928 self.0
929 }
930
931 #[inline]
933 pub fn lane(self, i: usize) -> u8 {
934 self.0[i]
935 }
936
937 #[inline]
940 pub fn shuffle(self, idx: Self) -> Self {
941 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
942 unsafe {
943 use core::arch::x86_64::*;
944 let a: __m128i = core::mem::transmute(self.0);
945 let b: __m128i = core::mem::transmute(idx.0);
946 Self(core::mem::transmute(_mm_shuffle_epi8(a, b)))
947 }
948 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
949 {
950 let mut r = [0u8; 16];
951 for i in 0..16 {
952 let x = idx.0[i];
953 r[i] = if x & 0x80 != 0 { 0 } else { self.0[(x & 0x0f) as usize] };
954 }
955 Self(r)
956 }
957 }
958
959 #[inline]
961 pub fn bitand(self, o: Self) -> Self {
962 let mut r = [0u8; 16];
963 for i in 0..16 {
964 r[i] = self.0[i] & o.0[i];
965 }
966 Self(r)
967 }
968
969 #[inline]
971 pub fn shr_bytes(self, n: u32) -> Self {
972 let mut r = [0u8; 16];
973 for i in 0..16 {
974 r[i] = self.0[i] >> n;
975 }
976 Self(r)
977 }
978
979 #[inline]
981 pub fn interleave_lo(self, o: Self) -> Self {
982 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
983 unsafe {
984 use core::arch::x86_64::*;
985 let a: __m128i = core::mem::transmute(self.0);
986 let b: __m128i = core::mem::transmute(o.0);
987 Self(core::mem::transmute(_mm_unpacklo_epi8(a, b)))
988 }
989 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
990 {
991 let mut r = [0u8; 16];
992 for i in 0..8 {
993 r[2 * i] = self.0[i];
994 r[2 * i + 1] = o.0[i];
995 }
996 Self(r)
997 }
998 }
999
1000 #[inline]
1002 pub fn interleave_hi(self, o: Self) -> Self {
1003 #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
1004 unsafe {
1005 use core::arch::x86_64::*;
1006 let a: __m128i = core::mem::transmute(self.0);
1007 let b: __m128i = core::mem::transmute(o.0);
1008 Self(core::mem::transmute(_mm_unpackhi_epi8(a, b)))
1009 }
1010 #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
1011 {
1012 let mut r = [0u8; 16];
1013 for i in 0..8 {
1014 r[2 * i] = self.0[8 + i];
1015 r[2 * i + 1] = o.0[8 + i];
1016 }
1017 Self(r)
1018 }
1019 }
1020
1021 #[inline]
1023 pub fn byte_add(self, o: Self) -> Self {
1024 let mut r = [0u8; 16];
1025 for i in 0..16 {
1026 r[i] = self.0[i].wrapping_add(o.0[i]);
1027 }
1028 Self(r)
1029 }
1030
1031 #[inline]
1035 pub fn maddubs(self, o: Self) -> Self {
1036 let mut r = [0u8; 16];
1037 for i in 0..8 {
1038 let a0 = self.0[2 * i] as i32;
1039 let a1 = self.0[2 * i + 1] as i32;
1040 let b0 = o.0[2 * i] as i8 as i32;
1041 let b1 = o.0[2 * i + 1] as i8 as i32;
1042 let s = (a0 * b0 + a1 * b1).clamp(-32768, 32767) as i16 as u16;
1043 r[2 * i] = (s & 0xff) as u8;
1044 r[2 * i + 1] = (s >> 8) as u8;
1045 }
1046 Self(r)
1047 }
1048
1049 #[inline]
1052 pub fn packus(self, o: Self) -> Self {
1053 let mut r = [0u8; 16];
1054 for i in 0..8 {
1055 let a = (self.0[2 * i] as u16 | (self.0[2 * i + 1] as u16) << 8) as i16;
1056 r[i] = a.clamp(0, 255) as u8;
1057 let b = (o.0[2 * i] as u16 | (o.0[2 * i + 1] as u16) << 8) as i16;
1058 r[8 + i] = b.clamp(0, 255) as u8;
1059 }
1060 Self(r)
1061 }
1062}
1063
1064impl ::core::ops::BitAnd for Lanes16Word8 {
1065 type Output = Self;
1066 #[inline]
1067 fn bitand(self, o: Self) -> Self {
1068 Lanes16Word8::bitand(self, o)
1069 }
1070}
1071
1072#[cfg(all(target_arch = "x86_64", not(target_feature = "sha")))]
1076#[inline]
1077fn shani_available() -> bool {
1078 std::is_x86_feature_detected!("sha")
1079 && std::is_x86_feature_detected!("ssse3")
1080 && std::is_x86_feature_detected!("sse4.1")
1081}
1082
1083#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1086#[repr(C, align(32))]
1087pub struct Lanes4Word64(pub [u64; 4]);
1088
1089impl Lanes4Word64 {
1090 pub const LANES: usize = 4;
1092
1093 #[inline]
1095 pub fn from_words(s: &[Word64]) -> Self {
1096 let mut a = [0u64; 4];
1097 for (i, w) in s.iter().take(4).enumerate() {
1098 a[i] = w.0;
1099 }
1100 Self(a)
1101 }
1102
1103 #[inline]
1105 pub fn to_words(self) -> [Word64; 4] {
1106 self.0.map(Word64)
1107 }
1108
1109 #[inline]
1111 pub fn lane(self, i: usize) -> Word64 {
1112 Word64(self.0[i])
1113 }
1114
1115 #[inline]
1118 pub fn hsum(self) -> u64 {
1119 self.0[0]
1120 .wrapping_add(self.0[1])
1121 .wrapping_add(self.0[2])
1122 .wrapping_add(self.0[3])
1123 }
1124
1125 #[inline(always)]
1129 pub fn add(self, o: Self) -> Self {
1130 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1131 unsafe {
1132 use std::arch::x86_64::*;
1133 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1134 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1135 let mut r = [0u64; 4];
1136 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_add_epi64(a, b));
1137 return Self(r);
1138 }
1139 #[allow(unreachable_code)]
1140 {
1141 let mut r = [0u64; 4];
1142 for i in 0..4 {
1143 r[i] = self.0[i].wrapping_add(o.0[i]);
1144 }
1145 Self(r)
1146 }
1147 }
1148
1149 #[cfg(test)]
1150 #[inline]
1151 fn add_scalar(self, o: Self) -> Self {
1152 let mut r = [0u64; 4];
1153 for i in 0..4 {
1154 r[i] = self.0[i].wrapping_add(o.0[i]);
1155 }
1156 Self(r)
1157 }
1158
1159 #[inline(always)]
1163 pub fn mul_lo32_wide(self, o: Self) -> Self {
1164 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1165 unsafe {
1166 use std::arch::x86_64::*;
1167 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1168 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1169 let mut r = [0u64; 4];
1170 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_mul_epu32(a, b));
1171 return Self(r);
1172 }
1173 #[allow(unreachable_code)]
1174 {
1175 let mut r = [0u64; 4];
1176 for i in 0..4 {
1177 r[i] = (self.0[i] & 0xffff_ffff) * (o.0[i] & 0xffff_ffff);
1178 }
1179 Self(r)
1180 }
1181 }
1182
1183 #[cfg(test)]
1184 #[inline]
1185 fn mul_lo32_wide_scalar(self, o: Self) -> Self {
1186 let mut r = [0u64; 4];
1187 for i in 0..4 {
1188 r[i] = (self.0[i] & 0xffff_ffff) * (o.0[i] & 0xffff_ffff);
1189 }
1190 Self(r)
1191 }
1192
1193 #[inline(always)]
1202 pub fn splat(x: u64) -> Self {
1203 Self([x; 4])
1204 }
1205
1206 #[inline(always)]
1208 pub fn bitxor(self, o: Self) -> Self {
1209 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1210 unsafe {
1211 use std::arch::x86_64::*;
1212 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1213 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1214 let mut r = [0u64; 4];
1215 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_xor_si256(a, b));
1216 return Self(r);
1217 }
1218 #[allow(unreachable_code)]
1219 Self([self.0[0] ^ o.0[0], self.0[1] ^ o.0[1], self.0[2] ^ o.0[2], self.0[3] ^ o.0[3]])
1220 }
1221
1222 #[inline(always)]
1224 pub fn and(self, o: Self) -> Self {
1225 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1226 unsafe {
1227 use std::arch::x86_64::*;
1228 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1229 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1230 let mut r = [0u64; 4];
1231 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_and_si256(a, b));
1232 return Self(r);
1233 }
1234 #[allow(unreachable_code)]
1235 Self([self.0[0] & o.0[0], self.0[1] & o.0[1], self.0[2] & o.0[2], self.0[3] & o.0[3]])
1236 }
1237
1238 #[inline(always)]
1240 pub fn andnot(self, o: Self) -> Self {
1241 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1242 unsafe {
1243 use std::arch::x86_64::*;
1244 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1245 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1246 let mut r = [0u64; 4];
1247 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_andnot_si256(a, b));
1248 return Self(r);
1249 }
1250 #[allow(unreachable_code)]
1251 Self([!self.0[0] & o.0[0], !self.0[1] & o.0[1], !self.0[2] & o.0[2], !self.0[3] & o.0[3]])
1252 }
1253
1254 #[inline(always)]
1257 pub fn rotl(self, n: u32) -> Self {
1258 let n = n % 64;
1259 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1260 unsafe {
1261 use std::arch::x86_64::*;
1262 if n == 0 {
1263 return self;
1264 }
1265 let x = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1266 let l = _mm256_sll_epi64(x, _mm_cvtsi32_si128(n as i32));
1267 let r_sh = _mm256_srl_epi64(x, _mm_cvtsi32_si128((64 - n) as i32));
1268 let mut r = [0u64; 4];
1269 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, _mm256_or_si256(l, r_sh));
1270 return Self(r);
1271 }
1272 #[allow(unreachable_code)]
1273 {
1274 let mut r = [0u64; 4];
1275 for i in 0..4 {
1276 r[i] = self.0[i].rotate_left(n);
1277 }
1278 Self(r)
1279 }
1280 }
1281}
1282
1283impl ::core::ops::Add for Lanes4Word64 {
1284 type Output = Self;
1285 #[inline]
1286 fn add(self, o: Self) -> Self {
1287 Lanes4Word64::add(self, o)
1288 }
1289}
1290
1291impl ::core::ops::BitXor for Lanes4Word64 {
1292 type Output = Self;
1293 #[inline]
1294 fn bitxor(self, o: Self) -> Self {
1295 Lanes4Word64::bitxor(self, o)
1296 }
1297}
1298
1299impl ::core::ops::BitAnd for Lanes4Word64 {
1300 type Output = Self;
1301 #[inline]
1302 fn bitand(self, o: Self) -> Self {
1303 Lanes4Word64::and(self, o)
1304 }
1305}
1306
1307#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1311#[repr(C, align(32))]
1312pub struct Lanes16Word16(pub [u16; 16]);
1313
1314#[cfg(target_arch = "x86_64")]
1319type Avx256 = std::arch::x86_64::__m256i;
1320#[cfg(not(target_arch = "x86_64"))]
1321type Avx256 = [u16; 16];
1322
1323#[cfg(target_arch = "x86_64")]
1327#[inline]
1328fn simd_add(a: Avx256, b: Avx256) -> Avx256 {
1329 unsafe { std::arch::x86_64::_mm256_add_epi16(a, b) }
1330}
1331#[cfg(target_arch = "x86_64")]
1332#[inline]
1333fn simd_sub(a: Avx256, b: Avx256) -> Avx256 {
1334 unsafe { std::arch::x86_64::_mm256_sub_epi16(a, b) }
1335}
1336#[cfg(target_arch = "x86_64")]
1337#[inline]
1338fn simd_mullo(a: Avx256, b: Avx256) -> Avx256 {
1339 unsafe { std::arch::x86_64::_mm256_mullo_epi16(a, b) }
1340}
1341#[cfg(target_arch = "x86_64")]
1342#[inline]
1343fn simd_mulhi(a: Avx256, b: Avx256) -> Avx256 {
1344 unsafe { std::arch::x86_64::_mm256_mulhi_epi16(a, b) }
1345}
1346#[cfg(not(target_arch = "x86_64"))]
1347#[inline]
1348fn simd_add(_a: Avx256, _b: Avx256) -> Avx256 {
1349 unreachable!("SIMD lane path is x86_64-only")
1350}
1351#[cfg(not(target_arch = "x86_64"))]
1352#[inline]
1353fn simd_sub(_a: Avx256, _b: Avx256) -> Avx256 {
1354 unreachable!("SIMD lane path is x86_64-only")
1355}
1356#[cfg(not(target_arch = "x86_64"))]
1357#[inline]
1358fn simd_mullo(_a: Avx256, _b: Avx256) -> Avx256 {
1359 unreachable!("SIMD lane path is x86_64-only")
1360}
1361#[cfg(not(target_arch = "x86_64"))]
1362#[inline]
1363fn simd_mulhi(_a: Avx256, _b: Avx256) -> Avx256 {
1364 unreachable!("SIMD lane path is x86_64-only")
1365}
1366
1367impl Lanes16Word16 {
1368 pub const LANES: usize = 16;
1370
1371 #[inline]
1373 pub const fn splat(x: u16) -> Self {
1374 Self([x; 16])
1375 }
1376
1377 #[inline]
1379 pub fn from_words(s: &[Word16]) -> Self {
1380 let mut a = [0u16; 16];
1381 for (i, w) in s.iter().take(16).enumerate() {
1382 a[i] = w.0;
1383 }
1384 Self(a)
1385 }
1386
1387 #[inline]
1389 pub fn to_words(self) -> [Word16; 16] {
1390 self.0.map(Word16)
1391 }
1392
1393 #[inline]
1395 pub fn lane(self, i: usize) -> Word16 {
1396 Word16(self.0[i])
1397 }
1398
1399 #[inline(always)]
1401 pub fn add(self, o: Self) -> Self {
1402 self.binop(o, |a, b| a.wrapping_add(b), |a, b| simd_add(a, b))
1403 }
1404
1405 #[inline(always)]
1407 pub fn sub(self, o: Self) -> Self {
1408 self.binop(o, |a, b| a.wrapping_sub(b), |a, b| simd_sub(a, b))
1409 }
1410
1411 #[inline(always)]
1413 pub fn mullo(self, o: Self) -> Self {
1414 self.binop(o, |a, b| a.wrapping_mul(b), |a, b| simd_mullo(a, b))
1415 }
1416
1417 #[inline(always)]
1419 pub fn mulhi(self, o: Self) -> Self {
1420 self.binop(
1421 o,
1422 |a, b| (((a as i16 as i32) * (b as i16 as i32)) >> 16) as u16,
1423 |a, b| simd_mulhi(a, b),
1424 )
1425 }
1426
1427 #[inline(always)]
1432 pub fn ntt_bcast_lo(self, h: usize) -> Self {
1433 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1434 {
1435 use std::arch::x86_64::*;
1436 let mut r = [0u16; 16];
1437 unsafe {
1438 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1439 let v = match h {
1440 8 => _mm256_permute2x128_si256::<0x00>(a, a),
1441 4 => _mm256_shuffle_epi32::<0x44>(a),
1442 2 => _mm256_shuffle_epi32::<0xA0>(a),
1443 _ => unreachable!("within-vector NTT stride is 8/4/2"),
1444 };
1445 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
1446 }
1447 return Self(r);
1448 }
1449 #[allow(unreachable_code)]
1450 Self(core::array::from_fn(|i| self.0[(i / (2 * h)) * (2 * h) + (i % (2 * h)) % h]))
1451 }
1452
1453 #[inline(always)]
1456 pub fn ntt_bcast_hi(self, h: usize) -> Self {
1457 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1458 {
1459 use std::arch::x86_64::*;
1460 let mut r = [0u16; 16];
1461 unsafe {
1462 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1463 let v = match h {
1464 8 => _mm256_permute2x128_si256::<0x11>(a, a),
1465 4 => _mm256_shuffle_epi32::<0xEE>(a),
1466 2 => _mm256_shuffle_epi32::<0xF5>(a),
1467 _ => unreachable!("within-vector NTT stride is 8/4/2"),
1468 };
1469 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
1470 }
1471 return Self(r);
1472 }
1473 #[allow(unreachable_code)]
1474 Self(core::array::from_fn(|i| self.0[(i / (2 * h)) * (2 * h) + h + (i % (2 * h)) % h]))
1475 }
1476
1477 #[inline(always)]
1481 pub fn ntt_blend(self, o: Self, h: usize) -> Self {
1482 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1483 {
1484 use std::arch::x86_64::*;
1485 let mut r = [0u16; 16];
1486 unsafe {
1487 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1488 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1489 let v = match h {
1490 8 => _mm256_permute2x128_si256::<0x30>(a, b),
1491 4 => _mm256_blend_epi32::<0xCC>(a, b),
1492 2 => _mm256_blend_epi32::<0xAA>(a, b),
1493 _ => unreachable!("within-vector NTT stride is 8/4/2"),
1494 };
1495 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, v);
1496 }
1497 return Self(r);
1498 }
1499 #[allow(unreachable_code)]
1500 Self(core::array::from_fn(|i| if (i % (2 * h)) < h { self.0[i] } else { o.0[i] }))
1501 }
1502
1503 #[inline(always)]
1509 fn binop(
1510 self,
1511 o: Self,
1512 scalar: impl Fn(u16, u16) -> u16,
1513 #[allow(unused_variables)] vector: impl Fn(Avx256, Avx256) -> Avx256,
1514 ) -> Self {
1515 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
1516 {
1517 use std::arch::x86_64::*;
1518 let mut r = [0u16; 16];
1519 unsafe {
1520 let a = _mm256_loadu_si256(self.0.as_ptr() as *const __m256i);
1521 let b = _mm256_loadu_si256(o.0.as_ptr() as *const __m256i);
1522 _mm256_storeu_si256(r.as_mut_ptr() as *mut __m256i, vector(a, b));
1523 }
1524 return Self(r);
1525 }
1526 #[allow(unreachable_code)]
1527 Self(core::array::from_fn(|i| scalar(self.0[i], o.0[i])))
1528 }
1529}
1530
1531macro_rules! lanes16_op {
1532 ($trait:ident, $tm:ident, $m:ident) => {
1533 impl ::core::ops::$trait for Lanes16Word16 {
1534 type Output = Self;
1535 #[inline]
1536 fn $tm(self, o: Self) -> Self {
1537 Lanes16Word16::$m(self, o)
1538 }
1539 }
1540 };
1541}
1542lanes16_op!(Add, add, add);
1543lanes16_op!(Sub, sub, sub);
1544lanes16_op!(Mul, mul, mullo);
1545
1546#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1549pub enum LanesVal {
1550 L8W32(Lanes8Word32),
1552 L4W64(Lanes4Word64),
1554 L16W16(Lanes16Word16),
1556 L4W32(Lanes4Word32),
1558 L16W8(Lanes16Word8),
1560}
1561
1562impl LanesVal {
1563 #[inline]
1565 pub const fn lanes(self) -> usize {
1566 match self {
1567 LanesVal::L8W32(_) => 8,
1568 LanesVal::L4W64(_) => 4,
1569 LanesVal::L16W16(_) => 16,
1570 LanesVal::L4W32(_) => 4,
1571 LanesVal::L16W8(_) => 16,
1572 }
1573 }
1574
1575 #[inline]
1577 pub const fn elem_bits(self) -> u32 {
1578 match self {
1579 LanesVal::L8W32(_) => 32,
1580 LanesVal::L4W64(_) => 64,
1581 LanesVal::L16W16(_) => 16,
1582 LanesVal::L4W32(_) => 32,
1583 LanesVal::L16W8(_) => 8,
1584 }
1585 }
1586
1587 #[inline]
1589 pub const fn type_name(self) -> &'static str {
1590 match self {
1591 LanesVal::L8W32(_) => "Lanes8Word32",
1592 LanesVal::L4W64(_) => "Lanes4Word64",
1593 LanesVal::L16W16(_) => "Lanes16Word16",
1594 LanesVal::L4W32(_) => "Lanes4Word32",
1595 LanesVal::L16W8(_) => "Lanes16Word8",
1596 }
1597 }
1598
1599 #[inline]
1601 pub fn lane(self, i: usize) -> u64 {
1602 match self {
1603 LanesVal::L8W32(v) => v.lane(i).0 as u64,
1604 LanesVal::L4W64(v) => v.lane(i).0,
1605 LanesVal::L16W16(v) => v.lane(i).0 as u64,
1606 LanesVal::L4W32(v) => v.lane(i).0 as u64,
1607 LanesVal::L16W8(v) => v.lane(i) as u64,
1608 }
1609 }
1610
1611 #[inline]
1613 pub fn shuffle(self, idx: Self) -> Option<Self> {
1614 match (self, idx) {
1615 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.shuffle(b))),
1616 _ => None,
1617 }
1618 }
1619 #[inline]
1620 pub fn byte_and(self, o: Self) -> Option<Self> {
1621 match (self, o) {
1622 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.bitand(b))),
1623 _ => None,
1624 }
1625 }
1626 #[inline]
1627 pub fn shr_bytes(self, n: u32) -> Option<Self> {
1628 match self {
1629 LanesVal::L16W8(a) => Some(LanesVal::L16W8(a.shr_bytes(n))),
1630 _ => None,
1631 }
1632 }
1633 #[inline]
1634 pub fn interleave_lo(self, o: Self) -> Option<Self> {
1635 match (self, o) {
1636 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.interleave_lo(b))),
1637 _ => None,
1638 }
1639 }
1640 #[inline]
1641 pub fn interleave_hi(self, o: Self) -> Option<Self> {
1642 match (self, o) {
1643 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.interleave_hi(b))),
1644 _ => None,
1645 }
1646 }
1647 #[inline]
1648 pub fn byte_add(self, o: Self) -> Option<Self> {
1649 match (self, o) {
1650 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.byte_add(b))),
1651 _ => None,
1652 }
1653 }
1654 #[inline]
1655 pub fn maddubs_bytes(self, o: Self) -> Option<Self> {
1656 match (self, o) {
1657 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.maddubs(b))),
1658 _ => None,
1659 }
1660 }
1661 #[inline]
1662 pub fn packus_bytes(self, o: Self) -> Option<Self> {
1663 match (self, o) {
1664 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.packus(b))),
1665 _ => None,
1666 }
1667 }
1668
1669 #[inline]
1671 pub fn sha1rnds4(self, msg: Self, func: u32) -> Option<Self> {
1672 match (self, msg) {
1673 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.sha1rnds4(b, func))),
1674 _ => None,
1675 }
1676 }
1677 #[inline]
1678 pub fn sha1msg1(self, o: Self) -> Option<Self> {
1679 match (self, o) {
1680 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.sha1msg1(b))),
1681 _ => None,
1682 }
1683 }
1684 #[inline]
1685 pub fn sha1msg2(self, o: Self) -> Option<Self> {
1686 match (self, o) {
1687 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.sha1msg2(b))),
1688 _ => None,
1689 }
1690 }
1691 #[inline]
1692 pub fn sha1nexte(self, o: Self) -> Option<Self> {
1693 match (self, o) {
1694 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.sha1nexte(b))),
1695 _ => None,
1696 }
1697 }
1698
1699 #[inline]
1701 pub fn bitxor(self, o: Self) -> Option<Self> {
1702 match (self, o) {
1703 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.bitxor(b))),
1704 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.bitxor(b))),
1705 _ => None,
1706 }
1707 }
1708
1709 #[inline]
1711 pub fn bitand(self, o: Self) -> Option<Self> {
1712 match (self, o) {
1713 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.bitand(b))),
1714 (LanesVal::L16W8(a), LanesVal::L16W8(b)) => Some(LanesVal::L16W8(a.bitand(b))),
1715 _ => None,
1716 }
1717 }
1718
1719 #[inline]
1721 pub fn bitor(self, o: Self) -> Option<Self> {
1722 match (self, o) {
1723 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.bitor(b))),
1724 _ => None,
1725 }
1726 }
1727
1728 #[inline]
1730 pub fn lane_not(self) -> Option<Self> {
1731 match self {
1732 LanesVal::L8W32(a) => Some(LanesVal::L8W32(a.not())),
1733 _ => None,
1734 }
1735 }
1736
1737 #[inline]
1739 pub fn add(self, o: Self) -> Option<Self> {
1740 match (self, o) {
1741 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.add(b))),
1742 (LanesVal::L4W64(a), LanesVal::L4W64(b)) => Some(LanesVal::L4W64(a.add(b))),
1743 (LanesVal::L16W16(a), LanesVal::L16W16(b)) => Some(LanesVal::L16W16(a.add(b))),
1744 (LanesVal::L4W32(a), LanesVal::L4W32(b)) => Some(LanesVal::L4W32(a.add(b))),
1745 _ => None,
1746 }
1747 }
1748
1749 #[inline]
1751 pub fn sub(self, o: Self) -> Option<Self> {
1752 match (self, o) {
1753 (LanesVal::L16W16(a), LanesVal::L16W16(b)) => Some(LanesVal::L16W16(a.sub(b))),
1754 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.sub(b))),
1755 _ => None,
1756 }
1757 }
1758
1759 #[inline]
1761 pub fn mullo(self, o: Self) -> Option<Self> {
1762 match (self, o) {
1763 (LanesVal::L16W16(a), LanesVal::L16W16(b)) => Some(LanesVal::L16W16(a.mullo(b))),
1764 _ => None,
1765 }
1766 }
1767
1768 #[inline]
1770 pub fn mulhi(self, o: Self) -> Option<Self> {
1771 match (self, o) {
1772 (LanesVal::L16W16(a), LanesVal::L16W16(b)) => Some(LanesVal::L16W16(a.mulhi(b))),
1773 _ => None,
1774 }
1775 }
1776
1777 #[inline]
1780 pub fn ntt_bcast_lo(self, h: usize) -> Option<Self> {
1781 match self {
1782 LanesVal::L16W16(a) => Some(LanesVal::L16W16(a.ntt_bcast_lo(h))),
1783 LanesVal::L8W32(a) => Some(LanesVal::L8W32(a.ntt_bcast_lo(h))),
1784 _ => None,
1785 }
1786 }
1787
1788 #[inline]
1790 pub fn ntt_bcast_hi(self, h: usize) -> Option<Self> {
1791 match self {
1792 LanesVal::L16W16(a) => Some(LanesVal::L16W16(a.ntt_bcast_hi(h))),
1793 LanesVal::L8W32(a) => Some(LanesVal::L8W32(a.ntt_bcast_hi(h))),
1794 _ => None,
1795 }
1796 }
1797
1798 #[inline]
1801 pub fn ntt_blend(self, o: Self, h: usize) -> Option<Self> {
1802 match (self, o) {
1803 (LanesVal::L16W16(a), LanesVal::L16W16(b)) => Some(LanesVal::L16W16(a.ntt_blend(b, h))),
1804 (LanesVal::L8W32(a), LanesVal::L8W32(b)) => Some(LanesVal::L8W32(a.ntt_blend(b, h))),
1805 _ => None,
1806 }
1807 }
1808
1809 #[inline]
1811 pub fn rotl(self, n: u32) -> Option<Self> {
1812 match self {
1813 LanesVal::L8W32(a) => Some(LanesVal::L8W32(a.rotl(n))),
1814 _ => None,
1815 }
1816 }
1817
1818 #[inline]
1820 pub fn mul_lo32_wide(self, o: Self) -> Option<Self> {
1821 match (self, o) {
1822 (LanesVal::L4W64(a), LanesVal::L4W64(b)) => Some(LanesVal::L4W64(a.mul_lo32_wide(b))),
1823 _ => None,
1824 }
1825 }
1826
1827 #[inline]
1830 pub fn montmul32(self, b: Self, q: Self, qinv: Self) -> Option<Self> {
1831 match (self, b, q, qinv) {
1832 (LanesVal::L8W32(a), LanesVal::L8W32(b), LanesVal::L8W32(q), LanesVal::L8W32(qi)) => {
1833 Some(LanesVal::L8W32(a.montmul32(b, q, qi)))
1834 }
1835 _ => None,
1836 }
1837 }
1838
1839 #[inline]
1841 pub fn hsum(self) -> i64 {
1842 match self {
1843 LanesVal::L8W32(a) => (0..8).map(|i| a.lane(i).0 as i64).sum(),
1844 LanesVal::L4W64(a) => a.hsum() as i64,
1845 LanesVal::L16W16(a) => (0..16).map(|i| a.lane(i).0 as i64).sum(),
1846 LanesVal::L4W32(a) => (0..4).map(|i| a.lane(i).0 as i64).sum(),
1847 LanesVal::L16W8(a) => (0..16).map(|i| a.lane(i) as i64).sum(),
1848 }
1849 }
1850}
1851
1852#[cfg(test)]
1853mod tests {
1854 use super::*;
1855
1856 #[test]
1857 fn word32_add_wraps_not_promotes() {
1858 assert_eq!(Word32::MAX.add(Word32::ONE), Word32::ZERO);
1860 assert_eq!(Word32(0xFFFF_FFFF).add(Word32(2)), Word32(1));
1861 }
1862
1863 #[test]
1864 fn word64_add_wraps_not_promotes() {
1865 assert_eq!(Word64::MAX.add(Word64::ONE), Word64::ZERO);
1866 assert_eq!(Word64(u64::MAX).add(Word64(5)), Word64(4));
1867 }
1868
1869 #[test]
1870 fn div_rem_shl_shr_operators_are_unsigned_primitive() {
1871 use core::ops::{Div, Rem, Shl, Shr};
1872 assert_eq!(Word32(0x1234_5678).rem(Word32(65536)), Word32(0x5678), "% 2^16 masks the low bits");
1874 assert_eq!(Word32(0x1234_5678).div(Word32(65536)), Word32(0x1234), "/ 2^16 shifts right 16");
1875 assert_eq!(Word32(7).div(Word32(2)), Word32(3), "unsigned truncating division");
1876 assert_eq!(Word32(7).rem(Word32(3)), Word32(1), "unsigned remainder");
1877 assert_eq!(Word32(1).shl(31), Word32(0x8000_0000), "<< 31 sets the top bit");
1878 assert_eq!(Word32(0x8000_0000).shr(31), Word32(1), ">> 31 is a logical (unsigned) shift");
1879 assert_eq!(Word64(1).shl(63).shr(63), Word64(1), "Word64 logical shift round-trips the top bit");
1880 assert_eq!(Word8(200).div(Word8(8)), Word8(25), "Word8 unsigned division (200 ÷ 8)");
1881 }
1882
1883 #[test]
1884 fn rotl_is_cyclic_and_inverse_of_rotr() {
1885 let x = Word32(0x1234_5678);
1886 assert_eq!(x.rotl(8), Word32(0x3456_7812), "rotl by 8 is the documented permutation");
1887 assert_eq!(x.rotl(0), x, "rotate by 0 is identity");
1888 assert_eq!(x.rotl(32), x, "a full rotation is identity");
1889 for n in 0..Word32::BITS {
1890 assert_eq!(x.rotl(n).rotr(n), x, "rotr undoes rotl by {n}");
1891 }
1892 }
1893
1894 #[test]
1895 fn agrees_with_native_wrapping_ops_over_fuzz() {
1896 let mut s = 0xDEAD_BEEF_CAFE_F00Du64;
1898 let mut next = || {
1899 s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
1900 s
1901 };
1902 for _ in 0..5000 {
1903 let a = next() as u32;
1904 let b = next() as u32;
1905 let n = (next() % 32) as u32;
1906 assert_eq!(Word32(a).add(Word32(b)).get(), a.wrapping_add(b), "add");
1907 assert_eq!(Word32(a).sub(Word32(b)).get(), a.wrapping_sub(b), "sub");
1908 assert_eq!(Word32(a).mul(Word32(b)).get(), a.wrapping_mul(b), "mul");
1909 assert_eq!(Word32(a).bitxor(Word32(b)).get(), a ^ b, "xor");
1910 assert_eq!(Word32(a).bitand(Word32(b)).get(), a & b, "and");
1911 assert_eq!(Word32(a).bitor(Word32(b)).get(), a | b, "or");
1912 assert_eq!(Word32(a).not().get(), !a, "not");
1913 assert_eq!(Word32(a).shl(n).get(), a.wrapping_shl(n), "shl");
1914 assert_eq!(Word32(a).shr(n).get(), a.wrapping_shr(n), "shr");
1915 assert_eq!(Word32(a).rotl(n).get(), a.rotate_left(n), "rotl");
1916 assert_eq!(Word32(a).rotr(n).get(), a.rotate_right(n), "rotr");
1917 }
1918 }
1919
1920 #[test]
1921 fn lanes8word32_avx2_xor_equals_scalar_lanes() {
1922 let mut s = 0x0BAD_F00D_1234_5678u64;
1926 let mut next = || {
1927 s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1928 s
1929 };
1930 for _ in 0..2000 {
1931 let a = Lanes8Word32(core::array::from_fn(|_| next() as u32));
1932 let b = Lanes8Word32(core::array::from_fn(|_| next() as u32));
1933 let n = (next() % 64) as u32; let xor_exp = Lanes8Word32(core::array::from_fn(|i| a.0[i] ^ b.0[i]));
1936 assert_eq!(a.bitxor_scalar(b), xor_exp, "scalar lane xor is the spec");
1937 assert_eq!(a.bitxor(b), xor_exp, "dispatched (AVX2) xor == spec");
1938 let add_exp = Lanes8Word32(core::array::from_fn(|i| a.0[i].wrapping_add(b.0[i])));
1940 assert_eq!(a.add_scalar(b), add_exp, "scalar lane add is the spec");
1941 assert_eq!(a.add(b), add_exp, "dispatched (AVX2) add == spec");
1942 let rot_exp = Lanes8Word32(core::array::from_fn(|i| a.0[i].rotate_left(n % 32)));
1944 assert_eq!(a.rotl_scalar(n % 32), rot_exp, "scalar lane rotl is the spec");
1945 assert_eq!(a.rotl(n), rot_exp, "dispatched (AVX2) rotl == spec (n mod 32)");
1946 let sub_exp = Lanes8Word32(core::array::from_fn(|i| a.0[i].wrapping_sub(b.0[i])));
1948 assert_eq!(a.sub_scalar(b), sub_exp, "scalar lane sub32 is the spec");
1949 assert_eq!(a.sub(b), sub_exp, "dispatched (AVX2) sub32 == spec");
1950 const Q: i32 = 8_380_417;
1952 const QINV: i32 = 58_728_449;
1953 let qv = Lanes8Word32::splat(Q as u32);
1954 let qiv = Lanes8Word32::splat(QINV as u32);
1955 let ar = Lanes8Word32(core::array::from_fn(|i| (a.0[i] as i32 % Q) as u32));
1957 let br = Lanes8Word32(core::array::from_fn(|i| (b.0[i] as i32 % Q) as u32));
1958 let mont_exp = Lanes8Word32(core::array::from_fn(|i| {
1959 let p = (ar.0[i] as i32 as i64) * (br.0[i] as i32 as i64);
1960 let t = (p as i32).wrapping_mul(QINV) as i64;
1961 (((p - t * Q as i64) >> 32) as i32) as u32
1962 }));
1963 assert_eq!(ar.montmul32_scalar(br, qv, qiv), mont_exp, "scalar montmul32 is the spec");
1964 assert_eq!(ar.montmul32(br, qv, qiv), mont_exp, "dispatched (AVX2) montmul32 == spec");
1965 for &h in &[4usize, 2, 1] {
1967 let bl = Lanes8Word32(core::array::from_fn(|i| a.0[(i / (2 * h)) * (2 * h) + (i % (2 * h)) % h]));
1968 let bh = Lanes8Word32(core::array::from_fn(|i| a.0[(i / (2 * h)) * (2 * h) + h + (i % (2 * h)) % h]));
1969 let bd = Lanes8Word32(core::array::from_fn(|i| if (i % (2 * h)) < h { a.0[i] } else { b.0[i] }));
1970 assert_eq!(a.ntt_bcast_lo(h), bl, "i32 ntt_bcast_lo({h}) (AVX2) == scalar gather");
1971 assert_eq!(a.ntt_bcast_hi(h), bh, "i32 ntt_bcast_hi({h}) (AVX2) == scalar gather");
1972 assert_eq!(a.ntt_blend(b, h), bd, "i32 ntt_blend({h}) (AVX2) == scalar gather");
1973 }
1974 }
1975 let v = Lanes8Word32::splat(0xABCD_1234);
1977 assert_eq!(v.lane(3), Word32(0xABCD_1234));
1978 assert_eq!(v.to_words()[7], Word32(0xABCD_1234));
1979 let packed = Lanes8Word32::from_words(&[Word32(1), Word32(2), Word32(3)]);
1980 assert_eq!(packed.0, [1, 2, 3, 0, 0, 0, 0, 0], "short slice zero-fills");
1981 }
1982
1983 #[test]
1984 fn lanes4word64_avx2_add_and_widemul_equal_scalar_lanes() {
1985 let mut s = 0x1357_9bdf_2468_ace0u64;
1988 let mut next = || {
1989 s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1990 s
1991 };
1992 for _ in 0..2000 {
1993 let a = Lanes4Word64(core::array::from_fn(|_| next()));
1994 let b = Lanes4Word64(core::array::from_fn(|_| next()));
1995 let add_exp = Lanes4Word64(core::array::from_fn(|i| a.0[i].wrapping_add(b.0[i])));
1996 assert_eq!(a.add_scalar(b), add_exp, "scalar lane add64 is the spec");
1997 assert_eq!(a.add(b), add_exp, "dispatched (AVX2) add64 == spec");
1998 let mul_exp =
1999 Lanes4Word64(core::array::from_fn(|i| (a.0[i] & 0xffff_ffff) * (b.0[i] & 0xffff_ffff)));
2000 assert_eq!(a.mul_lo32_wide_scalar(b), mul_exp, "scalar widening mul is the spec");
2001 assert_eq!(a.mul_lo32_wide(b), mul_exp, "dispatched (AVX2) vpmuludq == spec");
2002 let hs = a.0[0].wrapping_add(a.0[1]).wrapping_add(a.0[2]).wrapping_add(a.0[3]);
2004 assert_eq!(a.hsum(), hs, "horizontal sum");
2005 }
2006 let v = Lanes4Word64::from_words(&[Word64(5), Word64(9)]);
2007 assert_eq!(v.0, [5, 9, 0, 0], "short slice zero-fills");
2008 assert_eq!(v.lane(1), Word64(9));
2009 assert_eq!(LanesVal::L4W64(v).hsum(), 14, "5 + 9 + 0 + 0");
2010 assert_eq!(LanesVal::L4W64(v).type_name(), "Lanes4Word64");
2011 }
2012
2013 #[test]
2014 fn lanes16word16_avx2_ntt_ops_equal_scalar_lanes() {
2015 let mut s = 0x2468_ace0_1357_9bdfu64;
2019 let mut next = || {
2020 s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
2021 (s >> 40) as u16
2022 };
2023 for _ in 0..3000 {
2024 let a = Lanes16Word16(core::array::from_fn(|_| next()));
2025 let b = Lanes16Word16(core::array::from_fn(|_| next()));
2026 let add_exp = Lanes16Word16(core::array::from_fn(|i| a.0[i].wrapping_add(b.0[i])));
2027 let sub_exp = Lanes16Word16(core::array::from_fn(|i| a.0[i].wrapping_sub(b.0[i])));
2028 let lo_exp = Lanes16Word16(core::array::from_fn(|i| a.0[i].wrapping_mul(b.0[i])));
2029 let hi_exp = Lanes16Word16(core::array::from_fn(|i| {
2030 (((a.0[i] as i16 as i32) * (b.0[i] as i16 as i32)) >> 16) as u16
2031 }));
2032 assert_eq!(a.add(b), add_exp, "lane add16 == spec");
2033 assert_eq!(a.sub(b), sub_exp, "lane sub16 == spec");
2034 assert_eq!(a.mullo(b), lo_exp, "lane mullo16 == spec");
2035 assert_eq!(a.mulhi(b), hi_exp, "lane (signed) mulhi16 == spec");
2036 for &h in &[8usize, 4, 2] {
2039 let bl = Lanes16Word16(core::array::from_fn(|i| a.0[(i / (2 * h)) * (2 * h) + (i % (2 * h)) % h]));
2040 let bh = Lanes16Word16(core::array::from_fn(|i| a.0[(i / (2 * h)) * (2 * h) + h + (i % (2 * h)) % h]));
2041 let bd = Lanes16Word16(core::array::from_fn(|i| if (i % (2 * h)) < h { a.0[i] } else { b.0[i] }));
2042 assert_eq!(a.ntt_bcast_lo(h), bl, "ntt_bcast_lo({h}) (AVX2) == scalar gather");
2043 assert_eq!(a.ntt_bcast_hi(h), bh, "ntt_bcast_hi({h}) (AVX2) == scalar gather");
2044 assert_eq!(a.ntt_blend(b, h), bd, "ntt_blend({h}) (AVX2) == scalar gather");
2045 }
2046 }
2047 assert_eq!(Lanes16Word16::splat(7).lane(9), Word16(7));
2048 let p = Lanes16Word16::from_words(&[Word16(3), Word16(5)]);
2049 assert_eq!(p.0[0..3], [3, 5, 0], "short slice zero-fills");
2050 assert_eq!(LanesVal::L16W16(p).type_name(), "Lanes16Word16");
2051 assert_eq!(LanesVal::L16W16(p).lanes(), 16);
2052 }
2053
2054 #[test]
2055 fn wordval_dispatches_by_width_and_rejects_mismatch() {
2056 let a = WordVal::W32(Word32(0xFFFF_FFFF));
2057 let b = WordVal::W32(Word32(1));
2058 assert_eq!(a.add(b), Some(WordVal::W32(Word32(0))), "same-width add wraps");
2059 assert_eq!(a.width(), 32);
2060 assert_eq!(a.to_u64(), 0xFFFF_FFFF);
2061
2062 let c = WordVal::W64(Word64(1));
2063 assert_eq!(a.add(c), None, "mixed-width add is a type error");
2064 assert_eq!(a.bitxor(c), None, "mixed-width xor is a type error");
2065
2066 assert_eq!(WordVal::W64(Word64(0)).not(), WordVal::W64(Word64(u64::MAX)));
2068 assert_eq!(WordVal::W32(Word32(0x1234_5678)).rotl(8), WordVal::W32(Word32(0x3456_7812)));
2069
2070 assert_eq!(WordVal::from_u64(32, 0x1_0000_0005), Some(WordVal::W32(Word32(5))), "32 truncates");
2072 assert_eq!(WordVal::from_u64(64, 5), Some(WordVal::W64(Word64(5))));
2073 assert_eq!(WordVal::from_u64(16, 5), None, "only 32/64 are valid widths");
2074
2075 assert_eq!(WordVal::W32(Word32(42)).to_string(), "42");
2077 }
2078}