Skip to main content

rustfs_erasure_codec/
galois_16.rs

1//! GF(2^16) implementation.
2//!
3//! More accurately, this is a `GF((2^8)^2)` implementation which builds an extension
4//! field of `GF(2^8)`, as defined in the `galois_8` module.
5
6use crate::galois_8;
7use core::ops::{Add, Div, Mul, Sub};
8
9// the irreducible polynomial used as a modulus for the field.
10// print R.irreducible_element(2,algorithm="first_lexicographic" )
11// x^2 + a*x + a^7
12//
13// hopefully it is a fast polynomial
14const EXT_POLY: [u8; 3] = [1, 2, 128];
15
16/// The field GF(2^16).
17#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
18pub struct Field;
19
20impl crate::Field for Field {
21    const ORDER: usize = 65536;
22
23    type Elem = [u8; 2];
24
25    fn add(a: [u8; 2], b: [u8; 2]) -> [u8; 2] {
26        (Element(a) + Element(b)).0
27    }
28
29    fn mul(a: [u8; 2], b: [u8; 2]) -> [u8; 2] {
30        (Element(a) * Element(b)).0
31    }
32
33    fn div(a: [u8; 2], b: [u8; 2]) -> [u8; 2] {
34        (Element(a) / Element(b)).0
35    }
36
37    fn exp(elem: [u8; 2], n: usize) -> [u8; 2] {
38        Element(elem).exp(n).0
39    }
40
41    fn zero() -> [u8; 2] {
42        [0; 2]
43    }
44
45    fn one() -> [u8; 2] {
46        [0, 1]
47    }
48
49    fn nth_internal(n: usize) -> [u8; 2] {
50        [(n >> 8) as u8, n as u8]
51    }
52
53    fn mul_slice(elem: [u8; 2], input: &[[u8; 2]], out: &mut [[u8; 2]]) {
54        gf16_mul_slice(elem, input, out, false);
55    }
56
57    fn mul_slice_add(elem: [u8; 2], input: &[[u8; 2]], out: &mut [[u8; 2]]) {
58        gf16_mul_slice(elem, input, out, true);
59    }
60}
61
62/// SIMD-accelerated `out[i] = elem * input[i]` (or `out[i] ^= elem * input[i]`
63/// when `accumulate`) over the `GF((2^8)^2)` tower field.
64///
65/// A GF(2^16) element `ax·X + ac` multiplied by the fixed `elem = cx·X + cc`
66/// reduces (via `X² = 2·X + 128`, from [`EXT_POLY`]) to two GF(2^8) output
67/// planes, each a linear combination of the `ax`/`ac` input byte planes:
68///
69/// * `out_x = A·ax ^ B·ac`, `out_c = C·ax ^ D·ac`
70/// * `A = cc ^ 2·cx`, `B = cx`, `C = 128·cx`, `D = cc` (all in GF(2^8)).
71///
72/// Each `·` is a fixed-multiplier GF(2^8) slice multiply, so this reuses the
73/// SIMD-accelerated [`galois_8::mul_slice`]/[`galois_8::mul_slice_xor`] backends
74/// (ssse3/avx2/gfni/avx512/neon/vsx with runtime dispatch). Byte planes are
75/// de-/re-interleaved in fixed stack chunks via [`deinterleave`]/[`interleave`]
76/// (SIMD ssse3/neon, scalar fallback) to stay `no_std` and allocation free; the
77/// whole path is byte-wise GF(2^8), so it is endian-agnostic.
78///
79/// On x86 with GFNI the GF(2^8) multiplies are so fast that a *scalar* byte
80/// de-/re-interleave dominates the runtime (Amdahl); SIMD-ing the layout
81/// conversion is what restores the tower decomposition's speed-up there.
82fn gf16_mul_slice(elem: [u8; 2], input: &[[u8; 2]], out: &mut [[u8; 2]], accumulate: bool) {
83    assert_eq!(input.len(), out.len());
84
85    let cx = elem[0];
86    let cc = elem[1];
87    let coef_a = cc ^ galois_8::mul(2, cx);
88    let coef_b = cx;
89    let coef_c = galois_8::mul(128, cx);
90    let coef_d = cc;
91
92    const CHUNK: usize = 1024;
93    let mut ax = [0u8; CHUNK];
94    let mut ac = [0u8; CHUNK];
95    let mut ox = [0u8; CHUNK];
96    let mut oc = [0u8; CHUNK];
97
98    let mut offset = 0;
99    while offset < input.len() {
100        let n = core::cmp::min(CHUNK, input.len() - offset);
101
102        // Split the interleaved `[[u8; 2]]` input into the two GF(2^8) byte
103        // planes `ax`/`ac`. `as_flattened` is the safe `&[[u8; 2]] -> &[u8]`
104        // byte view; the split is a pure byte permutation (endian-agnostic).
105        deinterleave(
106            input[offset..offset + n].as_flattened(),
107            &mut ax[..n],
108            &mut ac[..n],
109        );
110
111        if accumulate {
112            deinterleave(
113                out[offset..offset + n].as_flattened(),
114                &mut ox[..n],
115                &mut oc[..n],
116            );
117            galois_8::mul_slice_xor(coef_a, &ax[..n], &mut ox[..n]);
118            galois_8::mul_slice_xor(coef_b, &ac[..n], &mut ox[..n]);
119            galois_8::mul_slice_xor(coef_c, &ax[..n], &mut oc[..n]);
120            galois_8::mul_slice_xor(coef_d, &ac[..n], &mut oc[..n]);
121        } else {
122            galois_8::mul_slice(coef_a, &ax[..n], &mut ox[..n]);
123            galois_8::mul_slice_xor(coef_b, &ac[..n], &mut ox[..n]);
124            galois_8::mul_slice(coef_c, &ax[..n], &mut oc[..n]);
125            galois_8::mul_slice_xor(coef_d, &ac[..n], &mut oc[..n]);
126        }
127
128        // Re-interleave the `ox`/`oc` planes back into the output elements.
129        interleave(
130            &ox[..n],
131            &oc[..n],
132            out[offset..offset + n].as_flattened_mut(),
133        );
134        offset += n;
135    }
136}
137
138/// De-interleave interleaved GF(2^16) element bytes into two contiguous GF(2^8)
139/// byte planes: `even[i] = src[2*i]`, `odd[i] = src[2*i + 1]`.
140///
141/// Requires `src.len() == 2 * even.len()` and `even.len() == odd.len()`. This is
142/// a pure byte permutation, so it is endian-agnostic. SIMD-accelerated on
143/// ssse3 (x86_64) and neon (aarch64), with a scalar fallback that also handles
144/// the sub-32-element tail of the SIMD paths.
145#[allow(clippy::needless_return)]
146#[inline]
147fn deinterleave(src: &[u8], even: &mut [u8], odd: &mut [u8]) {
148    debug_assert_eq!(src.len(), even.len() * 2);
149    debug_assert_eq!(even.len(), odd.len());
150
151    #[cfg(all(feature = "std", target_arch = "x86_64"))]
152    {
153        // 128-bit ssse3 kernel — covers every AVX2/GFNI CPU too, so the fast
154        // GF(2^8) backends no longer stall on a scalar layout conversion.
155        if is_x86_feature_detected!("ssse3") {
156            // SAFETY: ssse3 confirmed at runtime, matching the callee's
157            // `#[target_feature(enable = "ssse3")]`; the length contract above
158            // matches what the kernel indexes.
159            unsafe {
160                deinterleave_ssse3(src, even, odd);
161                return;
162            }
163        }
164    }
165    #[cfg(target_arch = "aarch64")]
166    {
167        // SAFETY: NEON is baseline on aarch64, so calling the
168        // `#[target_feature(enable = "neon")]` fn is sound; the length contract
169        // above matches what the kernel indexes.
170        unsafe {
171            deinterleave_neon(src, even, odd);
172            return;
173        }
174    }
175    #[cfg(not(target_arch = "aarch64"))]
176    deinterleave_scalar(src, even, odd);
177}
178
179/// Re-interleave two GF(2^8) byte planes into GF(2^16) element bytes:
180/// `dst[2*i] = even[i]`, `dst[2*i + 1] = odd[i]`. Inverse of [`deinterleave`].
181///
182/// Requires `dst.len() == 2 * even.len()` and `even.len() == odd.len()`.
183#[allow(clippy::needless_return)]
184#[inline]
185fn interleave(even: &[u8], odd: &[u8], dst: &mut [u8]) {
186    debug_assert_eq!(dst.len(), even.len() * 2);
187    debug_assert_eq!(even.len(), odd.len());
188
189    #[cfg(all(feature = "std", target_arch = "x86_64"))]
190    {
191        if is_x86_feature_detected!("ssse3") {
192            // SAFETY: ssse3 confirmed at runtime, matching the callee; the
193            // length contract above matches what the kernel indexes.
194            unsafe {
195                interleave_ssse3(even, odd, dst);
196                return;
197            }
198        }
199    }
200    #[cfg(target_arch = "aarch64")]
201    {
202        // SAFETY: NEON is baseline on aarch64; the length contract above matches
203        // what the kernel indexes.
204        unsafe {
205            interleave_neon(even, odd, dst);
206            return;
207        }
208    }
209    #[cfg(not(target_arch = "aarch64"))]
210    interleave_scalar(even, odd, dst);
211}
212
213fn deinterleave_scalar(src: &[u8], even: &mut [u8], odd: &mut [u8]) {
214    for (i, (e, o)) in even.iter_mut().zip(odd.iter_mut()).enumerate() {
215        *e = src[2 * i];
216        *o = src[2 * i + 1];
217    }
218}
219
220fn interleave_scalar(even: &[u8], odd: &[u8], dst: &mut [u8]) {
221    for (i, (e, o)) in even.iter().zip(odd.iter()).enumerate() {
222        dst[2 * i] = *e;
223        dst[2 * i + 1] = *o;
224    }
225}
226
227#[cfg(target_arch = "x86_64")]
228#[target_feature(enable = "ssse3")]
229unsafe fn deinterleave_ssse3(src: &[u8], even: &mut [u8], odd: &mut [u8]) {
230    use core::arch::x86_64::{_mm_loadu_si128, _mm_shuffle_epi8, _mm_storel_epi64};
231
232    // Extract even/odd bytes into the low 8 lanes; the high 8 are zeroed (0x80).
233    #[rustfmt::skip]
234    // SAFETY: the 16-byte array literal backs a valid 128-bit unaligned load of the shuffle mask.
235    let even_mask = unsafe { _mm_loadu_si128([
236        0u8, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
237    ].as_ptr().cast()) };
238    #[rustfmt::skip]
239    // SAFETY: the 16-byte array literal backs a valid 128-bit unaligned load of the shuffle mask.
240    let odd_mask = unsafe { _mm_loadu_si128([
241        1u8, 3, 5, 7, 9, 11, 13, 15, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
242    ].as_ptr().cast()) };
243
244    let batches = even.len() / 32;
245    for b in 0..batches {
246        let s = &src[b * 64..b * 64 + 64];
247        let e = &mut even[b * 32..b * 32 + 32];
248        let o = &mut odd[b * 32..b * 32 + 32];
249        // SAFETY: the slices above are exactly 64/32/32 bytes, so the four
250        // 128-bit loads at 0/16/32/48 and the eight 8-byte `storel_epi64` stores
251        // at 0/8/16/24 of `e` and `o` are all in-bounds; ssse3 confirmed by caller.
252        unsafe {
253            let p0 = _mm_loadu_si128(s.as_ptr().cast());
254            let p1 = _mm_loadu_si128(s[16..].as_ptr().cast());
255            let p2 = _mm_loadu_si128(s[32..].as_ptr().cast());
256            let p3 = _mm_loadu_si128(s[48..].as_ptr().cast());
257            _mm_storel_epi64(e.as_mut_ptr().cast(), _mm_shuffle_epi8(p0, even_mask));
258            _mm_storel_epi64(e[8..].as_mut_ptr().cast(), _mm_shuffle_epi8(p1, even_mask));
259            _mm_storel_epi64(e[16..].as_mut_ptr().cast(), _mm_shuffle_epi8(p2, even_mask));
260            _mm_storel_epi64(e[24..].as_mut_ptr().cast(), _mm_shuffle_epi8(p3, even_mask));
261            _mm_storel_epi64(o.as_mut_ptr().cast(), _mm_shuffle_epi8(p0, odd_mask));
262            _mm_storel_epi64(o[8..].as_mut_ptr().cast(), _mm_shuffle_epi8(p1, odd_mask));
263            _mm_storel_epi64(o[16..].as_mut_ptr().cast(), _mm_shuffle_epi8(p2, odd_mask));
264            _mm_storel_epi64(o[24..].as_mut_ptr().cast(), _mm_shuffle_epi8(p3, odd_mask));
265        }
266    }
267    let done = batches * 32;
268    deinterleave_scalar(&src[done * 2..], &mut even[done..], &mut odd[done..]);
269}
270
271#[cfg(target_arch = "x86_64")]
272#[target_feature(enable = "ssse3")]
273unsafe fn interleave_ssse3(even: &[u8], odd: &[u8], dst: &mut [u8]) {
274    use core::arch::x86_64::{
275        _mm_loadu_si128, _mm_storeu_si128, _mm_unpackhi_epi8, _mm_unpacklo_epi8,
276    };
277
278    let batches = even.len() / 32;
279    for b in 0..batches {
280        let e = &even[b * 32..b * 32 + 32];
281        let o = &odd[b * 32..b * 32 + 32];
282        let d = &mut dst[b * 64..b * 64 + 64];
283        // SAFETY: the slices above are exactly 32/32/64 bytes, so the four
284        // 128-bit loads at 0/16 of `e`/`o` and the four 128-bit stores at
285        // 0/16/32/48 of `d` are all in-bounds; ssse3 confirmed by caller.
286        unsafe {
287            let lo = _mm_loadu_si128(e.as_ptr().cast());
288            let hi = _mm_loadu_si128(o.as_ptr().cast());
289            _mm_storeu_si128(d.as_mut_ptr().cast(), _mm_unpacklo_epi8(lo, hi));
290            _mm_storeu_si128(d[16..].as_mut_ptr().cast(), _mm_unpackhi_epi8(lo, hi));
291            let lo2 = _mm_loadu_si128(e[16..].as_ptr().cast());
292            let hi2 = _mm_loadu_si128(o[16..].as_ptr().cast());
293            _mm_storeu_si128(d[32..].as_mut_ptr().cast(), _mm_unpacklo_epi8(lo2, hi2));
294            _mm_storeu_si128(d[48..].as_mut_ptr().cast(), _mm_unpackhi_epi8(lo2, hi2));
295        }
296    }
297    let done = batches * 32;
298    interleave_scalar(&even[done..], &odd[done..], &mut dst[done * 2..]);
299}
300
301#[cfg(target_arch = "aarch64")]
302#[target_feature(enable = "neon")]
303unsafe fn deinterleave_neon(src: &[u8], even: &mut [u8], odd: &mut [u8]) {
304    use core::arch::aarch64::{vld1q_u8, vst1q_u8, vuzp1q_u8, vuzp2q_u8};
305
306    let batches = even.len() / 32;
307    for b in 0..batches {
308        let s = &src[b * 64..b * 64 + 64];
309        let e = &mut even[b * 32..b * 32 + 32];
310        let o = &mut odd[b * 32..b * 32 + 32];
311        // vuzp1q extracts even-indexed bytes, vuzp2q the odd-indexed ones.
312        // SAFETY: the slices above are exactly 64/32/32 bytes, so the 128-bit
313        // loads at 0/16/32/48 of `s` and the 128-bit stores at 0/16 of `e`/`o`
314        // are in-bounds; NEON is baseline on aarch64.
315        unsafe {
316            let p0 = vld1q_u8(s.as_ptr());
317            let p1 = vld1q_u8(s[16..].as_ptr());
318            vst1q_u8(e.as_mut_ptr(), vuzp1q_u8(p0, p1));
319            vst1q_u8(o.as_mut_ptr(), vuzp2q_u8(p0, p1));
320            let p2 = vld1q_u8(s[32..].as_ptr());
321            let p3 = vld1q_u8(s[48..].as_ptr());
322            vst1q_u8(e[16..].as_mut_ptr(), vuzp1q_u8(p2, p3));
323            vst1q_u8(o[16..].as_mut_ptr(), vuzp2q_u8(p2, p3));
324        }
325    }
326    let done = batches * 32;
327    deinterleave_scalar(&src[done * 2..], &mut even[done..], &mut odd[done..]);
328}
329
330#[cfg(target_arch = "aarch64")]
331#[target_feature(enable = "neon")]
332unsafe fn interleave_neon(even: &[u8], odd: &[u8], dst: &mut [u8]) {
333    use core::arch::aarch64::{vld1q_u8, vst1q_u8, vzip1q_u8, vzip2q_u8};
334
335    let batches = even.len() / 32;
336    for b in 0..batches {
337        let e = &even[b * 32..b * 32 + 32];
338        let o = &odd[b * 32..b * 32 + 32];
339        let d = &mut dst[b * 64..b * 64 + 64];
340        // vzip1q/vzip2q interleave the low/high 16 bytes of the two planes.
341        // SAFETY: the slices above are exactly 32/32/64 bytes, so the 128-bit
342        // loads at 0/16 of `e`/`o` and the stores at 0/16/32/48 of `d` are
343        // in-bounds; NEON is baseline on aarch64.
344        unsafe {
345            let lo = vld1q_u8(e.as_ptr());
346            let hi = vld1q_u8(o.as_ptr());
347            vst1q_u8(d.as_mut_ptr(), vzip1q_u8(lo, hi));
348            vst1q_u8(d[16..].as_mut_ptr(), vzip2q_u8(lo, hi));
349            let lo2 = vld1q_u8(e[16..].as_ptr());
350            let hi2 = vld1q_u8(o[16..].as_ptr());
351            vst1q_u8(d[32..].as_mut_ptr(), vzip1q_u8(lo2, hi2));
352            vst1q_u8(d[48..].as_mut_ptr(), vzip2q_u8(lo2, hi2));
353        }
354    }
355    let done = batches * 32;
356    interleave_scalar(&even[done..], &odd[done..], &mut dst[done * 2..]);
357}
358
359/// Type alias of ReedSolomon over GF(2^8).
360pub type ReedSolomon = crate::ReedSolomon<Field>;
361
362/// Type alias of ShardByShard over GF(2^8).
363pub type ShardByShard<'a> = crate::ShardByShard<'a, Field>;
364
365/// An element of `GF(2^16)`.
366#[derive(Debug, Copy, Clone, PartialEq, Eq)]
367struct Element(pub [u8; 2]);
368
369impl Element {
370    // Create the zero element.
371    fn zero() -> Self {
372        Element([0, 0])
373    }
374
375    // A constant element evaluating to `n`.
376    fn constant(n: u8) -> Element {
377        Element([0, n])
378    }
379
380    // Whether this is the zero element.
381    fn is_zero(&self) -> bool {
382        self.0 == [0; 2]
383    }
384
385    fn exp(mut self, n: usize) -> Element {
386        if n == 0 {
387            Element::constant(1)
388        } else if self == Element::zero() {
389            Element::zero()
390        } else {
391            let x = self;
392            for _ in 1..n {
393                self = self * x;
394            }
395
396            self
397        }
398    }
399
400    // reduces from some polynomial with degree <= 2.
401    #[inline]
402    fn reduce_from(mut x: [u8; 3]) -> Self {
403        if x[0] != 0 {
404            // divide x by EXT_POLY and use remainder.
405            // i = 0 here.
406            // c*x^(i+j)  = a*x^i*b*x^j
407            x[1] ^= galois_8::mul(EXT_POLY[1], x[0]);
408            x[2] ^= galois_8::mul(EXT_POLY[2], x[0]);
409        }
410
411        Element([x[1], x[2]])
412    }
413
414    fn degree(&self) -> usize {
415        if self.0[0] != 0 { 1 } else { 0 }
416    }
417}
418
419impl From<[u8; 2]> for Element {
420    fn from(c: [u8; 2]) -> Self {
421        Element(c)
422    }
423}
424
425impl Default for Element {
426    fn default() -> Self {
427        Element::zero()
428    }
429}
430
431impl Add for Element {
432    type Output = Element;
433
434    fn add(self, other: Self) -> Element {
435        Element([self.0[0] ^ other.0[0], self.0[1] ^ other.0[1]])
436    }
437}
438
439impl Sub for Element {
440    type Output = Element;
441
442    fn sub(self, other: Self) -> Element {
443        self.add(other)
444    }
445}
446
447impl Mul for Element {
448    type Output = Element;
449
450    fn mul(self, rhs: Self) -> Element {
451        // FOIL; our elements are linear at most, with two coefficients
452        let out: [u8; 3] = [
453            galois_8::mul(self.0[0], rhs.0[0]),
454            galois_8::add(
455                galois_8::mul(self.0[1], rhs.0[0]),
456                galois_8::mul(self.0[0], rhs.0[1]),
457            ),
458            galois_8::mul(self.0[1], rhs.0[1]),
459        ];
460
461        Element::reduce_from(out)
462    }
463}
464
465impl Mul<u8> for Element {
466    type Output = Element;
467
468    fn mul(self, rhs: u8) -> Element {
469        Element([galois_8::mul(rhs, self.0[0]), galois_8::mul(rhs, self.0[1])])
470    }
471}
472
473impl Div for Element {
474    type Output = Element;
475
476    #[allow(clippy::suspicious_arithmetic_impl)]
477    fn div(self, rhs: Self) -> Element {
478        self * rhs.inverse()
479    }
480}
481
482// helpers for division.
483
484#[derive(Debug)]
485enum EgcdRhs {
486    Element(Element),
487    ExtPoly,
488}
489
490impl Element {
491    // compute extended euclidean algorithm against an element of self,
492    // where the GCD is known to be constant.
493    fn const_egcd(self, rhs: EgcdRhs) -> (u8, Element, Element) {
494        if self.is_zero() {
495            let rhs = match rhs {
496                EgcdRhs::Element(elem) => elem,
497                EgcdRhs::ExtPoly => {
498                    debug_assert!(false, "const_egcd invoked with divisible");
499                    Element::constant(1)
500                }
501            };
502            (rhs.0[1], Element::constant(0), Element::constant(1))
503        } else {
504            let (cur_quotient, cur_remainder) = match rhs {
505                EgcdRhs::Element(rhs) => rhs.polynom_div(self),
506                EgcdRhs::ExtPoly => Element::div_ext_by(self),
507            };
508
509            // GCD is constant because EXT_POLY is irreducible
510            let (g, x, y) = cur_remainder.const_egcd(EgcdRhs::Element(self));
511            (g, y + (cur_quotient * x), x)
512        }
513    }
514
515    // divide EXT_POLY by self.
516    fn div_ext_by(rhs: Self) -> (Element, Element) {
517        if rhs.degree() == 0 {
518            // dividing by constant is the same as multiplying by another constant.
519            // and all constant multiples of EXT_POLY are in the equivalence class
520            // of 0.
521            return (Element::zero(), Element::zero());
522        }
523
524        // divisor is ensured linear here.
525        // now ensure divisor is monic.
526        let leading_mul_inv = galois_8::div(1, rhs.0[0]);
527
528        let monictized = rhs * leading_mul_inv;
529        let mut poly = EXT_POLY;
530
531        for i in 0..2 {
532            let coef = poly[i];
533            for j in 1..2 {
534                if rhs.0[j] != 0 {
535                    poly[i + j] ^= galois_8::mul(monictized.0[j], coef);
536                }
537            }
538        }
539
540        let remainder = Element::constant(poly[2]);
541        let quotient = Element([poly[0], poly[1]]) * leading_mul_inv;
542
543        (quotient, remainder)
544    }
545
546    fn polynom_div(self, rhs: Self) -> (Element, Element) {
547        let divisor_degree = rhs.degree();
548        if rhs.is_zero() {
549            (Element::zero(), self)
550        } else if self.degree() < divisor_degree {
551            // If divisor's degree (len-1) is bigger, all dividend is a remainder
552            (Element::zero(), self)
553        } else if divisor_degree == 0 {
554            // divide by constant.
555            let invert = galois_8::div(1, rhs.0[1]);
556            let quotient = Element([
557                galois_8::mul(invert, self.0[0]),
558                galois_8::mul(invert, self.0[1]),
559            ]);
560
561            (quotient, Element::zero())
562        } else {
563            // self degree is at least divisor degree, divisor degree not 0.
564            // therefore both are 1.
565            debug_assert_eq!(self.degree(), divisor_degree);
566            debug_assert_eq!(self.degree(), 1);
567
568            // ensure rhs is constant.
569            let leading_mul_inv = galois_8::div(1, rhs.0[0]);
570            let monic = Element([
571                galois_8::mul(leading_mul_inv, rhs.0[0]),
572                galois_8::mul(leading_mul_inv, rhs.0[1]),
573            ]);
574
575            let leading_coeff = self.0[0];
576            let mut remainder = self.0[1];
577
578            if monic.0[1] != 0 {
579                remainder ^= galois_8::mul(monic.0[1], self.0[0]);
580            }
581
582            (
583                Element::constant(galois_8::mul(leading_mul_inv, leading_coeff)),
584                Element::constant(remainder),
585            )
586        }
587    }
588
589    /// Convert the inverse of this field element. Returns zero for zero input.
590    fn inverse(self) -> Element {
591        if self.is_zero() {
592            return Element::zero();
593        }
594
595        // first step of extended euclidean algorithm.
596        // done here because EXT_POLY is outside the scope of `Element`.
597        let (gcd, y) = {
598            // self / EXT_POLY = (0, self)
599            let remainder = self;
600
601            // GCD is constant because EXT_POLY is irreducible
602            let (g, x, _) = remainder.const_egcd(EgcdRhs::ExtPoly);
603
604            (g, x)
605        };
606
607        // we still need to normalize it by dividing by the gcd
608        if gcd != 0 {
609            // EXT_POLY is irreducible so the GCD will always be constant.
610            // EXT_POLY*x + self*y = gcd
611            // self*y = gcd - EXT_POLY*x
612            //
613            // EXT_POLY*x is representative of the equivalence class of 0.
614            let normalizer = galois_8::div(1, gcd);
615            y * normalizer
616        } else {
617            // self is equivalent to zero.
618            Element::zero()
619        }
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    extern crate alloc;
626    use alloc::{vec, vec::Vec};
627
628    use super::*;
629    use crate::Field as _;
630    use quickcheck::Arbitrary;
631
632    impl Arbitrary for Element {
633        fn arbitrary(gens: &mut quickcheck::Gen) -> Self {
634            let a = u8::arbitrary(gens);
635            let b = u8::arbitrary(gens);
636
637            Element([a, b])
638        }
639    }
640
641    quickcheck! {
642        fn qc_add_associativity(a: Element, b: Element, c: Element) -> bool {
643            a + (b + c) == (a + b) + c
644        }
645
646        fn qc_mul_associativity(a: Element, b: Element, c: Element) -> bool {
647            a * (b * c) == (a * b) * c
648        }
649
650        fn qc_additive_identity(a: Element) -> bool {
651            let zero = Element::zero();
652            a - (zero - a) == zero
653        }
654
655        fn qc_multiplicative_identity(a: Element) -> bool {
656            a.is_zero() || {
657                let one = Element([0, 1]);
658                (one / a) * a == one
659            }
660        }
661
662        fn qc_add_commutativity(a: Element, b: Element) -> bool {
663            a + b == b + a
664        }
665
666        fn qc_mul_commutativity(a: Element, b: Element) -> bool {
667            a * b == b * a
668        }
669
670        fn qc_add_distributivity(a: Element, b: Element, c: Element) -> bool {
671            a * (b + c) == (a * b) + (a * c)
672        }
673
674        fn qc_inverse(a: Element) -> bool {
675            a.is_zero() || {
676                let inv = a.inverse();
677                a * inv == Element::constant(1)
678            }
679        }
680
681        fn qc_exponent_1(a: Element, n: u8) -> bool {
682            a.is_zero() || n == 0 || {
683                let mut b = a.exp(n as usize);
684                for _ in 1..n {
685                    b = b / a;
686                }
687
688                a == b
689            }
690        }
691
692        fn qc_exponent_2(a: Element, n: u8) -> bool {
693            a.is_zero() || {
694                let mut res = true;
695                let mut b = Element::constant(1);
696
697                for i in 0..n {
698                    res = res && b == a.exp(i as usize);
699                    b = b * a;
700                }
701
702                res
703            }
704        }
705
706        fn qc_exp_zero_is_one(a: Element) -> bool {
707            a.exp(0) == Element::constant(1)
708        }
709    }
710
711    #[test]
712    fn test_div_b_is_0() {
713        assert_eq!(Element::zero(), Element([1, 0]) / Element::zero());
714    }
715
716    #[test]
717    fn zero_to_zero_is_one() {
718        assert_eq!(Element::zero().exp(0), Element::constant(1))
719    }
720
721    // Verifies the SIMD-decomposed `mul_slice`/`mul_slice_add` overrides against
722    // the element-wise scalar `Field::mul` reference across a range of lengths
723    // (including CHUNK boundary and tail) and multiplier values. If the A/B/C/D
724    // tower-field decomposition were wrong, this would catch it.
725    #[test]
726    fn mul_slice_matches_scalar_reference() {
727        const N: usize = 2100; // spans past the 1024 internal CHUNK, with a tail
728        let mut input = [[0u8; 2]; N];
729        for (i, e) in input.iter_mut().enumerate() {
730            *e = [
731                (i.wrapping_mul(31).wrapping_add(7)) as u8,
732                (i.wrapping_mul(17).wrapping_add(3)) as u8,
733            ];
734        }
735
736        let coeffs = [
737            [0u8, 0],  // zero
738            [0, 1],    // multiplicative identity
739            [0, 0x8e], // pure constant coefficient
740            [0x9a, 0], // pure X coefficient
741            [0x9a, 0x3f],
742            [0xff, 0xff],
743            [0x01, 0x80],
744        ];
745        let lens = [0usize, 1, 2, 7, 16, 17, 63, 1023, 1024, 1025, N];
746
747        for &c in &coeffs {
748            for &len in &lens {
749                let inp = &input[..len];
750
751                // mul_slice: out = c * inp
752                let mut out = [[0u8; 2]; N];
753                Field::mul_slice(c, inp, &mut out[..len]);
754                for (i, &e) in inp.iter().enumerate() {
755                    assert_eq!(
756                        out[i],
757                        Field::mul(c, e),
758                        "mul_slice c={c:?} len={len} i={i}"
759                    );
760                }
761
762                // mul_slice_add: out ^= c * inp, starting from a nonzero seed
763                let mut acc = [[0u8; 2]; N];
764                for (i, e) in acc.iter_mut().enumerate() {
765                    *e = [
766                        (i.wrapping_mul(13).wrapping_add(5)) as u8,
767                        (i.wrapping_mul(19).wrapping_add(11)) as u8,
768                    ];
769                }
770                let seed = acc;
771                Field::mul_slice_add(c, inp, &mut acc[..len]);
772                for (i, &e) in inp.iter().enumerate() {
773                    let expected = Field::add(seed[i], Field::mul(c, e));
774                    assert_eq!(acc[i], expected, "mul_slice_add c={c:?} len={len} i={i}");
775                }
776            }
777        }
778    }
779
780    // Verifies the SIMD `deinterleave`/`interleave` (whatever path the host CPU
781    // dispatches to) is byte-exact against the scalar reference and round-trips
782    // to the identity, across lengths that exercise the 32-element SIMD batch
783    // boundary and every sub-32 tail. The runtime-dispatch `deinterleave`
784    // compared against `deinterleave_scalar` also cross-checks the active SIMD
785    // kernel on the build target (neon on aarch64, ssse3 on x86_64).
786    #[test]
787    fn deinterleave_interleave_match_scalar_and_round_trip() {
788        // Deterministic interleaved input; distinct even/odd byte streams so a
789        // swapped/misaligned plane would show up immediately.
790        fn src_byte(i: usize) -> u8 {
791            (i.wrapping_mul(37).wrapping_add(i / 2).wrapping_add(1)) as u8
792        }
793
794        let lens = [
795            0usize, 1, 2, 3, 7, 15, 16, 17, 31, 32, 33, 47, 63, 64, 65, 96, 127, 128, 1000, 1024,
796        ];
797
798        for &n in &lens {
799            let src: Vec<u8> = (0..2 * n).map(src_byte).collect();
800
801            let mut even = vec![0u8; n];
802            let mut odd = vec![0u8; n];
803            deinterleave(&src, &mut even, &mut odd);
804
805            let mut even_ref = vec![0u8; n];
806            let mut odd_ref = vec![0u8; n];
807            deinterleave_scalar(&src, &mut even_ref, &mut odd_ref);
808            assert_eq!(even, even_ref, "deinterleave even plane, n={n}");
809            assert_eq!(odd, odd_ref, "deinterleave odd plane, n={n}");
810            for i in 0..n {
811                assert_eq!(even[i], src[2 * i], "even[{i}] != src[2i], n={n}");
812                assert_eq!(odd[i], src[2 * i + 1], "odd[{i}] != src[2i+1], n={n}");
813            }
814
815            let mut dst = vec![0u8; 2 * n];
816            interleave(&even, &odd, &mut dst);
817
818            let mut dst_ref = vec![0u8; 2 * n];
819            interleave_scalar(&even, &odd, &mut dst_ref);
820            assert_eq!(dst, dst_ref, "interleave, n={n}");
821
822            // Full round-trip must reconstruct the original interleaved bytes.
823            assert_eq!(dst, src, "interleave(deinterleave(src)) != src, n={n}");
824        }
825    }
826}