//! Provides CRC-32/ISCSI calculations using a fusion of native CLMUL //! instructions and native CRC calculation instructions on x86 / x86_64. //! //! https://www.corsix.org/content/fast-crc32c-4k //! https://www.corsix.org/content/alternative-exposition-crc32_4k_pclmulqdq //! //! Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/ //! with the help of Claude.ai. //! //! Modified as necessary for this Rust implementation. //! //! MIT licensed. #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod iscsi; use iscsi::sse_pclmulqdq::crc32_iscsi_sse_v4s3x3; #[cfg(target_arch = "x86")] use core::arch::x86::*; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; #[cfg(target_arch = "x86_64")] use iscsi::avx512_pclmulqdq::crc32_iscsi_avx512_v4s3x3; #[cfg(target_arch = "x86_64")] use iscsi::avx512_vpclmulqdq::crc32_iscsi_avx512_vpclmulqdq_v3x2; /// CRC32 iSCSI calculation using the highest available instruction set (post-AVX-512 support) /// /// This function is called by the wrapper layer after feature detection has been performed. /// The wrapper layer ensures that only the appropriate implementation is called based on /// cached feature detection results, removing runtime checks from the hot path. #[inline(always)] pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 { let data_len = data.len(); // SSE4.2 is required for native CRC32 instructions used in small buffer path if data_len <= 256 && is_x86_feature_detected!("sse4.2") { unsafe { return crc32_iscsi_small_fast(crc, data); } } #[cfg(target_arch = "x86_64")] { // AVX512 + VPCLMULQDQ if is_x86_feature_detected!("avx512vl") && is_x86_feature_detected!("vpclmulqdq") { unsafe { return crc32_iscsi_avx512_vpclmulqdq_v3x2(crc, data.as_ptr(), data_len); } } // AVX512 if is_x86_feature_detected!("avx512vl") { unsafe { return crc32_iscsi_avx512_v4s3x3(crc, data.as_ptr(), data_len); } } } // Fallback to SSE implementation unsafe { crc32_iscsi_sse_v4s3x3(crc, data.as_ptr(), data_len) } } #[cfg(target_arch = "x86_64")] #[inline] #[target_feature(enable = "avx512vl,vpclmulqdq")] unsafe fn clmul_lo_avx512_vpclmulqdq(a: __m512i, b: __m512i) -> __m512i { _mm512_clmulepi64_epi128(a, b, 0) } #[cfg(target_arch = "x86_64")] #[inline] #[target_feature(enable = "avx512vl,vpclmulqdq")] unsafe fn clmul_hi_avx512_vpclmulqdq(a: __m512i, b: __m512i) -> __m512i { _mm512_clmulepi64_epi128(a, b, 17) } #[inline] #[target_feature(enable = "pclmulqdq")] unsafe fn clmul_lo_sse(a: __m128i, b: __m128i) -> __m128i { _mm_clmulepi64_si128(a, b, 0) } #[inline] #[target_feature(enable = "pclmulqdq")] unsafe fn clmul_hi_sse(a: __m128i, b: __m128i) -> __m128i { _mm_clmulepi64_si128(a, b, 17) } #[inline] #[target_feature(enable = "pclmulqdq")] unsafe fn clmul_scalar_sse(a: u32, b: u32) -> __m128i { _mm_clmulepi64_si128(_mm_cvtsi32_si128(a as i32), _mm_cvtsi32_si128(b as i32), 0) } // x^n mod P, in log(n) time #[target_feature(enable = "sse4.2,pclmulqdq")] unsafe fn xnmodp_iscsi_sse(mut n: u64) -> u32 { let mut stack = !1u64; let mut acc: u32; let mut low: u32; while n > 191 { stack = (stack << 1) + (n & 1); n = (n >> 1) - 16; } stack = !stack; acc = 0x80000000u32 >> (n & 31); n >>= 5; while n > 0 { // Use hardware CRC32C instruction acc = _mm_crc32_u32(acc, 0); n -= 1; } while { low = (stack & 1) as u32; stack >>= 1; stack != 0 } { let x = _mm_cvtsi32_si128(acc as i32); let clmul_result = _mm_clmulepi64_si128(x, x, 0); let y = mm_extract_epi64(clmul_result, 0); acc = mm_crc32_u64(0, y << low); } acc } #[inline] #[target_feature(enable = "pclmulqdq")] unsafe fn crc_shift_iscsi_sse(crc: u32, nbytes: usize) -> __m128i { clmul_scalar_sse(crc, xnmodp_iscsi_sse((nbytes * 8 - 33) as u64)) } #[inline] #[target_feature(enable = "sse4.1")] unsafe fn mm_extract_epi64(val: __m128i, idx: i32) -> u64 { #[cfg(target_arch = "x86_64")] { if idx == 0 { _mm_cvtsi128_si64(val) as u64 } else { _mm_cvtsi128_si64(_mm_srli_si128(val, 8)) as u64 } } #[cfg(target_arch = "x86")] { // On 32-bit x86, extract two 32-bit values and combine them let shifted = if idx == 0 { val } else { _mm_srli_si128(val, 8) }; let low = _mm_cvtsi128_si32(shifted) as u32; let high = _mm_cvtsi128_si32(_mm_srli_si128(shifted, 4)) as u32; (low as u64) | ((high as u64) << 32) } } #[inline] #[target_feature(enable = "sse4.2")] unsafe fn mm_crc32_u64(crc: u32, val: u64) -> u32 { #[cfg(target_arch = "x86_64")] { _mm_crc32_u64(crc.into(), val) as u32 } #[cfg(target_arch = "x86")] { // On 32-bit x86, process 64-bit value as two 32-bit CRC operations let low = val as u32; let high = (val >> 32) as u32; let crc = _mm_crc32_u32(crc, low); _mm_crc32_u32(crc, high) } } /// CRC-32/ISCSI calculation for small buffers (<= 256 bytes) using unrolled native CRC instructions #[inline] #[target_feature(enable = "sse4.2")] pub unsafe fn crc32_iscsi_small_fast(mut crc: u32, data: &[u8]) -> u32 { let (prefix, aligned, suffix) = data.align_to::(); for &byte in prefix { crc = _mm_crc32_u8(crc, byte); } let mut chunks = aligned.chunks_exact(8); for chunk in &mut chunks { crc = mm_crc32_u64(crc, chunk[0]); crc = mm_crc32_u64(crc, chunk[1]); crc = mm_crc32_u64(crc, chunk[2]); crc = mm_crc32_u64(crc, chunk[3]); crc = mm_crc32_u64(crc, chunk[4]); crc = mm_crc32_u64(crc, chunk[5]); crc = mm_crc32_u64(crc, chunk[6]); crc = mm_crc32_u64(crc, chunk[7]); } for &val in chunks.remainder() { crc = mm_crc32_u64(crc, val); } for &byte in suffix { crc = _mm_crc32_u8(crc, byte); } crc } #[cfg(test)] mod tests { use super::*; use crate::test::consts::TEST_CHECK_STRING; use crc::{Crc, Table}; use rand::{rng, Rng}; const RUST_CRC32_ISCSI: Crc> = Crc::>::new(&crc::CRC_32_ISCSI); #[test] fn test_crc32_iscsi_check() { if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { assert_eq!( crc32_iscsi(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff, 0xe3069283 ); } } #[test] fn test_crc32_iscsi_small_fast_check() { if is_x86_feature_detected!("sse4.2") { unsafe { assert_eq!( crc32_iscsi_small_fast(0xffffffff, TEST_CHECK_STRING) ^ 0xffffffff, 0xe3069283 ); } } } #[test] fn test_crc32_iscsi_small_fast_all_lengths() { if is_x86_feature_detected!("sse4.2") { for len in 1..=255 { test_crc32_iscsi_small_fast_random(len); } } } fn test_crc32_iscsi_small_fast_random(len: usize) { let mut data = vec![0u8; len]; rng().fill(&mut data[..]); let checksum = RUST_CRC32_ISCSI.checksum(&data); if is_x86_feature_detected!("sse4.2") { unsafe { assert_eq!( crc32_iscsi_small_fast(0xffffffff, &data) ^ 0xffffffff, checksum ); } } } #[test] fn test_crc32_iscsi_small_all_lengths() { if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { for len in 1..=255 { test_crc32_iscsi_random(len); } } } #[test] fn test_crc32_iscsi_medium_lengths() { if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { // Test each length from 256 to 1024, which should fold and include handling remainders for len in 256..=1024 { test_crc32_iscsi_random(len); } } } #[test] fn test_crc32_iscsi_large_lengths() { if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { // Test 1 MiB just before, at, and just after the folding boundaries for len in 1048575..1048577 { test_crc32_iscsi_random(len); } } } fn test_crc32_iscsi_random(len: usize) { let mut data = vec![0u8; len]; rng().fill(&mut data[..]); let checksum = RUST_CRC32_ISCSI.checksum(&data); if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { assert_eq!(crc32_iscsi(0xffffffff, &data) ^ 0xffffffff, checksum); } unsafe { #[cfg(target_arch = "x86_64")] { if is_x86_feature_detected!("vpclmulqdq") && is_x86_feature_detected!("avx512vl") && is_x86_feature_detected!("avx512f") { assert_eq!( crc32_iscsi_avx512_vpclmulqdq_v3x2(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff, checksum ); } if is_x86_feature_detected!("avx512vl") && is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("pclmulqdq") { assert_eq!( crc32_iscsi_avx512_v4s3x3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff, checksum ); } } assert_eq!( crc32_iscsi_sse_v4s3x3(0xffffffff, data.as_ptr(), data.len()) ^ 0xffffffff, checksum ); } } }