Skip to main content

p3_binary_dft/
butterfly.rs

1//! The butterfly kernel each field runs, and the subfield structure it exploits.
2
3use core::ops::Mul;
4
5use p3_binary_field::poly_basis::HAS_HARDWARE_CLMUL;
6use p3_binary_field::{
7    BinaryField2, BinaryField4, BinaryField8, BinaryField16, BinaryField32, BinaryField64,
8    BinaryField128, Gf2, Ghash128, Poly64, TowerLevel,
9};
10use p3_field::{PackedValue, PrimeCharacteristicRing};
11
12/// A field the additive transform has a butterfly kernel for.
13///
14/// Every tower level implements this, so the bound admits exactly what the transform did.
15/// The supertrait is sealed, so no type outside the field crate can gain an implementation.
16///
17/// What differs between levels is how much of the twiddle's structure the kernel can use.
18pub trait ButterflyField: TowerLevel {
19    /// Send `(lo, hi)` to `(lo + t*hi, lo + (t + 1)*hi)`, element by element.
20    ///
21    /// Setting the flag applies the inverse of that map instead.
22    ///
23    /// Callers outside a transform may use this as a standalone kernel over two runs.
24    ///
25    /// # Panics
26    /// Panics if the two runs have different lengths.
27    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self);
28}
29
30/// A tower level whose bytes are its `GF(2^8)` coordinates, lowest first.
31///
32/// # Safety
33///
34/// An implementor is a transparent wrapper over an unsigned integer of its own width.
35///
36/// Every bit pattern of that integer is a field element.
37///
38/// On a little-endian target its bytes are its tower coordinates, in order.
39unsafe trait ByteCoordinates: TowerLevel {}
40
41// The tower levels from `GF(2^8)` up, each a transparent wrapper over a matching integer.
42//
43// Below a byte a level does not fill its backing integer, so it has no byte coordinates.
44//
45// The GHASH representation is a different basis, so its bytes are not tower coordinates.
46unsafe impl ByteCoordinates for BinaryField8 {}
47unsafe impl ByteCoordinates for BinaryField16 {}
48unsafe impl ByteCoordinates for BinaryField32 {}
49unsafe impl ByteCoordinates for BinaryField64 {}
50unsafe impl ByteCoordinates for BinaryField128 {}
51
52/// The smallest byte-aligned tower subfield a twiddle lies in.
53///
54/// The coordinates of the subfield of `b` bytes occupy the low `b` bytes of the tower basis.
55///
56/// A twiddle's magnitude alone therefore says which subfield holds it.
57#[derive(Copy, Clone, Debug, PartialEq, Eq)]
58enum TwiddleWidth {
59    /// The twiddle lies in `GF(2^8)`, and this is its single coordinate.
60    Byte(u8),
61    /// The twiddle lies in `GF(2^16)` but not in `GF(2^8)`.
62    Word(u16),
63    /// The twiddle lies in `GF(2^32)` but not in `GF(2^16)`.
64    DoubleWord(u32),
65    /// The twiddle needs more than four bytes, which no byte map here covers.
66    Wide,
67}
68
69/// Which subfield a twiddle's bit pattern places it in.
70const fn classify(bits: u128) -> TwiddleWidth {
71    if bits <= u8::MAX as u128 {
72        TwiddleWidth::Byte(bits as u8)
73    } else if bits <= u16::MAX as u128 {
74        TwiddleWidth::Word(bits as u16)
75    } else if bits <= u32::MAX as u128 {
76        TwiddleWidth::DoubleWord(bits as u32)
77    } else {
78        TwiddleWidth::Wide
79    }
80}
81
82/// Apply a butterfly to full SIMD vectors and any remaining scalar elements.
83///
84/// Every level falls back to this, and every specialised path is pinned against it.
85#[inline]
86pub(crate) fn packed_butterfly<F: TowerLevel, const INVERSE: bool>(
87    lo: &mut [F],
88    hi: &mut [F],
89    t: F,
90) {
91    // Both sides have equal length, so their packed prefixes and tails pair exactly.
92    let (lo, lo_tail) = F::Packing::pack_slice_with_suffix_mut(lo);
93    let (hi, hi_tail) = F::Packing::pack_slice_with_suffix_mut(hi);
94    let zero = t.is_zero();
95    butterfly_values::<_, INVERSE>(lo, hi, t.into(), zero);
96    butterfly_values::<_, INVERSE>(lo_tail, hi_tail, t, zero);
97}
98
99/// Apply the same field identities to scalar or packed values.
100#[inline]
101fn butterfly_values<R: PrimeCharacteristicRing + Copy, const INVERSE: bool>(
102    lo: &mut [R],
103    hi: &mut [R],
104    t: R,
105    zero: bool,
106) {
107    if zero {
108        // A zero twiddle reduces both transform directions to (u, u + v).
109        for (u, v) in lo.iter_mut().zip(hi) {
110            *v += *u;
111        }
112    } else if INVERSE {
113        // Recover v first, then remove its twiddle contribution from u.
114        for (u, v) in lo.iter_mut().zip(hi) {
115            *v += *u;
116            *u += t * *v;
117        }
118    } else {
119        // Evaluate the pair as (u + t*v, u + t*v + v).
120        for (u, v) in lo.iter_mut().zip(hi) {
121            *u += t * *v;
122            *v += *u;
123        }
124    }
125}
126
127/// Apply a butterfly whose twiddle is typed as an element of a subfield.
128///
129/// Such a product is the subfield product applied to each coordinate.
130///
131/// That is cheaper than a full-width product at every level above the subfield.
132#[inline]
133fn typed_butterfly<F, S, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: S)
134where
135    F: Copy + PrimeCharacteristicRing + Mul<S, Output = F>,
136    S: Copy,
137{
138    if INVERSE {
139        // Recover v first, then remove its twiddle contribution from u.
140        for (u, v) in lo.iter_mut().zip(hi) {
141            *v += *u;
142            *u += *v * t;
143        }
144    } else {
145        // Evaluate the pair as (u + t*v, u + t*v + v).
146        for (u, v) in lo.iter_mut().zip(hi) {
147            *u += *v * t;
148            *v += *u;
149        }
150    }
151}
152
153/// Scale by a one-byte twiddle, or fall through to the full-width kernel.
154///
155/// This is the end of the chain: no byte-aligned subfield is narrower than one byte.
156#[inline]
157fn scale_by_byte<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
158where
159    F: TowerLevel + Mul<BinaryField8, Output = F>,
160{
161    if let TwiddleWidth::Byte(s) = width {
162        typed_butterfly::<F, BinaryField8, INVERSE>(lo, hi, BinaryField8::from_repr(s));
163    } else {
164        packed_butterfly::<F, INVERSE>(lo, hi, t);
165    }
166}
167
168/// Scale by a two-byte twiddle, or hand a narrower one down the chain.
169#[inline]
170fn scale_by_word<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
171where
172    F: TowerLevel + Mul<BinaryField8, Output = F> + Mul<BinaryField16, Output = F>,
173{
174    if let TwiddleWidth::Word(s) = width {
175        typed_butterfly::<F, BinaryField16, INVERSE>(lo, hi, BinaryField16::from_repr(s));
176    } else {
177        scale_by_byte::<F, INVERSE>(lo, hi, t, width);
178    }
179}
180
181/// Scale by a four-byte twiddle, or hand a narrower one down the chain.
182#[inline]
183fn scale_by_dword<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F, width: TwiddleWidth)
184where
185    F: TowerLevel
186        + Mul<BinaryField8, Output = F>
187        + Mul<BinaryField16, Output = F>
188        + Mul<BinaryField32, Output = F>,
189{
190    if let TwiddleWidth::DoubleWord(s) = width {
191        typed_butterfly::<F, BinaryField32, INVERSE>(lo, hi, BinaryField32::from_repr(s));
192    } else {
193        scale_by_word::<F, INVERSE>(lo, hi, t, width);
194    }
195}
196
197/// Whether a typed product beats the full-width product at the two widest levels.
198///
199/// - Scaling by a `D`-byte subfield costs one subfield product per coordinate.
200/// - Recursive multiplication triples in cost per doubling of the width.
201/// - The coordinate count only halves, so the narrower subfield always wins.
202///
203/// A carryless-multiply instruction breaks that at the widest two levels.
204/// Only the one-byte coordinates stay ahead of it, being single table lookups.
205///
206/// The field crate makes the same call for the product alone, and this is not the same call.
207/// A typed run walks one element at a time, and the full-width run packs a register of them.
208///
209/// The whole chain is inlined into one kernel.
210/// So carrying the wide levels through it costs the full-width route its own codegen.
211const WIDE_TYPED_PRODUCTS_PAY: bool = !HAS_HARDWARE_CLMUL;
212
213/// A tower level together with the typed subfield products its own width admits.
214///
215/// A typed product needs the subfield to be strictly narrower than the level.
216/// So the narrow levels route more of the twiddle widths to the full-width kernel.
217///
218/// The chain above holds the shared logic, and an implementation only names its entry point.
219trait SubfieldScaled: ByteCoordinates {
220    /// Apply the butterfly through the narrowest typed product that covers the twiddle.
221    fn scale_butterfly<const INVERSE: bool>(
222        lo: &mut [Self],
223        hi: &mut [Self],
224        t: Self,
225        width: TwiddleWidth,
226    );
227}
228
229impl SubfieldScaled for BinaryField8 {
230    /// One byte wide, so every twiddle is the whole element and no subfield is left.
231    #[inline]
232    fn scale_butterfly<const INVERSE: bool>(
233        lo: &mut [Self],
234        hi: &mut [Self],
235        t: Self,
236        width: TwiddleWidth,
237    ) {
238        scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
239    }
240}
241
242impl SubfieldScaled for BinaryField16 {
243    /// A two-byte twiddle is the whole element here, so the one-byte product is the only gain.
244    #[inline]
245    fn scale_butterfly<const INVERSE: bool>(
246        lo: &mut [Self],
247        hi: &mut [Self],
248        t: Self,
249        width: TwiddleWidth,
250    ) {
251        scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
252    }
253}
254
255impl SubfieldScaled for BinaryField32 {
256    /// A four-byte twiddle is the whole element here, so the chain stops at two bytes.
257    #[inline]
258    fn scale_butterfly<const INVERSE: bool>(
259        lo: &mut [Self],
260        hi: &mut [Self],
261        t: Self,
262        width: TwiddleWidth,
263    ) {
264        scale_by_word::<Self, INVERSE>(lo, hi, t, width);
265    }
266}
267
268impl SubfieldScaled for BinaryField64 {
269    /// Wide enough for every typed product, where the level's own product is recursive.
270    #[inline]
271    fn scale_butterfly<const INVERSE: bool>(
272        lo: &mut [Self],
273        hi: &mut [Self],
274        t: Self,
275        width: TwiddleWidth,
276    ) {
277        if WIDE_TYPED_PRODUCTS_PAY {
278            scale_by_dword::<Self, INVERSE>(lo, hi, t, width);
279        } else {
280            scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
281        }
282    }
283}
284
285impl SubfieldScaled for BinaryField128 {
286    /// Wide enough for every typed product, where the level's own product is recursive.
287    #[inline]
288    fn scale_butterfly<const INVERSE: bool>(
289        lo: &mut [Self],
290        hi: &mut [Self],
291        t: Self,
292        width: TwiddleWidth,
293    ) {
294        if WIDE_TYPED_PRODUCTS_PAY {
295            scale_by_dword::<Self, INVERSE>(lo, hi, t, width);
296        } else {
297            scale_by_byte::<Self, INVERSE>(lo, hi, t, width);
298        }
299    }
300}
301
302/// Run the leading whole registers through the byte map the twiddle's width allows.
303///
304/// Returns the number of elements covered, which the caller finishes from.
305#[cfg(all(
306    target_arch = "x86_64",
307    target_feature = "gfni",
308    target_feature = "avx512f",
309    target_feature = "avx512bw"
310))]
311#[inline]
312fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
313    lo: &mut [F],
314    hi: &mut [F],
315    width: TwiddleWidth,
316) -> usize {
317    use core::arch::x86_64::__m512i;
318
319    use crate::affine;
320    use crate::lanes::ByteRegister;
321
322    const {
323        // The layout contract holds only where the bytes run from the low coordinate up.
324        assert!(cfg!(target_endian = "little"));
325
326        // One element is its whole backing integer, so its size is its coordinate count.
327        assert!(size_of::<F>() == 1 << (F::LOG_BITS - 3));
328
329        // A register is 64 bytes, so a whole number of them is a whole number of elements.
330        assert!(64 % size_of::<F>() == 0);
331    }
332
333    let bytes = size_of_val(lo);
334
335    // A run below one register covers nothing, so its blocks would be built and thrown away.
336    // The narrow stages of a narrow matrix are almost all of the butterflies, and all short.
337    if bytes < <__m512i as ByteRegister>::BYTES {
338        return 0;
339    }
340
341    // SAFETY: the marker trait's contract makes each run exactly that many initialised bytes.
342    //
343    // It rules out padding, and any byte pattern a write could turn into an invalid element.
344    // The two runs are halves of one split slice, so no byte belongs to both.
345    // The runs have equal length, so one byte count is in bounds for both.
346    let (lo, hi) = unsafe {
347        (
348            core::slice::from_raw_parts_mut(lo.as_mut_ptr().cast::<u8>(), bytes),
349            core::slice::from_raw_parts_mut(hi.as_mut_ptr().cast::<u8>(), bytes),
350        )
351    };
352
353    // A group is one coordinate of the twiddle's subfield.
354    //
355    // So an element has to hold whole groups for the map to act coordinate by coordinate.
356    let covered = match width {
357        TwiddleWidth::Byte(t) => {
358            affine::subfield_butterfly::<__m512i, 1, INVERSE>(lo, hi, &affine::byte_blocks(t))
359        }
360        TwiddleWidth::Word(t) if size_of::<F>() >= 2 => {
361            affine::subfield_butterfly::<__m512i, 2, INVERSE>(lo, hi, &affine::word_blocks(t))
362        }
363        TwiddleWidth::DoubleWord(t) if size_of::<F>() >= 4 => {
364            affine::subfield_butterfly::<__m512i, 4, INVERSE>(lo, hi, &affine::dword_blocks(t))
365        }
366        _ => 0,
367    };
368    covered / size_of::<F>()
369}
370
371/// The one-byte subfield butterfly on NEON.
372///
373/// # Returns
374///
375/// - The number of elements covered, which the caller finishes from.
376#[cfg(all(
377    target_arch = "aarch64",
378    target_feature = "neon",
379    target_endian = "little"
380))]
381#[inline]
382fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
383    lo: &mut [F],
384    hi: &mut [F],
385    width: TwiddleWidth,
386) -> usize {
387    use core::arch::aarch64::uint8x16_t;
388
389    use crate::lanes::ByteRegister;
390    use crate::neon;
391
392    // The byte layout only holds where the bytes run from the low coordinate up.
393    //
394    // That is a little-endian target, which the gate on this function already requires.
395    const {
396        // One element is its whole backing integer, so its size is its coordinate count.
397        assert!(size_of::<F>() == 1 << (F::LOG_BITS - 3));
398
399        // A register is sixteen bytes, so a whole number of them is a whole number of elements.
400        assert!(<uint8x16_t as ByteRegister>::BYTES.is_multiple_of(size_of::<F>()));
401    }
402
403    let TwiddleWidth::Byte(t) = width else {
404        return 0;
405    };
406
407    let bytes = size_of_val(lo);
408    if bytes < neon::MIN_BYTES {
409        return 0;
410    }
411
412    // SAFETY: the marker trait's contract makes each run exactly that many initialised bytes.
413    //
414    // It rules out padding, and any byte pattern a write could turn into an invalid element.
415    // The two runs are halves of one split slice, so no byte belongs to both.
416    // The runs have equal length, so one byte count is in bounds for both.
417    let (lo, hi) = unsafe {
418        (
419            core::slice::from_raw_parts_mut(lo.as_mut_ptr().cast::<u8>(), bytes),
420            core::slice::from_raw_parts_mut(hi.as_mut_ptr().cast::<u8>(), bytes),
421        )
422    };
423
424    // The map is linear, so its products with the basis elements determine it.
425    let t = BinaryField8::from_repr(t);
426    let columns: [u8; 8] =
427        core::array::from_fn(|b| (t * BinaryField8::from_repr(1 << b)).to_repr());
428
429    neon::byte_butterfly::<INVERSE>(lo, hi, &columns) / size_of::<F>()
430}
431
432/// Without the byte map there is no prefix to take, so the whole run falls to the caller.
433#[cfg(not(any(
434    all(
435        target_arch = "x86_64",
436        target_feature = "gfni",
437        target_feature = "avx512f",
438        target_feature = "avx512bw"
439    ),
440    all(
441        target_arch = "aarch64",
442        target_feature = "neon",
443        target_endian = "little"
444    )
445)))]
446#[inline]
447const fn subfield_prefix<F: ByteCoordinates, const INVERSE: bool>(
448    _lo: &mut [F],
449    _hi: &mut [F],
450    _width: TwiddleWidth,
451) -> usize {
452    0
453}
454
455/// The butterfly of a level whose bytes are tower coordinates.
456///
457/// A twiddle narrow enough to sit in a byte-aligned subfield drives a byte map.
458///
459/// Whatever the byte map leaves over falls to a typed product of the same subfield.
460///
461/// # Panics
462/// Panics if the two runs have different lengths.
463#[inline]
464fn coordinate_butterfly<F, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F)
465where
466    F: SubfieldScaled,
467    F::Repr: Into<u128>,
468{
469    // Invariant: the two sides are paired element for element.
470    //
471    // Every kernel below stops at the shorter one, so a mismatch would silently drop work.
472    assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
473
474    // A zero twiddle leaves an addition, which the packed kernel already shortcuts.
475    if t.is_zero() {
476        return packed_butterfly::<F, INVERSE>(lo, hi, t);
477    }
478
479    let width = classify(t.to_repr().into());
480    let covered = subfield_prefix::<F, INVERSE>(lo, hi, width);
481
482    // Whatever the register loop left over, down to one element.
483    //
484    // A subfield twiddle scales each coordinate on its own.
485    // That beats a full-width product even with no byte map to run it through.
486    F::scale_butterfly::<INVERSE>(&mut lo[covered..], &mut hi[covered..], t, width);
487}
488
489/// The butterfly of a level with no subfield structure to exploit.
490///
491/// # Panics
492/// Panics if the two runs have different lengths.
493#[inline]
494fn plain_butterfly<F: TowerLevel, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F) {
495    // Invariant: the two sides are paired element for element.
496    //
497    // The packed kernel stops at the shorter one, so a mismatch would silently drop work.
498    assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
499    packed_butterfly::<F, INVERSE>(lo, hi, t);
500}
501
502impl ButterflyField for BinaryField8 {
503    #[inline]
504    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
505        coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
506    }
507}
508
509impl ButterflyField for BinaryField16 {
510    #[inline]
511    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
512        coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
513    }
514}
515
516impl ButterflyField for BinaryField32 {
517    #[inline]
518    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
519        coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
520    }
521}
522
523impl ButterflyField for BinaryField64 {
524    #[inline]
525    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
526        coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
527    }
528}
529
530impl ButterflyField for BinaryField128 {
531    #[inline]
532    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
533        coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
534    }
535}
536
537impl ButterflyField for Gf2 {
538    #[inline]
539    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
540        plain_butterfly::<Self, INVERSE>(lo, hi, t);
541    }
542}
543
544impl ButterflyField for BinaryField2 {
545    #[inline]
546    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
547        plain_butterfly::<Self, INVERSE>(lo, hi, t);
548    }
549}
550
551impl ButterflyField for BinaryField4 {
552    #[inline]
553    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
554        plain_butterfly::<Self, INVERSE>(lo, hi, t);
555    }
556}
557
558impl ButterflyField for Ghash128 {
559    #[inline]
560    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
561        plain_butterfly::<Self, INVERSE>(lo, hi, t);
562    }
563}
564
565impl ButterflyField for Poly64 {
566    #[inline]
567    fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
568        plain_butterfly::<Self, INVERSE>(lo, hi, t);
569    }
570}
571
572#[cfg(test)]
573mod tests {
574    use alloc::vec::Vec;
575
576    use p3_binary_field::TowerLevel;
577    use p3_field::PrimeCharacteristicRing;
578    use proptest::prelude::*;
579
580    use super::{
581        BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, ButterflyField,
582        Ghash128, TwiddleWidth, classify,
583    };
584
585    /// The twiddles a random search is unlikely to reach.
586    ///
587    /// Each one sits at a boundary of the subfield classification or of the butterfly itself:
588    ///
589    /// ```text
590    ///     0               the butterfly collapses to a single addition
591    ///     1               the identity multiplier
592    ///     0x80            the highest-degree basis element of the one-byte subfield
593    ///     0xff            the widest twiddle the one-byte map still covers
594    ///     0x100           the narrowest that needs the two-byte map
595    ///     0x8000          the highest-degree basis element of the two-byte subfield
596    ///     0xffff          the widest the two-byte map covers
597    ///     0x1_0000        the narrowest that needs the four-byte map
598    ///     0x8000_0000     the highest-degree basis element of the four-byte subfield
599    ///     0xffff_ffff     the widest the four-byte map covers
600    ///     0x1_0000_0000   the narrowest that falls through to the packed kernel
601    ///     1 << 127        the highest-degree basis element of the widest level
602    ///     0x87            the tail of the GHASH modulus, for the level that uses it
603    /// ```
604    const CORNERS: [u128; 13] = [
605        0,
606        1,
607        0x80,
608        0xff,
609        0x100,
610        0x8000,
611        0xffff,
612        0x1_0000,
613        0x8000_0000,
614        0xffff_ffff,
615        0x1_0000_0000,
616        1 << 127,
617        0x87,
618    ];
619
620    /// The lengths a sweep covers, in elements.
621    ///
622    /// A 512-bit register holds 64 bytes.
623    ///
624    /// That is 64 elements of the narrowest level and 4 of the widest.
625    ///
626    /// So this range spans several whole registers plus a tail of every size.
627    const LENGTHS: core::ops::RangeInclusive<usize> = 0..=70;
628
629    /// A run whose elements share no structure with one another.
630    fn sample<F: TowerLevel>(len: usize, seed: u64) -> Vec<F> {
631        (0..len)
632            .map(|i| {
633                // Two odd multipliers apart, so neighbouring positions share no low bits.
634                let bits = seed
635                    .wrapping_mul(0x9e37_79b9_7f4a_7c15)
636                    .wrapping_add(i as u64 + 1)
637                    .wrapping_mul(0xbf58_476d_1ce4_e5b9);
638
639                // Repeating the pattern fills a level wider than the eight bytes it holds.
640                F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
641            })
642            .collect()
643    }
644
645    /// The butterfly written out one element at a time, through the field's own product.
646    fn reference<F: TowerLevel>(lo: &mut [F], hi: &mut [F], t: F, inverse: bool) {
647        for (u, v) in lo.iter_mut().zip(hi) {
648            if inverse {
649                // Recover the upper half, then take the scaled result out of the lower one.
650                *v += *u;
651                *u += t * *v;
652            } else {
653                // Scale the upper half into the lower one, then sum both into the upper one.
654                *u += t * *v;
655                *v += *u;
656            }
657        }
658    }
659
660    /// Both directions of the kernel against the element-at-a-time loop, at one length.
661    fn agrees<F: ButterflyField>(len: usize, t: F) -> Result<(), TestCaseError> {
662        // Two runs with nothing in common, so a kernel that mixed them would show it.
663        let (lo, hi) = (sample::<F>(len, 1), sample::<F>(len, 2));
664
665        // Forward: the kernel and the reference must produce the same pair of runs.
666        let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
667        reference(&mut want_lo, &mut want_hi, t, false);
668
669        let (mut got_lo, mut got_hi) = (lo.clone(), hi.clone());
670        F::butterfly::<false>(&mut got_lo, &mut got_hi, t);
671        prop_assert_eq!(&got_lo, &want_lo);
672        prop_assert_eq!(&got_hi, &want_hi);
673
674        // The inverse applied to that output must return the input, for every twiddle.
675        F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
676        prop_assert_eq!(&got_lo, &lo);
677        prop_assert_eq!(&got_hi, &hi);
678
679        // Inverse on its own, against its own reference loop.
680        let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
681        reference(&mut want_lo, &mut want_hi, t, true);
682
683        let (mut got_lo, mut got_hi) = (lo, hi);
684        F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
685        prop_assert_eq!(&got_lo, &want_lo);
686        prop_assert_eq!(&got_hi, &want_hi);
687        Ok(())
688    }
689
690    /// Every corner twiddle at every length in the sweep, for one level.
691    fn sweep<F: ButterflyField>(name: &str) {
692        for &bits in &CORNERS {
693            // A corner wider than the level keeps only the coordinates the level has.
694            let t =
695                F::from_le_byte_iter(bits.to_le_bytes().into_iter().chain(core::iter::repeat(0)));
696            for len in LENGTHS {
697                agrees::<F>(len, t)
698                    .unwrap_or_else(|e| panic!("{name}, twiddle {bits:#x}, len {len}: {e}"));
699            }
700        }
701    }
702
703    #[test]
704    fn every_level_agrees_with_the_element_loop_at_the_corners() {
705        // The five byte-aligned levels take the subfield paths, the GHASH basis does not.
706        sweep::<BinaryField8>("BinaryField8");
707        sweep::<BinaryField16>("BinaryField16");
708        sweep::<BinaryField32>("BinaryField32");
709        sweep::<BinaryField64>("BinaryField64");
710        sweep::<BinaryField128>("BinaryField128");
711        sweep::<Ghash128>("Ghash128");
712    }
713
714    #[test]
715    fn one_byte_twiddles_agree_around_every_register_boundary() {
716        // Lengths around the register and threshold boundaries.
717        const LENS: [usize; 14] = [0, 1, 15, 16, 17, 31, 32, 33, 255, 256, 257, 511, 512, 513];
718        for t in [0x01u8, 0x02, 0x03, 0x80, 0xa5, 0xff] {
719            for len in LENS {
720                agrees::<BinaryField8>(len, BinaryField8::from_repr(t)).unwrap();
721                agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16)).unwrap();
722                agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32)).unwrap();
723                agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64)).unwrap();
724                agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128)).unwrap();
725            }
726        }
727    }
728
729    #[test]
730    fn the_classification_follows_the_twiddle_magnitude() {
731        // Fixture: both ends of each subfield band, plus the first value past the widest.
732        //
733        // ```text
734        //     0             ..= 0xff          one byte
735        //     0x100         ..= 0xffff        two bytes
736        //     0x1_0000      ..= 0xffff_ffff   four bytes
737        //     0x1_0000_0000 and up            no byte map
738        // ```
739        assert_eq!(classify(0), TwiddleWidth::Byte(0));
740        assert_eq!(classify(0xff), TwiddleWidth::Byte(0xff));
741        assert_eq!(classify(0x100), TwiddleWidth::Word(0x100));
742        assert_eq!(classify(0xffff), TwiddleWidth::Word(0xffff));
743        assert_eq!(classify(0x1_0000), TwiddleWidth::DoubleWord(0x1_0000));
744        assert_eq!(classify(0xffff_ffff), TwiddleWidth::DoubleWord(0xffff_ffff));
745        assert_eq!(classify(0x1_0000_0000), TwiddleWidth::Wide);
746        assert_eq!(classify(u128::MAX), TwiddleWidth::Wide);
747    }
748
749    #[test]
750    #[should_panic = "butterfly lengths differ"]
751    fn a_length_mismatch_is_refused() {
752        // A short upper half would leave the rest of the lower one untransformed.
753        let mut lo = [BinaryField32::ONE; 4];
754        let mut hi = [BinaryField32::ONE; 3];
755        BinaryField32::butterfly::<false>(&mut lo, &mut hi, BinaryField32::ONE);
756    }
757
758    proptest! {
759        #![proptest_config(ProptestConfig::with_cases(64))]
760
761        /// Random twiddles and lengths, over the levels the three kernels split between.
762        #[test]
763        fn random_twiddles_agree_with_the_element_loop(
764            bits in any::<u128>(),
765            len in 0usize..70,
766        ) {
767            let bytes = bits.to_le_bytes();
768            agrees::<BinaryField32>(len, BinaryField32::from_le_byte_iter(bytes.into_iter()))?;
769            agrees::<BinaryField64>(len, BinaryField64::from_le_byte_iter(bytes.into_iter()))?;
770            agrees::<BinaryField128>(len, BinaryField128::from_le_byte_iter(bytes.into_iter()))?;
771        }
772
773        /// The same, with the twiddle drawn from the one-byte subfield the byte map covers.
774        #[test]
775        fn byte_twiddles_agree_with_the_element_loop(t in any::<u8>(), len in 0usize..70) {
776            agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
777            agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
778            agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
779            agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
780            agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
781        }
782
783        /// The same at lengths that reach the register kernels.
784        #[test]
785        fn byte_twiddles_agree_on_runs_past_the_register_threshold(
786            t in any::<u8>(),
787            len in 0usize..600,
788        ) {
789            agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
790            agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
791            agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
792            agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
793            agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
794        }
795
796        /// The same, with the twiddle drawn from the two-byte subfield.
797        #[test]
798        fn word_twiddles_agree_with_the_element_loop(t in 0x100u16.., len in 0usize..70) {
799            agrees::<BinaryField16>(len, BinaryField16::from_repr(t))?;
800            agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
801            agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
802            agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
803        }
804
805        /// The same, with the twiddle drawn from the four-byte subfield.
806        #[test]
807        fn double_word_twiddles_agree_with_the_element_loop(
808            t in 0x1_0000u32..,
809            len in 0usize..70,
810        ) {
811            agrees::<BinaryField32>(len, BinaryField32::from_repr(t))?;
812            agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
813            agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
814        }
815    }
816}