const SHORTEST: usize = 64;
#[cfg(target_arch = "x86_64")]
pub(crate) fn crc32_continue(previous: u32, data: &[u8]) -> Option<u32> {
if data.len() < SHORTEST
|| !std::arch::is_x86_feature_detected!("pclmulqdq")
|| !std::arch::is_x86_feature_detected!("sse4.1")
{
return None;
}
Some(unsafe { x86::fold(previous, data) })
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn crc32_continue(previous: u32, data: &[u8]) -> Option<u32> {
if data.len() < SHORTEST || !std::arch::is_aarch64_feature_detected!("crc") {
return None;
}
Some(unsafe { arm::crc(previous, data) })
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub(crate) fn crc32_continue(_previous: u32, _data: &[u8]) -> Option<u32> {
None
}
#[cfg(target_arch = "x86_64")]
mod x86 {
use std::arch::x86_64::{
__m128i, _mm_and_si128, _mm_clmulepi64_si128, _mm_cvtsi32_si128, _mm_extract_epi32,
_mm_loadu_si128, _mm_set_epi32, _mm_set_epi64x, _mm_srli_si128, _mm_xor_si128,
};
const K1: i64 = 0x1_5444_2bd4;
const K2: i64 = 0x1_c6e4_1596;
const K3: i64 = 0x1_7519_97d0;
const K4: i64 = 0x0_ccaa_009e;
const K5: i64 = 0x1_63cd_6124;
const P_X: i64 = 0x1_DB71_0641;
const U_PRIME: i64 = 0x1_F701_1641;
#[target_feature(enable = "pclmulqdq,sse4.1")]
pub(super) unsafe fn fold(previous: u32, data: &[u8]) -> u32 {
let (blocks, tail) = data.as_chunks::<16>();
let (first, rest) = blocks.split_at(blocks.len().min(4));
let [b0, b1, b2, b3] = first else {
return crate::checksum::crc32_continue_table(previous, data);
};
let mut x3 = _mm_xor_si128(load(b0), _mm_cvtsi32_si128(!previous as i32));
let mut x2 = load(b1);
let mut x1 = load(b2);
let mut x0 = load(b3);
let k1k2 = _mm_set_epi64x(K2, K1);
let groups = rest.chunks_exact(4);
let singles = groups.remainder();
for group in groups {
if let [g0, g1, g2, g3] = group {
x3 = reduce(x3, load(g0), k1k2);
x2 = reduce(x2, load(g1), k1k2);
x1 = reduce(x1, load(g2), k1k2);
x0 = reduce(x0, load(g3), k1k2);
}
}
let k3k4 = _mm_set_epi64x(K4, K3);
let mut x = reduce(x3, x2, k3k4);
x = reduce(x, x1, k3k4);
x = reduce(x, x0, k3k4);
for block in singles {
x = reduce(x, load(block), k3k4);
}
let low32 = _mm_set_epi32(0, 0, 0, -1);
let x = _mm_xor_si128(_mm_clmulepi64_si128(x, k3k4, 0x10), _mm_srli_si128(x, 8));
let x = _mm_xor_si128(
_mm_clmulepi64_si128(_mm_and_si128(x, low32), _mm_set_epi64x(0, K5), 0x00),
_mm_srli_si128(x, 4),
);
let pu = _mm_set_epi64x(U_PRIME, P_X);
let t1 = _mm_clmulepi64_si128(_mm_and_si128(x, low32), pu, 0x10);
let t2 = _mm_clmulepi64_si128(_mm_and_si128(t1, low32), pu, 0x00);
let folded = !(_mm_extract_epi32(_mm_xor_si128(x, t2), 1) as u32);
crate::checksum::crc32_continue_table(folded, tail)
}
#[target_feature(enable = "pclmulqdq,sse4.1")]
fn reduce(a: __m128i, b: __m128i, keys: __m128i) -> __m128i {
let low = _mm_clmulepi64_si128(a, keys, 0x00);
let high = _mm_clmulepi64_si128(a, keys, 0x11);
_mm_xor_si128(_mm_xor_si128(b, low), high)
}
#[target_feature(enable = "pclmulqdq,sse4.1")]
fn load(block: &[u8; 16]) -> __m128i {
unsafe { _mm_loadu_si128(block.as_ptr().cast::<__m128i>()) }
}
}
#[cfg(target_arch = "aarch64")]
mod arm {
use std::arch::aarch64::{__crc32b, __crc32d};
#[target_feature(enable = "crc")]
pub(super) unsafe fn crc(previous: u32, data: &[u8]) -> u32 {
let (words, tail) = data.as_chunks::<8>();
let mut state = !previous;
for word in words {
state = __crc32d(state, u64::from_le_bytes(*word));
}
for byte in tail {
state = __crc32b(state, *byte);
}
!state
}
}