use crate::checksum::common::tables::{CRC64_NVME_POLY, CRC64_XZ_POLY};
#[must_use]
const fn clmul64(a: u64, b: u64) -> (u64, u64) {
let mut hi: u64 = 0;
let mut lo: u64 = 0;
let mut i: u32 = 0;
while i < 64 {
if (a >> i) & 1 != 0 {
if i == 0 {
lo ^= b;
} else {
lo ^= b << i;
hi ^= b >> (64 - i);
}
}
i = i.strict_add(1);
}
(hi, lo)
}
#[must_use]
pub(crate) const fn reduce128(hi: u64, lo: u64, poly: u64) -> u64 {
let mut result_hi = hi;
let mut result_lo = lo;
let mut i: i32 = 63;
while i >= 0 {
if (result_hi >> i) & 1 != 0 {
if i == 0 {
result_lo ^= poly;
result_hi ^= 1;
} else {
result_lo ^= poly << i;
result_hi ^= (poly >> (64 - i)) | (1 << i);
}
}
i = i.strict_sub(1);
}
result_lo
}
#[must_use]
const fn xpow_mod(n: u32, poly: u64) -> u64 {
if n == 0 {
return 1;
}
if n == 1 {
return 2; }
let mut result: u64 = 1; let mut base: u64 = 2;
let mut exp = n;
while exp > 0 {
if exp & 1 != 0 {
let (hi, lo) = clmul64(result, base);
result = reduce128(hi, lo, poly);
}
let (hi, lo) = clmul64(base, base);
base = reduce128(hi, lo, poly);
exp = exp.strict_shr(1);
}
result
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Crc64ClmulConstants {
pub poly: u64,
pub mu: u64,
pub fold_128b: (u64, u64),
pub tail_fold_16b: [(u64, u64); 7],
pub fold_8b: u64,
}
#[must_use]
const fn reciprocal_poly(reflected_poly: u64) -> u64 {
(reflected_poly << 1) | 1
}
#[must_use]
const fn normal_poly(reflected_poly: u64) -> u64 {
reflected_poly.reverse_bits()
}
#[must_use]
const fn fold_k(normal_poly: u64, n: u32) -> u64 {
xpow_mod(n, normal_poly).reverse_bits()
}
#[must_use]
pub(crate) const fn fold16_coeff_for_bytes(reflected_poly: u64, shift_bytes: u32) -> (u64, u64) {
if shift_bytes == 0 {
return (0, 0);
}
let normal = normal_poly(reflected_poly);
let d = shift_bytes * 8;
(fold_k(normal, d - 1), fold_k(normal, d + 63))
}
impl Crc64ClmulConstants {
#[must_use]
pub const fn new(reflected_poly: u64) -> Self {
let poly = reciprocal_poly(reflected_poly);
let normal = normal_poly(reflected_poly);
let mu = compute_tikv_mu(poly);
Self {
poly,
mu,
fold_128b: (fold_k(normal, 1023), fold_k(normal, 1087)),
tail_fold_16b: [
(fold_k(normal, 895), fold_k(normal, 959)), (fold_k(normal, 767), fold_k(normal, 831)), (fold_k(normal, 639), fold_k(normal, 703)), (fold_k(normal, 511), fold_k(normal, 575)), (fold_k(normal, 383), fold_k(normal, 447)), (fold_k(normal, 255), fold_k(normal, 319)), (fold_k(normal, 127), fold_k(normal, 191)), ],
fold_8b: fold_k(normal, 127),
}
}
}
#[must_use]
const fn compute_tikv_mu(poly: u64) -> u64 {
let mut inv: u64 = 1;
let mut k: u32 = 1;
while k < 64 {
let mut s: u64 = 0;
let mut i: u32 = 1;
while i <= k {
let p_i = (poly >> i) & 1;
let q_j = (inv >> (k - i)) & 1;
s ^= p_i & q_j;
i = i.strict_add(1);
}
inv |= s << k;
k = k.strict_add(1);
}
inv
}
pub(crate) const CRC64_XZ_CLMUL: Crc64ClmulConstants = Crc64ClmulConstants::new(CRC64_XZ_POLY);
pub(crate) const CRC64_NVME_CLMUL: Crc64ClmulConstants = Crc64ClmulConstants::new(CRC64_NVME_POLY);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[derive(Clone, Copy, Debug)]
#[allow(dead_code)] pub(crate) struct Crc64StreamConstants {
pub fold_256b: (u64, u64),
pub fold_384b: (u64, u64),
pub fold_512b: (u64, u64),
pub fold_896b: (u64, u64),
pub fold_1024b: (u64, u64),
pub combine_4way: [(u64, u64); 3],
pub combine_7way: [(u64, u64); 6],
pub combine_8way: [(u64, u64); 7],
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl Crc64StreamConstants {
#[must_use]
pub const fn new(reflected_poly: u64) -> Self {
Self {
fold_256b: fold16_coeff_for_bytes(reflected_poly, 256),
fold_384b: fold16_coeff_for_bytes(reflected_poly, 384),
fold_512b: fold16_coeff_for_bytes(reflected_poly, 512),
fold_896b: fold16_coeff_for_bytes(reflected_poly, 896),
fold_1024b: fold16_coeff_for_bytes(reflected_poly, 1024),
combine_4way: [
fold16_coeff_for_bytes(reflected_poly, 384),
fold16_coeff_for_bytes(reflected_poly, 256),
fold16_coeff_for_bytes(reflected_poly, 128),
],
combine_7way: [
fold16_coeff_for_bytes(reflected_poly, 768),
fold16_coeff_for_bytes(reflected_poly, 640),
fold16_coeff_for_bytes(reflected_poly, 512),
fold16_coeff_for_bytes(reflected_poly, 384),
fold16_coeff_for_bytes(reflected_poly, 256),
fold16_coeff_for_bytes(reflected_poly, 128),
],
combine_8way: [
fold16_coeff_for_bytes(reflected_poly, 896),
fold16_coeff_for_bytes(reflected_poly, 768),
fold16_coeff_for_bytes(reflected_poly, 640),
fold16_coeff_for_bytes(reflected_poly, 512),
fold16_coeff_for_bytes(reflected_poly, 384),
fold16_coeff_for_bytes(reflected_poly, 256),
fold16_coeff_for_bytes(reflected_poly, 128),
],
}
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) const CRC64_XZ_STREAM: Crc64StreamConstants = Crc64StreamConstants::new(CRC64_XZ_POLY);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) const CRC64_NVME_STREAM: Crc64StreamConstants = Crc64StreamConstants::new(CRC64_NVME_POLY);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clmul64_basic() {
assert_eq!(clmul64(0, 12345), (0, 0));
assert_eq!(clmul64(12345, 0), (0, 0));
assert_eq!(clmul64(1, 0x1234), (0, 0x1234));
assert_eq!(clmul64(0x1234, 1), (0, 0x1234));
assert_eq!(clmul64(2, 2), (0, 4));
assert_eq!(clmul64(3, 3), (0, 5)); }
#[test]
fn test_clmul64_overflow() {
let a = 1u64 << 63;
let b = 2u64; let (hi, lo) = clmul64(a, b);
assert_eq!(hi, 1);
assert_eq!(lo, 0);
}
#[test]
fn test_xpow_mod_basic() {
let poly = CRC64_XZ_CLMUL.poly;
assert_eq!(xpow_mod(0, poly), 1);
assert_eq!(xpow_mod(1, poly), 2);
assert_eq!(xpow_mod(2, poly), 4);
let x64 = xpow_mod(64, poly);
assert_eq!(x64, poly);
}
#[test]
fn test_xpow_mod_xz_polynomial() {
let poly = CRC64_XZ_POLY;
assert_eq!(xpow_mod(64, poly), poly);
assert!(xpow_mod(128, poly) != 0);
assert!(xpow_mod(1024, poly) != 0);
}
#[test]
fn test_fold_constants_generated() {
assert_ne!(CRC64_XZ_CLMUL.poly, CRC64_NVME_CLMUL.poly);
assert_ne!(CRC64_XZ_CLMUL.mu, CRC64_NVME_CLMUL.mu);
}
#[test]
fn test_barrett_mu() {
assert_ne!(CRC64_XZ_CLMUL.mu, 0);
}
#[test]
fn test_polynomial_property() {
assert_eq!(xpow_mod(64, CRC64_XZ_CLMUL.poly), CRC64_XZ_CLMUL.poly);
assert_eq!(xpow_mod(64, CRC64_NVME_CLMUL.poly), CRC64_NVME_CLMUL.poly);
}
#[test]
fn test_reduce128_identity() {
let poly = CRC64_XZ_POLY;
assert_eq!(reduce128(0, 0x12345678, poly), 0x12345678);
assert_eq!(reduce128(0, poly - 1, poly), poly - 1);
}
#[test]
fn test_reduce128_single_bit() {
let poly = CRC64_XZ_POLY;
assert_eq!(reduce128(1, 0, poly), poly);
let x65 = reduce128(2, 0, poly);
let expected = xpow_mod(65, poly);
assert_eq!(x65, expected);
}
#[test]
fn test_constants_symmetry() {
assert_ne!(CRC64_XZ_CLMUL.fold_8b, 0);
}
#[test]
fn test_xz_constants_match_tikv() {
assert_eq!(CRC64_XZ_CLMUL.poly, 0x92d8_af2b_af0e_1e85);
assert_eq!(CRC64_XZ_CLMUL.mu, 0x9c3e_466c_1729_63d5);
assert_eq!(CRC64_XZ_CLMUL.fold_128b, (0xd7d8_6b2a_f73d_e740, 0x8757_d71d_4fcc_1000));
assert_eq!(
CRC64_XZ_CLMUL.tail_fold_16b,
[
(0x9478_74de_5950_52cb, 0x9e73_5cb5_9b47_24da), (0xe4ce_2cd5_5fea_0037, 0x2fe3_fd29_20ce_82ec), (0x0e31_d519_421a_63a5, 0x2e30_2032_12ca_c325), (0x081f_6054_a784_2df4, 0x6ae3_efbb_9dd4_41f3), (0x69a3_5d91_c373_0254, 0xb5ea_1af9_c013_aca4), (0x3be6_53a3_0fe1_af51, 0x6009_5b00_8a9e_fa44), (0xdabe_95af_c787_5f40, 0xe05d_d497_ca39_3ae4), ]
);
assert_eq!(CRC64_XZ_CLMUL.fold_8b, 0xdabe_95af_c787_5f40);
}
#[test]
fn test_nvme_constants_match_tikv() {
assert_eq!(CRC64_NVME_CLMUL.poly, 0x34d9_2653_5897_936b);
assert_eq!(CRC64_NVME_CLMUL.mu, 0x27ec_fa32_9aef_9f77);
assert_eq!(
CRC64_NVME_CLMUL.fold_128b,
(0x5f85_2fb6_1e8d_92dc, 0xa1ca_681e_733f_9c40)
);
assert_eq!(
CRC64_NVME_CLMUL.tail_fold_16b,
[
(0x9465_8840_3d4a_dcbc, 0xd083_dd59_4d96_319d), (0x34f5_a24e_22d6_6e90, 0x3c25_5f5e_bc41_4423), (0x0336_3823_e6e7_91e5, 0x7b0a_b10d_d0f8_09fe), (0x6224_2240_ace5_045a, 0x0c32_cdb3_1e18_a84a), (0xa3ff_dc1f_e8e8_2a8b, 0xbdd7_ac0e_e1a4_a0f0), (0xe1e0_bb9d_45d7_a44c, 0xb0bc_2e58_9204_f500), (0x21e9_761e_2526_21ac, 0xeadc_41fd_2ba3_d420), ]
);
assert_eq!(CRC64_NVME_CLMUL.fold_8b, 0x21e9_761e_2526_21ac);
}
}