use core::slice;
use super::packing::{PackedGf2, block_mask};
use super::underlier::{Underlier, Word};
#[inline]
const fn words_of<U: Underlier>(rows: &mut [PackedGf2<U>]) -> &mut [U::Word] {
const {
assert!(size_of::<PackedGf2<U>>() == U::WORDS * size_of::<U::Word>());
assert!(align_of::<PackedGf2<U>>() >= align_of::<U::Word>());
}
let len = rows.len() * U::WORDS;
unsafe { slice::from_raw_parts_mut(rows.as_mut_ptr().cast::<U::Word>(), len) }
}
#[allow(clippy::chunks_exact_to_as_chunks)]
#[inline(always)]
fn stage<U: Underlier, const STEP: usize>(group: &mut [PackedGf2<U>]) {
if STEP >= <U::Word as Word>::BITS {
return;
}
let mask = U::Word::from_u64(block_mask(STEP));
for run in group.chunks_exact_mut(2 * STEP) {
let (top, bottom) = run.split_at_mut(STEP);
for (a, b) in words_of(top).iter_mut().zip(words_of(bottom)) {
let diff = ((*a >> STEP) ^ *b) & mask;
*a = *a ^ (diff << STEP);
*b = *b ^ diff;
}
}
}
impl<U: Underlier> PackedGf2<U> {
pub fn transpose(rows: &mut [Self]) {
assert_eq!(
rows.len(),
Self::WIDTH,
"a bit transpose needs one row per lane"
);
let block = Self::WORD_BITS;
for group in rows.chunks_exact_mut(block) {
stage::<U, 32>(group);
stage::<U, 16>(group);
stage::<U, 8>(group);
stage::<U, 4>(group);
stage::<U, 2>(group);
stage::<U, 1>(group);
}
for i in 0..U::WORDS {
for j in (i + 1)..U::WORDS {
let (above, below) = rows.split_at_mut(block * j);
for r in 0..block {
let upper = above[block * i + r].words()[j];
let lower = below[r].words()[i];
above[block * i + r].words_mut()[j] = lower;
below[r].words_mut()[i] = upper;
}
}
}
}
}
#[cfg(test)]
mod tests {
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;
use crate::{Gf2, PackedGf2x8, PackedGf2x64, PackedGf2x128};
macro_rules! transpose_tests {
($module:ident, $name:ident, $underlier:ty, $word:ty, $words:literal, $width:literal) => {
mod $module {
use super::*;
use crate::bitslice::underlier::Underlier;
use crate::$name;
fn from_words(words: [$word; $words]) -> $name {
$name::new(<$underlier>::from_words_fn(|i| words[i]))
}
fn reference(rows: &[$name; $width]) -> [$name; $width] {
core::array::from_fn(|r| $name::from_fn(|c| rows[c].get(r)))
}
fn fixture() -> [$name; $width] {
core::array::from_fn(|r| {
$name::from_fn(|c| Gf2::from_bool((r * 7 + c * 13 + r * c) % 3 == 0))
})
}
#[test]
fn transpose_matches_the_definition() {
let mut rows = fixture();
let expected = reference(&rows);
$name::transpose(&mut rows);
assert_eq!(rows, expected);
}
#[test]
fn transposing_twice_is_the_identity() {
let original = fixture();
let mut rows = original;
$name::transpose(&mut rows);
$name::transpose(&mut rows);
assert_eq!(rows, original);
}
#[test]
fn the_extremes_are_fixed_or_swapped_as_expected() {
let mut zeros = [$name::ZERO; $width];
$name::transpose(&mut zeros);
assert_eq!(zeros, [$name::ZERO; $width]);
let mut ones = [$name::ONE; $width];
$name::transpose(&mut ones);
assert_eq!(ones, [$name::ONE; $width]);
let mut corner = [$name::ZERO; $width];
corner[0].set($width - 1, Gf2::ONE);
$name::transpose(&mut corner);
let mut expected = [$name::ZERO; $width];
expected[$width - 1].set(0, Gf2::ONE);
assert_eq!(corner, expected);
}
#[test]
fn the_identity_matrix_is_its_own_transpose() {
let identity: [$name; $width] =
core::array::from_fn(|r| $name::from_fn(|c| Gf2::from_bool(r == c)));
let mut rows = identity;
$name::transpose(&mut rows);
assert_eq!(rows, identity);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn transpose_matches_the_definition_on_random_matrices(
seed in any::<u64>(),
) {
let mut state = seed | 1;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let mut rows: [$name; $width] = core::array::from_fn(|_| {
from_words(core::array::from_fn(|_| next() as $word))
});
let expected = reference(&rows);
$name::transpose(&mut rows);
prop_assert_eq!(rows, expected);
}
}
}
};
}
transpose_tests!(x8, PackedGf2x8, u8, u8, 1, 8);
transpose_tests!(x16, PackedGf2x16, u16, u16, 1, 16);
transpose_tests!(x32, PackedGf2x32, u32, u32, 1, 32);
transpose_tests!(x64, PackedGf2x64, u64, u64, 1, 64);
transpose_tests!(x128, PackedGf2x128, crate::M128, u64, 2, 128);
transpose_tests!(x256, PackedGf2x256, crate::M256, u64, 4, 256);
transpose_tests!(x512, PackedGf2x512, crate::M512, u64, 8, 512);
#[test]
fn a_hand_written_eight_by_eight_transposes_as_drawn() {
let input = [
0b1111_1111u8,
0b0000_0000,
0b0000_0000,
0b0000_1000,
0b0000_0000,
0b0000_0000,
0b0000_0000,
0b1111_1110,
];
let expected = [
0b0000_0001u8,
0b1000_0001,
0b1000_0001,
0b1000_1001,
0b1000_0001,
0b1000_0001,
0b1000_0001,
0b1000_0001,
];
let mut rows: [PackedGf2x8; 8] = core::array::from_fn(|r| PackedGf2x8::new(input[r]));
PackedGf2x8::transpose(&mut rows);
let got: [u8; 8] = core::array::from_fn(|r| rows[r].words()[0]);
assert_eq!(got, expected);
}
#[test]
fn the_transpose_reads_lanes_the_way_the_packings_write_them() {
for r in [0usize, 1, 31, 62, 63] {
for c in [0usize, 1, 32, 63] {
let mut rows = [PackedGf2x64::ZERO; 64];
rows[r].set(c, Gf2::ONE);
PackedGf2x64::transpose(&mut rows);
let mut expected = [PackedGf2x64::ZERO; 64];
expected[c].set(r, Gf2::ONE);
assert_eq!(rows, expected, "entry ({r}, {c})");
}
}
}
#[test]
fn a_wide_transpose_moves_entries_across_word_boundaries() {
let mut rows = [PackedGf2x128::ZERO; 128];
rows[10].set(100, Gf2::ONE);
PackedGf2x128::transpose(&mut rows);
let mut expected = [PackedGf2x128::ZERO; 128];
expected[100].set(10, Gf2::ONE);
assert_eq!(rows, expected);
}
}