p3-binary-field 0.8.0

Characteristic-2 tower fields GF(2) ⊂ GF(4) ⊂ … ⊂ GF(2^128) for Plonky3
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
//! The packing of the polynomial-basis `GF(2^128)` over the wide carryless multiply.
//!
//! `VPCLMULQDQ` applies the carryless multiply to every 128-bit lane of a wide register.
//! One field element is exactly one lane, so the scalar kernel becomes the packed one.
//!
//! Every other operation is exclusive or, a lane-local shift, or a permutation of lanes.
//!
//! Only the widest register the target supports is compiled.
//! One width per build means everything below the lane wrappers is written once.

use core::iter::{Product, Sum};
use core::mem::transmute;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};

use p3_field::op_assign_macros::{
    impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
    impl_packed_field_div, impl_packed_value, impl_rng, impl_sub_assign, impl_sub_base_field,
    impl_sum_prod_base_field, ring_sum,
};
use p3_field::{
    Algebra, Field, PackedField, PackedFieldPow2, PackedValue, PrimeCharacteristicRing,
};
use rand::distr::{Distribution, StandardUniform};
use rand::{Rng, RngExt};

use super::split::{HIGH_BY_HIGH, LOW_BY_LOW, Lanes, fold_shifted};
use crate::gf2::characteristic_two_methods;
use crate::{BinaryField128, Gf2, Ghash128};

/// Swaps the two quadwords of every lane, so `x ^ swap(x)` holds `x_lo ^ x_hi` in both halves.
const SWAP_QUADWORDS: i32 = 0x4e;

/// The 256-bit register, holding two field elements.
#[cfg(all(
    target_feature = "avx2",
    target_feature = "vpclmulqdq",
    not(target_feature = "avx512f")
))]
pub(crate) mod lanes {
    use core::arch::x86_64::{
        __m256i, _mm_set_epi64x, _mm256_broadcastsi128_si256, _mm256_clmulepi64_epi128,
        _mm256_loadu_si256, _mm256_setzero_si256, _mm256_shuffle_epi32, _mm256_storeu_si256,
        _mm256_unpacklo_epi64, _mm256_xor_si256,
    };

    use p3_field::interleave::interleave_u128;

    /// The register one packed value occupies.
    pub(crate) type Reg = __m256i;

    /// Field elements per register.
    pub(crate) const WIDTH: usize = 2;

    // SAFETY for every wrapper below: this module is compiled only when the target features
    // its intrinsics require are enabled for the crate, which is what makes each call sound.

    /// All lanes zero.
    #[inline(always)]
    pub(crate) fn zero() -> Reg {
        unsafe { _mm256_setzero_si256() }
    }

    /// Bitwise exclusive or.
    #[inline(always)]
    pub(crate) fn xor(a: Reg, b: Reg) -> Reg {
        unsafe { _mm256_xor_si256(a, b) }
    }

    /// The low quadword of each operand, paired within each lane.
    #[inline(always)]
    pub(crate) fn unpack_low_64(a: Reg, b: Reg) -> Reg {
        unsafe { _mm256_unpacklo_epi64(a, b) }
    }

    /// The carryless product of one quadword of each operand, in every lane.
    #[inline(always)]
    pub(crate) fn clmul<const IMM: i32>(a: Reg, b: Reg) -> Reg {
        unsafe { _mm256_clmulepi64_epi128::<IMM>(a, b) }
    }

    /// Exchanges the two halves of every lane.
    #[inline(always)]
    pub(crate) fn swap_halves(a: Reg) -> Reg {
        unsafe { _mm256_shuffle_epi32::<{ super::SWAP_QUADWORDS }>(a) }
    }

    /// The same field element in every lane.
    #[inline(always)]
    pub(crate) fn broadcast(value: u128) -> Reg {
        unsafe {
            // Arguments run from the highest quadword down.
            let lane = _mm_set_epi64x((value >> 64) as i64, value as i64);
            _mm256_broadcastsi128_si256(lane)
        }
    }

    /// Reads one register of consecutive field elements.
    ///
    /// # Safety
    ///
    /// The address must be readable for as many elements as one register holds.
    /// No alignment is required.
    #[inline(always)]
    pub(crate) unsafe fn load(from: *const u128) -> Reg {
        // SAFETY: the readability of the address is the caller's obligation.
        unsafe { _mm256_loadu_si256(from.cast()) }
    }

    /// Writes one register of consecutive field elements.
    ///
    /// # Safety
    ///
    /// The address must be writable for as many elements as one register holds.
    /// No alignment is required.
    #[inline(always)]
    pub(crate) unsafe fn store(to: *mut u128, value: Reg) {
        // SAFETY: the writability of the address is the caller's obligation.
        unsafe { _mm256_storeu_si256(to.cast(), value) }
    }

    /// Interleaves whole field elements between two registers.
    ///
    /// # Panics
    /// Panics if the block length does not divide the width.
    #[inline(always)]
    pub(crate) fn interleave(a: Reg, b: Reg, block_len: usize) -> (Reg, Reg) {
        match block_len {
            1 => interleave_u128(a, b),
            WIDTH => (a, b),
            _ => panic!("unsupported block_len"),
        }
    }
}

/// The 512-bit register, holding four field elements.
#[cfg(all(target_feature = "avx512f", target_feature = "vpclmulqdq"))]
pub(crate) mod lanes {
    use core::arch::x86_64::{
        __m512i, _mm_set_epi64x, _mm512_broadcast_i32x4, _mm512_clmulepi64_epi128,
        _mm512_loadu_si512, _mm512_setzero_si512, _mm512_shuffle_epi32, _mm512_storeu_si512,
        _mm512_unpacklo_epi64, _mm512_xor_si512,
    };

    use p3_field::interleave::{interleave_u128, interleave_u256};

    /// The register one packed value occupies.
    pub(crate) type Reg = __m512i;

    /// Field elements per register.
    pub(crate) const WIDTH: usize = 4;

    // SAFETY for every wrapper below: this module is compiled only when the target features
    // its intrinsics require are enabled for the crate, which is what makes each call sound.

    /// All lanes zero.
    #[inline(always)]
    pub(crate) fn zero() -> Reg {
        unsafe { _mm512_setzero_si512() }
    }

    /// Bitwise exclusive or.
    #[inline(always)]
    pub(crate) fn xor(a: Reg, b: Reg) -> Reg {
        unsafe { _mm512_xor_si512(a, b) }
    }

    /// The low quadword of each operand, paired within each lane.
    #[inline(always)]
    pub(crate) fn unpack_low_64(a: Reg, b: Reg) -> Reg {
        unsafe { _mm512_unpacklo_epi64(a, b) }
    }

    /// The carryless product of one quadword of each operand, in every lane.
    #[inline(always)]
    pub(crate) fn clmul<const IMM: i32>(a: Reg, b: Reg) -> Reg {
        unsafe { _mm512_clmulepi64_epi128::<IMM>(a, b) }
    }

    /// Exchanges the two halves of every lane.
    #[inline(always)]
    pub(crate) fn swap_halves(a: Reg) -> Reg {
        unsafe { _mm512_shuffle_epi32::<{ super::SWAP_QUADWORDS }>(a) }
    }

    /// The same field element in every lane.
    #[inline(always)]
    pub(crate) fn broadcast(value: u128) -> Reg {
        unsafe {
            // Arguments run from the highest quadword down.
            let lane = _mm_set_epi64x((value >> 64) as i64, value as i64);
            _mm512_broadcast_i32x4(lane)
        }
    }

    /// Reads one register of consecutive field elements.
    ///
    /// # Safety
    ///
    /// The address must be readable for as many elements as one register holds.
    /// No alignment is required.
    #[inline(always)]
    pub(crate) unsafe fn load(from: *const u128) -> Reg {
        // SAFETY: the readability of the address is the caller's obligation.
        unsafe { _mm512_loadu_si512(from.cast()) }
    }

    /// Writes one register of consecutive field elements.
    ///
    /// # Safety
    ///
    /// The address must be writable for as many elements as one register holds.
    /// No alignment is required.
    #[inline(always)]
    pub(crate) unsafe fn store(to: *mut u128, value: Reg) {
        // SAFETY: the writability of the address is the caller's obligation.
        unsafe { _mm512_storeu_si512(to.cast(), value) }
    }

    /// Interleaves whole field elements between two registers.
    ///
    /// # Panics
    /// Panics if the block length does not divide the width.
    #[inline(always)]
    pub(crate) fn interleave(a: Reg, b: Reg, block_len: usize) -> (Reg, Reg) {
        match block_len {
            1 => interleave_u128(a, b),
            2 => interleave_u256(a, b),
            WIDTH => (a, b),
            _ => panic!("unsupported block_len"),
        }
    }
}

use lanes::WIDTH;

// The register is the production backend of the shared split-multiplier algebra.
//
// Every method is one intrinsic.
//
// So the generic code monomorphizes to the instructions a hand-written kernel would emit.
impl Lanes for lanes::Reg {
    #[inline(always)]
    fn zero() -> Self {
        lanes::zero()
    }

    #[inline(always)]
    fn broadcast(value: u128) -> Self {
        lanes::broadcast(value)
    }

    #[inline(always)]
    fn xor(self, other: Self) -> Self {
        lanes::xor(self, other)
    }

    #[inline(always)]
    fn unpack_low_64(self, other: Self) -> Self {
        lanes::unpack_low_64(self, other)
    }

    #[inline(always)]
    fn clmul<const IMM: i32>(self, other: Self) -> Self {
        lanes::clmul::<IMM>(self, other)
    }
}

/// Several elements of the polynomial-basis `GF(2^128)`, one per 128-bit lane of a register.
///
/// Two under `AVX2`, four under `AVX-512`.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
// Needed to make the transmutes below sound.
#[repr(transparent)]
#[must_use]
pub struct PackedGhash128([Ghash128; WIDTH]);

impl PackedGhash128 {
    /// The register holding these elements.
    #[inline]
    #[must_use]
    fn to_vector(self) -> lanes::Reg {
        // SAFETY: the scalar is `repr(transparent)` over `u128`, so the array is `WIDTH`
        // contiguous `u128` values, which is the register's own layout.
        // This type is `repr(transparent)` over that array.
        unsafe { transmute(self) }
    }

    /// The elements held in a register.
    #[inline]
    fn from_vector(vector: lanes::Reg) -> Self {
        // SAFETY: the inverse of the transmute above.
        // Every bit pattern is a valid element, so no value can be out of range.
        unsafe { transmute(vector) }
    }

    /// The same element in every lane.
    #[inline]
    const fn broadcast(value: Ghash128) -> Self {
        Self([value; WIDTH])
    }
}

impl From<Ghash128> for PackedGhash128 {
    #[inline]
    fn from(value: Ghash128) -> Self {
        Self::broadcast(value)
    }
}

impl Add for PackedGhash128 {
    type Output = Self;

    #[inline]
    #[allow(clippy::suspicious_arithmetic_impl)]
    fn add(self, rhs: Self) -> Self {
        // Addition in characteristic 2 is `XOR`, lane by lane.
        Self::from_vector(lanes::xor(self.to_vector(), rhs.to_vector()))
    }
}

impl Sub for PackedGhash128 {
    type Output = Self;

    #[inline]
    #[allow(clippy::suspicious_arithmetic_impl)]
    fn sub(self, rhs: Self) -> Self {
        // Subtraction coincides with addition in characteristic 2.
        self + rhs
    }
}

impl Neg for PackedGhash128 {
    type Output = Self;

    #[inline]
    fn neg(self) -> Self {
        // `-x = x` in characteristic 2.
        self
    }
}

impl Mul for PackedGhash128 {
    type Output = Self;

    #[inline]
    fn mul(self, rhs: Self) -> Self {
        let (x, y) = (self.to_vector(), rhs.to_vector());

        // The two diagonal half products.
        let low = lanes::clmul::<LOW_BY_LOW>(x, y);
        let high = lanes::clmul::<HIGH_BY_HIGH>(x, y);

        // Karatsuba reaches the middle coefficient with one product instead of two.
        //
        //     middle = (a0 + a1)(b0 + b1) + a0 b0 + a1 b1
        //
        // The scalar kernel takes the schoolbook form instead.
        // A wide carryless multiply has half the throughput of the 128-bit one on Zen 4 and
        // Zen 5, so trading a product for two shuffles and three exclusive ors pays only here.
        //
        // Measured on Zen 5, four lanes: 0.47 ns per element against 0.55 for schoolbook.
        let mixed_x = lanes::xor(x, lanes::swap_halves(x));
        let mixed_y = lanes::xor(y, lanes::swap_halves(y));
        let middle = lanes::xor(
            lanes::xor(low, high),
            lanes::clmul::<LOW_BY_LOW>(mixed_x, mixed_y),
        );

        // Inner fold brings the top limb down, outer fold finishes the reduction.
        Self::from_vector(fold_shifted(low, fold_shifted(middle, high)))
    }
}

impl PrimeCharacteristicRing for PackedGhash128 {
    type PrimeSubfield = Gf2;

    const ZERO: Self = Self::broadcast(Ghash128::ZERO);
    const ONE: Self = Self::broadcast(Ghash128::ONE);
    // The characteristic is 2, so `TWO = ONE + ONE = ZERO`.
    const TWO: Self = Self::broadcast(Ghash128::ZERO);
    // The characteristic is 2, so `NEG_ONE = ONE`.
    const NEG_ONE: Self = Self::broadcast(Ghash128::ONE);

    #[inline]
    fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
        Self::broadcast(Ghash128::from_prime_subfield(f))
    }

    characteristic_two_methods!();

    #[inline]
    fn square(&self) -> Self {
        let x = self.to_vector();

        // The cross term of `(p0 + p1 x^64)^2` doubles to zero, so the square has no middle
        // coefficient and the inner fold has nothing to add to.
        let low = lanes::clmul::<LOW_BY_LOW>(x, x);
        let high = lanes::clmul::<HIGH_BY_HIGH>(x, x);

        Self::from_vector(fold_shifted(low, fold_shifted(lanes::zero(), high)))
    }

    /// `x·(x - 1) = x² - x = x² + x` in characteristic 2, and [`Self::square`] skips the
    /// cross-term carryless multiplies a general product pays for.
    #[inline]
    fn bool_check(&self) -> Self {
        self.square() + *self
    }

    #[inline]
    fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
        let (mut low, mut high, mut middle) = (lanes::zero(), lanes::zero(), lanes::zero());

        for (a, b) in u.iter().zip(v) {
            let (x, y) = (a.to_vector(), b.to_vector());

            // Accumulate the three polynomial coefficients without reducing.
            low = lanes::xor(low, lanes::clmul::<LOW_BY_LOW>(x, y));
            high = lanes::xor(high, lanes::clmul::<HIGH_BY_HIGH>(x, y));

            // Karatsuba's third product, from the sum of each operand's halves.
            let mixed_x = lanes::xor(x, lanes::swap_halves(x));
            let mixed_y = lanes::xor(y, lanes::swap_halves(y));
            middle = lanes::xor(middle, lanes::clmul::<LOW_BY_LOW>(mixed_x, mixed_y));
        }

        // Remove the diagonal contributions from the accumulated third product.
        middle = lanes::xor(middle, lanes::xor(low, high));

        // Reduction is linear, so the whole sum folds the modulus once.
        Self::from_vector(fold_shifted(low, fold_shifted(middle, high)))
    }
}

impl_add_assign!(PackedGhash128);
impl_sub_assign!(PackedGhash128);
impl_mul_methods!(PackedGhash128);
ring_sum!(PackedGhash128);
impl_rng!(PackedGhash128);

impl_add_base_field!(PackedGhash128, Ghash128);
impl_sub_base_field!(PackedGhash128, Ghash128);
impl_mul_base_field!(PackedGhash128, Ghash128);
impl_div_methods!(PackedGhash128, Ghash128);
impl_packed_field_div!(PackedGhash128);
impl_sum_prod_base_field!(PackedGhash128, Ghash128);

impl Algebra<Ghash128> for PackedGhash128 {}

impl From<Gf2> for PackedGhash128 {
    /// `GF(2)` is the prime subfield, embedded as `{ZERO, ONE}` in every lane.
    #[inline]
    fn from(x: Gf2) -> Self {
        Self::from_prime_subfield(x)
    }
}

impl_add_base_field!(PackedGhash128, Gf2);
impl_sub_base_field!(PackedGhash128, Gf2);
impl_mul_base_field!(PackedGhash128, Gf2);

impl Algebra<Gf2> for PackedGhash128 {}

impl From<BinaryField128> for PackedGhash128 {
    /// The same field element, seen in the polynomial basis, in every lane.
    ///
    /// The change of basis is a field isomorphism, so it makes this packing an algebra over
    /// the tower, exactly as it does for one lane's [`Ghash128`].
    #[inline]
    fn from(x: BinaryField128) -> Self {
        Self::broadcast(Ghash128::from(x))
    }
}

impl_add_base_field!(PackedGhash128, BinaryField128);
impl_sub_base_field!(PackedGhash128, BinaryField128);
impl_mul_base_field!(PackedGhash128, BinaryField128);

impl Algebra<BinaryField128> for PackedGhash128 {}

impl_packed_value!(PackedGhash128, Ghash128, WIDTH);

// SAFETY: the transparent array satisfies the packed layout contract.
// Arithmetic acts independently on each 128-bit field element.
unsafe impl PackedField for PackedGhash128 {
    type Scalar = Ghash128;
}

// SAFETY: the width is two or four, both powers of two.
unsafe impl PackedFieldPow2 for PackedGhash128 {
    /// # Panics
    /// Panics if the block length does not divide the width.
    #[inline]
    fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) {
        let (a, b) = lanes::interleave(self.to_vector(), other.to_vector(), block_len);
        (Self::from_vector(a), Self::from_vector(b))
    }
}

#[cfg(test)]
mod tests {
    use p3_field::{PackedValue, PrimeCharacteristicRing};
    use p3_field_testing::test_packed_binary_field;
    use proptest::prelude::*;

    use super::lanes::{self, WIDTH};
    use crate::packed::split::model::Model;
    use crate::packed::split::{
        HIGH_BY_HIGH, HIGH_BY_LOW, LOW_BY_HIGH, LOW_BY_LOW, Lanes, SplitScalar, fold_shifted,
    };
    use crate::{Ghash128, PackedGhash128};

    /// The bit patterns a random search is unlikely to reach.
    ///
    /// Each one drives the fold of the modulus to an extreme:
    ///
    /// ```text
    ///     0            nothing to reduce
    ///     all ones     every coefficient of the product is live
    ///     x^127        the highest degree, so the fold spills furthest
    ///     0x87         the modulus tail itself
    /// ```
    const SPECIAL: [u128; 4] = [0, u128::MAX, 1 << 127, 0x87];

    /// One extreme bit pattern per lane.
    fn specials() -> PackedGhash128 {
        PackedValue::from_fn(|i| Ghash128::from_le_bytes(SPECIAL[i].to_le_bytes()))
    }

    /// The elements a register holds, read back one per lane.
    fn lanes_of(register: lanes::Reg) -> [u128; WIDTH] {
        let mut out = [0u128; WIDTH];

        // SAFETY: the destination is exactly one register of contiguous 128-bit integers.
        //
        // The store is the unaligned form, so the array's own alignment is irrelevant.
        unsafe { lanes::store(out.as_mut_ptr(), register) };

        out
    }

    /// A register holding the given elements, one per lane.
    fn register_of(values: [u128; WIDTH]) -> lanes::Reg {
        // SAFETY: the source is exactly one register of contiguous 128-bit integers.
        unsafe { lanes::load(values.as_ptr()) }
    }

    /// The two 64-bit halves of every lane, exchanged.
    fn swapped_halves(values: [u128; WIDTH]) -> [u128; WIDTH] {
        // Rotating a 128-bit value by half its width is exactly the exchange.
        core::array::from_fn(|i| values[i].rotate_left(64))
    }

    /// Each lane-local operation on the register, against the same operation on the model.
    ///
    /// One assertion per operation, so a mismatch names the intrinsic that disagreed.
    ///
    /// The whole-element interleave is the one left out, since it crosses lanes by design.
    fn lanes_conform(
        a: [u128; WIDTH],
        b: [u128; WIDTH],
        scalar: u128,
    ) -> Result<(), TestCaseError> {
        let (x, y) = (register_of(a), register_of(b));
        let (mx, my) = (Model(a), Model(b));

        prop_assert_eq!(lanes_of(lanes::Reg::zero()), Model::<WIDTH>::zero().0);
        prop_assert_eq!(
            lanes_of(lanes::Reg::broadcast(scalar)),
            Model::<WIDTH>::broadcast(scalar).0
        );
        prop_assert_eq!(lanes_of(lanes::Reg::tail()), Model::<WIDTH>::tail().0);
        prop_assert_eq!(lanes_of(x.xor(y)), mx.xor(my).0);
        prop_assert_eq!(lanes_of(x.unpack_low_64(y)), mx.unpack_low_64(my).0);
        prop_assert_eq!(
            lanes_of(x.clmul::<LOW_BY_LOW>(y)),
            mx.clmul::<LOW_BY_LOW>(my).0
        );
        prop_assert_eq!(
            lanes_of(x.clmul::<HIGH_BY_LOW>(y)),
            mx.clmul::<HIGH_BY_LOW>(my).0
        );
        prop_assert_eq!(
            lanes_of(x.clmul::<LOW_BY_HIGH>(y)),
            mx.clmul::<LOW_BY_HIGH>(my).0
        );
        prop_assert_eq!(
            lanes_of(x.clmul::<HIGH_BY_HIGH>(y)),
            mx.clmul::<HIGH_BY_HIGH>(my).0
        );

        // Not a trait method, so the model cannot supply the expectation.
        //
        // It carries the only hand-written shuffle immediate here, which is why it is pinned.
        prop_assert_eq!(lanes_of(lanes::swap_halves(x)), swapped_halves(a));

        // The two composites built from those operations.
        //
        // A lane-crossing slip therefore shows up here as well as in the primitives above.
        prop_assert_eq!(lanes_of(fold_shifted(x, y)), fold_shifted(mx, my).0);
        prop_assert_eq!(
            lanes_of(SplitScalar::new(scalar).apply(x)),
            SplitScalar::new(scalar).apply(mx).0
        );

        Ok(())
    }

    #[test]
    fn the_register_matches_the_scalar_model_at_the_corners() {
        // Invariant: each operation is lane-local, so distinct lanes must stay distinct.
        //
        // Lane 0 carries the pair under test, so all sixteen combinations are reached.
        //
        // That includes the squaring-shaped case where both operands are the same value.
        //
        // The other lanes rotate through the corners, so a value that crosses a lane
        // boundary lands on a different extreme and shows as a mismatch.
        for (i, &x) in SPECIAL.iter().enumerate() {
            for (j, &y) in SPECIAL.iter().enumerate() {
                let a = core::array::from_fn(|lane| match lane {
                    0 => x,
                    _ => SPECIAL[(i + lane) % SPECIAL.len()],
                });
                let b = core::array::from_fn(|lane| match lane {
                    0 => y,
                    _ => SPECIAL[(j + lane) % SPECIAL.len()],
                });

                // The multiplier walks the corners too, so the split runs from each extreme.
                for scalar in SPECIAL {
                    lanes_conform(a, b, scalar)
                        .unwrap_or_else(|e| panic!("a {x:#x}, b {y:#x}, scalar {scalar:#x}: {e}"));
                }
            }
        }
    }

    proptest! {
        #![proptest_config(ProptestConfig::with_cases(256))]

        #[test]
        fn the_register_matches_the_scalar_model(
            a in prop::array::uniform(any::<u128>()),
            b in prop::array::uniform(any::<u128>()),
            scalar in any::<u128>(),
        ) {
            // The seam the scalar model alone cannot reach.
            //
            // Each intrinsic must be the one the algebra was written against, lane by lane.
            lanes_conform(a, b, scalar)?;
        }
    }

    /// The squaring shortcut must answer what the general product answers, lane by lane.
    ///
    /// `bool_check` is taken on every booleanity constraint an AIR states, so the register
    /// takes the shortcut the scalar already does. The corners cover the two roots the check
    /// exists to accept, and the patterns whose reduction is extreme.
    #[test]
    fn bool_check_matches_the_general_product_in_every_lane() {
        for (index, &value) in SPECIAL.iter().enumerate() {
            // Lane 0 carries the value under test; the rest rotate through the corners, so a
            // result that crossed a lane boundary lands on a different extreme.
            let lanes: [Ghash128; WIDTH] = core::array::from_fn(|lane| {
                let pattern = if lane == 0 {
                    value
                } else {
                    SPECIAL[(index + lane) % SPECIAL.len()]
                };
                Ghash128::from_le_bytes(pattern.to_le_bytes())
            });
            let packed: PackedGhash128 = PackedValue::from_fn(|lane| lanes[lane]);

            for (lane, &scalar) in lanes.iter().enumerate() {
                assert_eq!(
                    packed.bool_check().as_slice()[lane],
                    scalar * (scalar - Ghash128::ONE),
                    "lane {lane} of corner {value:#x}"
                );
                assert_eq!(packed.bool_check().as_slice()[lane], scalar.bool_check());
            }
        }

        // Zero and one are the roots the check accepts, so both must vanish in every lane.
        for root in [Ghash128::ZERO, Ghash128::ONE] {
            let packed: PackedGhash128 = PackedValue::from_fn(|_| root);
            assert!(
                packed
                    .bool_check()
                    .as_slice()
                    .iter()
                    .all(|&value| value == Ghash128::ZERO)
            );
        }
    }

    test_packed_binary_field!(
        crate::PackedGhash128,
        &[crate::PackedGhash128::ZERO],
        &[crate::PackedGhash128::ONE],
        super::specials()
    );
}