pub(crate) const MIN_UPDATE: usize = 256;
#[inline]
pub(crate) fn available() -> bool {
#[cfg(all(target_arch = "x86_64", not(miri)))]
{
x86_vpclmul::available()
}
#[cfg(not(all(target_arch = "x86_64", not(miri))))]
{
false
}
}
#[inline]
pub(crate) fn update(initial: u32, data: &[u8]) -> u32 {
debug_assert!(
available(),
"accelerated CRC tier entered while unavailable"
);
#[cfg(all(target_arch = "x86_64", not(miri)))]
if x86_vpclmul::available() {
return unsafe { x86_vpclmul::update(initial, data) };
}
crc32_resume_reference(initial, data)
}
#[inline]
pub(crate) fn crc32_resume_reference(initial: u32, data: &[u8]) -> u32 {
let mut hasher = crc_fast::Digest::new_with_init_state(
crc_fast::CrcAlgorithm::Crc32IsoHdlc,
u64::from(!initial),
);
hasher.update(data);
hasher.finalize() as u32
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
pub(crate) mod x86_vpclmul {
#![allow(unsafe_op_in_unsafe_fn)]
use std::arch::x86_64::*;
use std::sync::OnceLock;
const FORCE_ENV: &str = "RARPAR_CRC32_VPCLMUL";
pub(crate) fn available() -> bool {
static AVAILABLE: OnceLock<bool> = OnceLock::new();
*AVAILABLE.get_or_init(|| {
let forced = std::env::var_os(FORCE_ENV);
if forced.as_deref().is_some_and(|value| value == "0") {
return false;
}
let capable = is_x86_feature_detected!("avx2")
&& is_x86_feature_detected!("pclmulqdq")
&& is_x86_feature_detected!("sse4.1")
&& is_x86_feature_detected!("vpclmulqdq");
if !capable {
return false;
}
if forced.as_deref().is_some_and(|value| value == "1") {
return true;
}
!is_x86_feature_detected!("avx512vl")
})
}
#[target_feature(enable = "avx2,pclmulqdq,sse4.1,vpclmulqdq")]
pub(crate) unsafe fn update(initial: u32, data: &[u8]) -> u32 {
crc_fold_256(initial, data)
}
#[inline(always)]
unsafe fn loadu256(data: &[u8]) -> __m256i {
debug_assert!(data.len() >= 32);
_mm256_loadu_si256(data.as_ptr() as *const __m256i)
}
#[inline(always)]
unsafe fn load_partial256(data: &[u8]) -> __m256i {
debug_assert!(data.len() < 32);
let mut tmp = [0u8; 32];
tmp[..data.len()].copy_from_slice(data);
_mm256_loadu_si256(tmp.as_ptr() as *const __m256i)
}
#[inline(always)]
unsafe fn zext128_256(value: __m128i) -> __m256i {
_mm256_inserti128_si256::<0>(_mm256_setzero_si256(), value)
}
#[inline(always)]
unsafe fn broadcast128(value: __m128i) -> __m256i {
let out = _mm256_castsi128_si256(value);
_mm256_inserti128_si256::<1>(out, value)
}
#[inline(always)]
unsafe fn xor3_128(a: __m128i, b: __m128i, c: __m128i) -> __m128i {
_mm_xor_si128(_mm_xor_si128(a, b), c)
}
#[inline(always)]
unsafe fn setr_epi32(a: u32, b: u32, c: u32, d: u32) -> __m128i {
_mm_set_epi32(d as i32, c as i32, b as i32, a as i32)
}
#[inline(always)]
unsafe fn do_one_fold(src: __m256i, data: __m256i) -> __m256i {
let fold4 = _mm256_set_epi32(
0x0000_0001u32 as i32,
0x5444_2bd4u32 as i32,
0x0000_0001u32 as i32,
0xc6e4_1596u32 as i32,
0x0000_0001u32 as i32,
0x5444_2bd4u32 as i32,
0x0000_0001u32 as i32,
0xc6e4_1596u32 as i32,
);
_mm256_xor_si256(
_mm256_xor_si256(data, _mm256_clmulepi64_epi128::<0x01>(src, fold4)),
_mm256_clmulepi64_epi128::<0x10>(src, fold4),
)
}
#[inline(always)]
unsafe fn partial_fold(len: usize, crc0: &mut __m256i, crc1: &mut __m256i, crc_part: __m256i) {
debug_assert!(len < 32);
const ROT_TABLE: [u8; 32] = [
0, 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,
];
let shuf128 = _mm_loadu_si128(ROT_TABLE.as_ptr().add(len & 15) as *const __m128i);
let shuf = broadcast128(shuf128);
let mask = _mm256_cmpgt_epi8(shuf, _mm256_set1_epi8(15));
*crc0 = _mm256_shuffle_epi8(*crc0, shuf);
*crc1 = _mm256_shuffle_epi8(*crc1, shuf);
let crc_part = _mm256_shuffle_epi8(crc_part, shuf);
let mut crc_out = _mm256_permute2x128_si256::<0x08>(*crc0, *crc0);
let crc01;
let crc1p;
if len >= 16 {
crc_out = _mm256_blendv_epi8(crc_out, *crc0, mask);
crc01 = *crc1;
crc1p = crc_part;
*crc0 = _mm256_permute2x128_si256::<0x21>(*crc0, *crc1);
*crc1 = _mm256_permute2x128_si256::<0x21>(*crc1, crc_part);
} else {
crc_out = _mm256_and_si256(crc_out, mask);
crc01 = _mm256_permute2x128_si256::<0x21>(*crc0, *crc1);
crc1p = _mm256_permute2x128_si256::<0x21>(*crc1, crc_part);
}
*crc0 = _mm256_blendv_epi8(*crc0, crc01, mask);
*crc1 = _mm256_blendv_epi8(*crc1, crc1p, mask);
*crc1 = do_one_fold(crc_out, *crc1);
}
#[inline(always)]
unsafe fn crc_fold_256(initial: u32, mut data: &[u8]) -> u32 {
if data.is_empty() {
return initial;
}
let xmm_t0 = _mm_clmulepi64_si128(
_mm_cvtsi32_si128((!initial) as i32),
_mm_cvtsi32_si128(0xdfde_d7ecu32 as i32),
0,
);
let mut crc0 = zext128_256(xmm_t0);
let mut crc1 = _mm256_setzero_si256();
if data.len() < 32 {
let part = load_partial256(data);
partial_fold(data.len(), &mut crc0, &mut crc1, part);
} else {
while data.len() >= 64 {
crc0 = do_one_fold(crc0, loadu256(data));
crc1 = do_one_fold(crc1, loadu256(&data[32..]));
data = &data[64..];
}
if data.len() >= 32 {
let old = crc1;
crc1 = do_one_fold(crc0, loadu256(data));
crc0 = old;
data = &data[32..];
}
if !data.is_empty() {
let part = load_partial256(data);
partial_fold(data.len(), &mut crc0, &mut crc1, part);
}
}
let mask = _mm_set_epi32(-1, -1, -1, 0);
let mut xmm_crc0 = _mm256_castsi256_si128(crc0);
let mut xmm_crc1 = _mm256_extracti128_si256::<1>(crc0);
let mut xmm_crc2 = _mm256_castsi256_si128(crc1);
let mut xmm_crc3 = _mm256_extracti128_si256::<1>(crc1);
let mut fold = setr_epi32(0xccaa_009e, 0x0000_0000, 0x7519_97d0, 0x0000_0001);
let tmp0 = _mm_clmulepi64_si128(xmm_crc0, fold, 0x10);
xmm_crc0 = _mm_clmulepi64_si128(xmm_crc0, fold, 0x01);
xmm_crc1 = xor3_128(xmm_crc1, tmp0, xmm_crc0);
let tmp1 = _mm_clmulepi64_si128(xmm_crc1, fold, 0x10);
xmm_crc1 = _mm_clmulepi64_si128(xmm_crc1, fold, 0x01);
xmm_crc2 = xor3_128(xmm_crc2, tmp1, xmm_crc1);
let tmp2 = _mm_clmulepi64_si128(xmm_crc2, fold, 0x10);
xmm_crc2 = _mm_clmulepi64_si128(xmm_crc2, fold, 0x01);
xmm_crc3 = xor3_128(xmm_crc3, tmp2, xmm_crc2);
fold = setr_epi32(0xccaa_009e, 0x0000_0000, 0x63cd_6124, 0x0000_0001);
xmm_crc0 = xmm_crc3;
xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, fold, 0);
xmm_crc0 = _mm_srli_si128::<8>(xmm_crc0);
xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
xmm_crc0 = xmm_crc3;
xmm_crc3 = _mm_slli_si128::<4>(xmm_crc3);
xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, fold, 0x10);
xmm_crc0 = _mm_and_si128(xmm_crc0, mask);
xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc0);
fold = setr_epi32(0xf701_1641, 0x0000_0000, 0xdb71_0640, 0x0000_0001);
xmm_crc1 = xmm_crc3;
xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, fold, 0);
xmm_crc3 = _mm_clmulepi64_si128(xmm_crc3, fold, 0x10);
xmm_crc1 = _mm_xor_si128(xmm_crc1, mask);
xmm_crc3 = _mm_xor_si128(xmm_crc3, xmm_crc1);
_mm_extract_epi32::<2>(xmm_crc3) as u32
}
#[cfg(test)]
pub(crate) fn test_update_forced(initial: u32, data: &[u8]) -> Option<u32> {
available().then(|| unsafe { update(initial, data) })
}
}
#[cfg(test)]
mod tests {
use super::*;
struct XorShift64 {
state: u64,
}
impl XorShift64 {
fn new(seed: u64) -> Self {
Self {
state: seed | 0x9E37_79B9_7F4A_7C15,
}
}
fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn fill(&mut self, buf: &mut [u8]) {
let mut chunks = buf.chunks_exact_mut(8);
for chunk in &mut chunks {
chunk.copy_from_slice(&self.next_u64().to_le_bytes());
}
let rem = chunks.into_remainder();
if !rem.is_empty() {
let bytes = self.next_u64().to_le_bytes();
rem.copy_from_slice(&bytes[..rem.len()]);
}
}
}
#[test]
fn resume_reference_at_zero_seed_matches_one_shot() {
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0001);
for len in [0usize, 1, 31, 32, 33, 255, 256, 4096, 100_003] {
let mut data = vec![0u8; len];
rng.fill(&mut data);
assert_eq!(
crc32_resume_reference(0, &data),
crc_fast::crc32_iso_hdlc(&data),
"len {len}"
);
}
}
#[test]
fn resume_reference_chains_across_splits() {
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0002);
let mut data = vec![0u8; 65_536];
rng.fill(&mut data);
let whole = crc_fast::crc32_iso_hdlc(&data);
for split in [
0usize, 1, 31, 32, 33, 255, 256, 4095, 32_768, 65_535, 65_536,
] {
let (a, b) = data.split_at(split);
let chained = crc32_resume_reference(crc32_resume_reference(0, a), b);
assert_eq!(chained, whole, "split {split}");
}
}
#[test]
fn dispatch_agrees_with_reference_or_reports_unavailable() {
if !available() {
eprintln!(
"skipping dispatch_agrees_with_reference_or_reports_unavailable: \
no accelerated CRC tier on this host/build"
);
return;
}
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0003);
let mut data = vec![0u8; 16_384];
rng.fill(&mut data);
for len in [0usize, 1, 256, 257, 4096, 16_384] {
assert_eq!(
update(0, &data[..len]),
crc_fast::crc32_iso_hdlc(&data[..len]),
"len {len}"
);
}
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_matches_crc_fast_exhaustive_short_lengths() {
if !x86_vpclmul::available() {
eprintln!(
"skipping vpclmul_matches_crc_fast_exhaustive_short_lengths: \
VPCLMULQDQ tier inactive (set RARPAR_CRC32_VPCLMUL=1 on a \
VPCLMULQDQ host to force it)"
);
return;
}
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0010);
let mut backing = vec![0u8; 64 + 192 + 8];
rng.fill(&mut backing);
let mut cases = 0usize;
for offset in 0..64usize {
for len in 0..=192usize {
let input = &backing[offset..offset + len];
let expected = crc_fast::crc32_iso_hdlc(input);
let actual = x86_vpclmul::test_update_forced(0, input)
.expect("tier availability was just checked");
assert_eq!(actual, expected, "offset {offset} len {len}");
cases += 1;
}
}
assert_eq!(cases, 64 * 193, "expected the full offset x length sweep");
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_matches_crc_fast_for_arbitrary_initial_values() {
if !x86_vpclmul::available() {
eprintln!(
"skipping vpclmul_matches_crc_fast_for_arbitrary_initial_values: \
VPCLMULQDQ tier inactive"
);
return;
}
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0011);
let mut data = vec![0u8; 8192];
rng.fill(&mut data);
let mut cases = 0usize;
for initial in [
0u32,
1,
0xFFFF_FFFF,
0x1234_5678,
0xDEAD_BEEF,
0x8000_0000,
0x0000_0001,
] {
for len in [
0usize, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 255, 256, 257, 1023, 4096,
8192,
] {
let input = &data[..len];
let expected = crc32_resume_reference(initial, input);
let actual = x86_vpclmul::test_update_forced(initial, input)
.expect("tier availability was just checked");
assert_eq!(actual, expected, "initial {initial:#010x} len {len}");
cases += 1;
}
}
assert!(cases >= 100, "expected >= 100 resume cases, ran {cases}");
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_survives_arbitrary_streaming_splits() {
if !x86_vpclmul::available() {
eprintln!(
"skipping vpclmul_survives_arbitrary_streaming_splits: \
VPCLMULQDQ tier inactive"
);
return;
}
let mut rng = XorShift64::new(0x0C3C_0DE0_0000_0012);
let mut data = vec![0u8; 300_000];
rng.fill(&mut data);
let whole = crc_fast::crc32_iso_hdlc(&data);
let mut cases = 0usize;
for trial in 0..64u32 {
let mut chunker = XorShift64::new(0xA5A5_0000_0000_0000 ^ u64::from(trial));
let mut running = 0u32;
let mut offset = 0usize;
while offset < data.len() {
let take = match chunker.next_u64() % 8 {
0 => 1,
1 => 31,
2 => 32,
3 => 33,
4 => 63,
5 => 64,
6 => 65,
_ => (chunker.next_u64() % 9973) as usize,
}
.min(data.len() - offset);
let take = take.max(1).min(data.len() - offset);
running = x86_vpclmul::test_update_forced(running, &data[offset..offset + take])
.expect("tier availability was just checked");
offset += take;
}
assert_eq!(running, whole, "trial {trial}");
cases += 1;
}
assert_eq!(cases, 64);
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_empty_update_is_identity() {
if !x86_vpclmul::available() {
eprintln!("skipping vpclmul_empty_update_is_identity: VPCLMULQDQ tier inactive");
return;
}
for initial in [0u32, 1, 0x1234_5678, 0xFFFF_FFFF] {
assert_eq!(
x86_vpclmul::test_update_forced(initial, &[]),
Some(initial),
"initial {initial:#010x}"
);
assert_eq!(crc32_resume_reference(initial, &[]), initial);
}
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_matches_the_standard_check_vector() {
if !x86_vpclmul::available() {
eprintln!("skipping vpclmul_matches_the_standard_check_vector: tier inactive");
return;
}
assert_eq!(
x86_vpclmul::test_update_forced(0, b"123456789"),
Some(0xCBF4_3926)
);
}
#[test]
fn shared_kernel_region_matches_the_unrar_copy() {
const BEGIN: &str = "// SHARED-KERNEL-BEGIN";
const END: &str = "// SHARED-KERNEL-END";
fn shared_region(source: &str, label: &str) -> String {
let start = source
.find(BEGIN)
.unwrap_or_else(|| panic!("{label} is missing {BEGIN}"));
let end = source
.find(END)
.unwrap_or_else(|| panic!("{label} is missing {END}"));
assert!(start < end, "{label} has the markers in the wrong order");
let region = &source[start + BEGIN.len()..end];
for anchor in [
"mod x86_vpclmul",
"unsafe fn crc_fold_256",
"_mm256_clmulepi64_epi128",
] {
assert!(
region.contains(anchor),
"{label}: the extracted SHARED-KERNEL region does not contain \
`{anchor}`, so the markers are not bracketing the kernel"
);
}
region.to_string()
}
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let sibling = manifest
.parent()
.expect("crate dir has a parent")
.join("unrar-rs/src/crc_simd.rs");
let Ok(sibling_source) = std::fs::read_to_string(&sibling) else {
eprintln!(
"skipping shared_kernel_region_matches_the_unrar_copy: no sibling copy at \
{} (expected inside a packaged .crate)",
sibling.display()
);
return;
};
let ours = shared_region(include_str!("crc_simd.rs"), "the par2-rs copy");
let theirs = shared_region(&sibling_source, "the unrar-rs copy");
assert_eq!(
ours, theirs,
"the shared CRC kernel has drifted between par2-rs and unrar-rs; \
the two SHARED-KERNEL regions must stay byte-identical"
);
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
#[test]
fn vpclmul_availability_never_outruns_the_isa() {
if x86_vpclmul::available() {
assert!(is_x86_feature_detected!("avx2"));
assert!(is_x86_feature_detected!("pclmulqdq"));
assert!(is_x86_feature_detected!("sse4.1"));
assert!(
is_x86_feature_detected!("vpclmulqdq"),
"the tier engaged without VPCLMULQDQ; the override must widen \
policy, never capability"
);
}
}
}