From 465c036f371d1b893356219cb74512177521e570 Mon Sep 17 00:00:00 2001 From: dyxushuai Date: Wed, 22 Jul 2026 15:33:07 +0800 Subject: [PATCH 1/3] perf(packet): use SIMD for long Internet checksums Route checksum_add through the existing NEON/SSSE3 accumulator for buffers of 64 bytes or more; short headers stay scalar. Add a small microbench example for scalar vs SIMD throughput. --- .../arcbox-packet/examples/checksum_bench.rs | 52 ++++ common/arcbox-packet/src/checksum.rs | 224 ++++++++---------- 2 files changed, 157 insertions(+), 119 deletions(-) create mode 100644 common/arcbox-packet/examples/checksum_bench.rs diff --git a/common/arcbox-packet/examples/checksum_bench.rs b/common/arcbox-packet/examples/checksum_bench.rs new file mode 100644 index 000000000..ff9fb59a5 --- /dev/null +++ b/common/arcbox-packet/examples/checksum_bench.rs @@ -0,0 +1,52 @@ +//! Microbench: scalar vs SIMD Internet checksum throughput. +//! +//! ```text +//! cargo run -p arcbox-packet --example checksum_bench --release +//! ``` + +use std::time::Instant; + +use arcbox_packet::checksum::{checksum_add_scalar, checksum_fold, checksum_simd}; + +fn bench(label: &str, len: usize, iters: usize, f: impl Fn(&[u8]) -> u16) { + let data: Vec = (0..len).map(|i| (i % 251) as u8).collect(); + for _ in 0..100 { + std::hint::black_box(f(&data)); + } + let t0 = Instant::now(); + let mut sink = 0u16; + for _ in 0..iters { + sink ^= f(&data); + } + let elapsed = t0.elapsed(); + std::hint::black_box(sink); + let bytes = (len as u64).saturating_mul(iters as u64); + let gbps = (bytes as f64 * 8.0) / elapsed.as_secs_f64() / 1e9; + let ns_per_call = elapsed.as_nanos() as f64 / iters as f64; + println!( + "{label:<18} len={len:>5} {iters:>8} iters {gbps:>8.2} Gbit/s {ns_per_call:>8.1} ns/call" + ); +} + +fn main() { + println!( + "# Internet checksum microbench (host = {})", + std::env::consts::ARCH + ); + println!(); + + for &len in &[64usize, 1500, 9000, 16384, 65536] { + let iters = (50_000_000 / len).max(2_000); + bench("scalar", len, iters, |d| { + checksum_fold(checksum_add_scalar(d)) + }); + bench("simd", len, iters, checksum_simd); + let data: Vec = (0..len).map(|i| (i % 251) as u8).collect(); + assert_eq!( + checksum_fold(checksum_add_scalar(&data)), + checksum_simd(&data), + "scalar/simd mismatch at len={len}" + ); + println!(); + } +} diff --git a/common/arcbox-packet/src/checksum.rs b/common/arcbox-packet/src/checksum.rs index 5a90c87bd..c61ed73ba 100644 --- a/common/arcbox-packet/src/checksum.rs +++ b/common/arcbox-packet/src/checksum.rs @@ -13,22 +13,37 @@ pub fn checksum_fold(mut sum: u32) -> u16 { !sum as u16 } +/// Length at which SIMD `checksum_add` is used. Short headers stay scalar +/// so setup cost does not dominate. +const SIMD_THRESHOLD: usize = 64; + /// Calculates the ones' complement sum of 16-bit words. /// -/// This is the core operation for IP/TCP/UDP checksums. +/// This is the core operation for IP/TCP/UDP checksums. Buffers of +/// [`SIMD_THRESHOLD`] bytes or more use the architecture SIMD path when +/// available (NEON / SSSE3); shorter buffers stay on the scalar loop. #[inline] pub fn checksum_add(data: &[u8]) -> u32 { + if data.len() >= SIMD_THRESHOLD { + checksum_add_fast(data) + } else { + checksum_add_scalar(data) + } +} + +/// Scalar ones' complement sum (always available; used for short buffers +/// and as the SIMD fallback). +#[inline] +pub fn checksum_add_scalar(data: &[u8]) -> u32 { let mut sum: u32 = 0; let mut i = 0; - // Process 16-bit words while i + 1 < data.len() { let word = u16::from_be_bytes([data[i], data[i + 1]]); sum = sum.wrapping_add(word as u32); i += 2; } - // Handle odd byte if i < data.len() { sum = sum.wrapping_add((data[i] as u32) << 8); } @@ -159,190 +174,126 @@ pub fn udp_checksum(src_ip: [u8; 4], dst_ip: [u8; 4], udp_datagram: &[u8]) -> u1 if result == 0 { 0xFFFF } else { result } } -/// SIMD-optimized checksum for ARM64 NEON. -/// -/// Uses NEON intrinsics to process 16 bytes at a time, with correct handling -/// of network byte order (big-endian 16-bit words). +/// Ones' complement sum via the fastest available path for this host. +#[inline] +fn checksum_add_fast(data: &[u8]) -> u32 { + #[cfg(target_arch = "aarch64")] + { + // SAFETY: NEON is mandatory on AArch64. + return unsafe { checksum_add_neon(data) }; + } + #[cfg(target_arch = "x86_64")] + { + if is_x86_feature_detected!("ssse3") { + // SAFETY: SSSE3 just verified. + unsafe { checksum_add_ssse3(data) } + } else { + checksum_add_scalar(data) + } + } + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + { + checksum_add_scalar(data) + } +} + +/// SIMD ones' complement sum for ARM64 NEON (16 bytes/iter, BE halfwords). /// /// # Safety -/// -/// This function uses `#[target_feature(enable = "neon")]` and requires NEON support. -/// On AArch64, NEON is always available as part of the architecture specification. +/// Requires NEON (`#[target_feature(enable = "neon")]`). Always true on AArch64. #[cfg(target_arch = "aarch64")] #[target_feature(enable = "neon")] -pub unsafe fn checksum_simd_neon(data: &[u8]) -> u16 { +unsafe fn checksum_add_neon(data: &[u8]) -> u32 { use std::arch::aarch64::*; - // SAFETY: All NEON intrinsics below are safe to call because: - // 1. We have #[target_feature(enable = "neon")] ensuring NEON is available - // 2. Pointer passed to vld1q_u8 is valid (from slice with length >= 16) + // SAFETY: NEON available; loads stay within `data`. unsafe { let mut sum = vdupq_n_u32(0); - let chunks = data.chunks_exact(16); - let remainder = chunks.remainder(); + let (chunks, remainder) = data.as_chunks::<16>(); for chunk in chunks { - // Load 16 bytes from memory let bytes = vld1q_u8(chunk.as_ptr()); - - // Network byte order is big-endian. On little-endian ARM64, we need to - // swap bytes within each 16-bit word to get the correct checksum value. - // vrev16q_u8 swaps adjacent bytes: [0,1,2,3,...] -> [1,0,3,2,...] + // Network order is BE; swap adjacent bytes on LE host. let swapped = vrev16q_u8(bytes); - - // Now interpret as 16-bit words (already in correct order for summation) let words = vreinterpretq_u16_u8(swapped); - - // Pairwise add and accumulate to 32-bit to avoid overflow - // vpadalq_u16 adds adjacent pairs of u16 into u32 accumulators sum = vpadalq_u16(sum, words); } - // Horizontal sum of the four 32-bit lanes - let sum32 = vaddvq_u32(sum); - - // Process remainder bytes using scalar code - let mut scalar_sum = sum32; + let mut scalar_sum = vaddvq_u32(sum); let mut i = 0; while i + 1 < remainder.len() { - // Read big-endian 16-bit word let word = u16::from_be_bytes([remainder[i], remainder[i + 1]]); scalar_sum = scalar_sum.wrapping_add(word as u32); i += 2; } - - // Handle odd byte (padded with zero on the right in network order) if i < remainder.len() { scalar_sum = scalar_sum.wrapping_add((remainder[i] as u32) << 8); } - - // Fold 32-bit sum into 16-bit checksum - while scalar_sum > 0xFFFF { - scalar_sum = (scalar_sum & 0xFFFF) + (scalar_sum >> 16); - } - - !scalar_sum as u16 + scalar_sum } } -/// SIMD-optimized checksum for ARM64 NEON (safe wrapper). -/// -/// This is the public safe interface that calls the unsafe NEON implementation. -/// NEON is always available on AArch64 processors. -#[cfg(target_arch = "aarch64")] -#[inline] -pub fn checksum_simd(data: &[u8]) -> u16 { - // SAFETY: NEON is mandatory on AArch64 architecture - unsafe { checksum_simd_neon(data) } -} - -/// SIMD-optimized checksum for x86_64 using SSSE3. -/// -/// Uses SSSE3 intrinsics to process 16 bytes at a time, with correct handling -/// of network byte order (big-endian 16-bit words). -/// -/// SSSE3 is required for the `pshufb` instruction used for byte swapping. -/// SSSE3 is available on all x86_64 CPUs since Intel Core 2 (2006) and -/// AMD Barcelona (2007), covering essentially all modern x86_64 systems. +/// SIMD ones' complement sum for x86_64 SSSE3. /// /// # Safety -/// -/// This function uses `#[target_feature(enable = "ssse3")]` and requires SSSE3 support. +/// Requires SSSE3 (`pshufb`). #[cfg(target_arch = "x86_64")] #[target_feature(enable = "ssse3")] -unsafe fn checksum_simd_ssse3(data: &[u8]) -> u16 { +unsafe fn checksum_add_ssse3(data: &[u8]) -> u32 { use std::arch::x86_64::*; - // SAFETY: All SSE/SSSE3 intrinsics below are safe to call because: - // 1. We have #[target_feature(enable = "ssse3")] ensuring SSSE3 is available - // 2. Pointers passed to _mm_loadu_si128 are valid (from slice with length >= 16) + // SAFETY: SSSE3 available; unaligned loads within `data`. unsafe { - // Accumulator: two 64-bit sums (we'll combine them at the end). let mut sum_lo = _mm_setzero_si128(); let mut sum_hi = _mm_setzero_si128(); - - // Shuffle mask to swap bytes within 16-bit words for big-endian interpretation. - // Network byte order is big-endian, x86 is little-endian. - // This mask converts [0,1,2,3,4,5,...] to [1,0,3,2,5,4,...] (swap adjacent bytes). let swap_mask = _mm_setr_epi8(1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14); - let chunks = data.chunks_exact(16); - let remainder = chunks.remainder(); + let (chunks, remainder) = data.as_chunks::<16>(); for chunk in chunks { - // Load 16 bytes from memory (unaligned load). let bytes = _mm_loadu_si128(chunk.as_ptr().cast()); - - // Swap bytes within each 16-bit word for big-endian interpretation. let swapped = _mm_shuffle_epi8(bytes, swap_mask); - - // Unpack low and high halves to 32-bit words and add to accumulators. - // _mm_unpacklo_epi16 with zero unpacks low 4 u16s to low 4 u32s. - // _mm_unpackhi_epi16 with zero unpacks high 4 u16s to high 4 u32s. let zero = _mm_setzero_si128(); - let words_lo = _mm_unpacklo_epi16(swapped, zero); // 4 x u32 (words 0-3) - let words_hi = _mm_unpackhi_epi16(swapped, zero); // 4 x u32 (words 4-7) - - // Add to accumulators. + let words_lo = _mm_unpacklo_epi16(swapped, zero); + let words_hi = _mm_unpackhi_epi16(swapped, zero); sum_lo = _mm_add_epi32(sum_lo, words_lo); sum_hi = _mm_add_epi32(sum_hi, words_hi); } - // Combine lo and hi accumulators. let sum = _mm_add_epi32(sum_lo, sum_hi); - - // Horizontal sum of the four 32-bit lanes. - // _mm_hadd_epi32: [a,b,c,d] + [a,b,c,d] => [a+b,c+d,a+b,c+d] let hadd1 = _mm_hadd_epi32(sum, sum); let hadd2 = _mm_hadd_epi32(hadd1, hadd1); - let sum32 = _mm_cvtsi128_si32(hadd2) as u32; + let mut scalar_sum = _mm_cvtsi128_si32(hadd2) as u32; - // Process remainder bytes using scalar code. - let mut scalar_sum = sum32; let mut i = 0; while i + 1 < remainder.len() { - // Read big-endian 16-bit word. let word = u16::from_be_bytes([remainder[i], remainder[i + 1]]); scalar_sum = scalar_sum.wrapping_add(word as u32); i += 2; } - - // Handle odd byte (padded with zero on the right in network order). if i < remainder.len() { scalar_sum = scalar_sum.wrapping_add((remainder[i] as u32) << 8); } - - // Fold 32-bit sum into 16-bit checksum. - while scalar_sum > 0xFFFF { - scalar_sum = (scalar_sum & 0xFFFF) + (scalar_sum >> 16); - } - - !scalar_sum as u16 + scalar_sum } } -/// SIMD-optimized checksum for x86_64 (safe wrapper). +/// Full Internet checksum using the SIMD path when available. /// -/// This function detects SSSE3 support at runtime and uses the optimized -/// SIMD implementation if available, otherwise falls back to scalar. -#[cfg(target_arch = "x86_64")] +/// Prefer [`checksum`] for call sites: it already selects SIMD for long +/// buffers. This entry point forces the fast path for benches and tests. #[inline] pub fn checksum_simd(data: &[u8]) -> u16 { - // Check for SSSE3 support at runtime. - // On modern x86_64 CPUs, SSSE3 is virtually always available. - if is_x86_feature_detected!("ssse3") { - // SAFETY: We just verified SSSE3 is available. - unsafe { checksum_simd_ssse3(data) } - } else { - // Fallback to scalar implementation for ancient CPUs. - checksum(data) - } + checksum_fold(checksum_add_fast(data)) } -/// Fallback for non-SIMD architectures. -#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] -pub fn checksum_simd(data: &[u8]) -> u16 { - checksum(data) +/// Deprecated name kept for external callers that linked the old NEON entry. +#[cfg(target_arch = "aarch64")] +#[target_feature(enable = "neon")] +#[inline] +pub unsafe fn checksum_simd_neon(data: &[u8]) -> u16 { + // SAFETY: NEON mandatory on AArch64. + checksum_fold(unsafe { checksum_add_neon(data) }) } #[cfg(test)] @@ -444,8 +395,43 @@ mod tests { #[test] fn test_checksum_simd() { let data: Vec = (0..100).collect(); - let scalar = checksum(&data); + let scalar = checksum_fold(checksum_add_scalar(&data)); let simd = checksum_simd(&data); assert_eq!(scalar, simd); } + + #[test] + fn test_checksum_add_fast_matches_scalar_long() { + let data: Vec = (0..4096).map(|i| (i % 251) as u8).collect(); + assert_eq!(checksum_add_scalar(&data), checksum_add_fast(&data)); + assert_eq!( + checksum_fold(checksum_add_scalar(&data)), + checksum(&data), + "checksum() must match pure scalar on long buffers" + ); + } + + #[test] + fn test_tcp_checksum_long_payload_matches_scalar() { + let mut segment = vec![0u8; 20 + 1500]; + segment[12] = 0x50; // data offset 5 + for (i, b) in segment[20..].iter_mut().enumerate() { + *b = (i % 251) as u8; + } + // Force scalar path for expected: zero checksum field then compute via scalar add. + segment[16] = 0; + segment[17] = 0; + let src = [10, 0, 0, 1]; + let dst = [10, 0, 0, 2]; + let mut sum = 0u32; + sum = sum.wrapping_add(u16::from_be_bytes([src[0], src[1]]) as u32); + sum = sum.wrapping_add(u16::from_be_bytes([src[2], src[3]]) as u32); + sum = sum.wrapping_add(u16::from_be_bytes([dst[0], dst[1]]) as u32); + sum = sum.wrapping_add(u16::from_be_bytes([dst[2], dst[3]]) as u32); + sum = sum.wrapping_add(6); + sum = sum.wrapping_add(segment.len() as u32); + sum = sum.wrapping_add(checksum_add_scalar(&segment)); + let expected = checksum_fold(sum); + assert_eq!(tcp_checksum(src, dst, &segment), expected); + } } From 82b17fe6569727266cf90292899eebf01aeaaa11 Mon Sep 17 00:00:00 2001 From: dyxushuai Date: Wed, 22 Jul 2026 15:38:09 +0800 Subject: [PATCH 2/3] refactor(packet): deprecate checksum_simd_neon and tidy SIMD dispatch Mark the legacy NEON entry #[deprecated] and use consistent expression-style cfg branches in checksum_add_fast. --- common/arcbox-packet/src/checksum.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/arcbox-packet/src/checksum.rs b/common/arcbox-packet/src/checksum.rs index c61ed73ba..a265f5d67 100644 --- a/common/arcbox-packet/src/checksum.rs +++ b/common/arcbox-packet/src/checksum.rs @@ -180,7 +180,7 @@ fn checksum_add_fast(data: &[u8]) -> u32 { #[cfg(target_arch = "aarch64")] { // SAFETY: NEON is mandatory on AArch64. - return unsafe { checksum_add_neon(data) }; + unsafe { checksum_add_neon(data) } } #[cfg(target_arch = "x86_64")] { @@ -288,7 +288,10 @@ pub fn checksum_simd(data: &[u8]) -> u16 { } /// Deprecated name kept for external callers that linked the old NEON entry. +/// +/// Prefer [`checksum`] or [`checksum_simd`]. #[cfg(target_arch = "aarch64")] +#[deprecated(note = "use checksum() or checksum_simd() instead")] #[target_feature(enable = "neon")] #[inline] pub unsafe fn checksum_simd_neon(data: &[u8]) -> u16 { From d533def0d875669799eae17f6519f06fe09cd21c Mon Sep 17 00:00:00 2001 From: dyxushuai Date: Wed, 5 Aug 2026 11:08:32 +0800 Subject: [PATCH 3/3] fix(packet): avoid checksum SIMD regressions --- .../arcbox-packet/examples/checksum_bench.rs | 15 +-- common/arcbox-packet/src/checksum.rs | 98 +++++++++++++------ 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/common/arcbox-packet/examples/checksum_bench.rs b/common/arcbox-packet/examples/checksum_bench.rs index ff9fb59a5..d56e1975e 100644 --- a/common/arcbox-packet/examples/checksum_bench.rs +++ b/common/arcbox-packet/examples/checksum_bench.rs @@ -1,4 +1,4 @@ -//! Microbench: scalar vs SIMD Internet checksum throughput. +//! Microbench: dispatched, scalar, and SIMD Internet checksum throughput. //! //! ```text //! cargo run -p arcbox-packet --example checksum_bench --release @@ -6,7 +6,7 @@ use std::time::Instant; -use arcbox_packet::checksum::{checksum_add_scalar, checksum_fold, checksum_simd}; +use arcbox_packet::checksum::{checksum, checksum_add_scalar, checksum_fold, checksum_simd}; fn bench(label: &str, len: usize, iters: usize, f: impl Fn(&[u8]) -> u16) { let data: Vec = (0..len).map(|i| (i % 251) as u8).collect(); @@ -28,6 +28,10 @@ fn bench(label: &str, len: usize, iters: usize, f: impl Fn(&[u8]) -> u16) { ); } +fn checksum_scalar(data: &[u8]) -> u16 { + checksum_fold(checksum_add_scalar(data)) +} + fn main() { println!( "# Internet checksum microbench (host = {})", @@ -35,11 +39,10 @@ fn main() { ); println!(); - for &len in &[64usize, 1500, 9000, 16384, 65536] { + for &len in &[63usize, 64, 65, 1500, 9000, 16384, 65536] { let iters = (50_000_000 / len).max(2_000); - bench("scalar", len, iters, |d| { - checksum_fold(checksum_add_scalar(d)) - }); + bench("checksum", len, iters, checksum); + bench("scalar", len, iters, checksum_scalar); bench("simd", len, iters, checksum_simd); let data: Vec = (0..len).map(|i| (i % 251) as u8).collect(); assert_eq!( diff --git a/common/arcbox-packet/src/checksum.rs b/common/arcbox-packet/src/checksum.rs index a265f5d67..9b7d9b19e 100644 --- a/common/arcbox-packet/src/checksum.rs +++ b/common/arcbox-packet/src/checksum.rs @@ -19,12 +19,12 @@ const SIMD_THRESHOLD: usize = 64; /// Calculates the ones' complement sum of 16-bit words. /// -/// This is the core operation for IP/TCP/UDP checksums. Buffers of -/// [`SIMD_THRESHOLD`] bytes or more use the architecture SIMD path when -/// available (NEON / SSSE3); shorter buffers stay on the scalar loop. +/// This is the core operation for IP/TCP/UDP checksums. Buffers longer than +/// [`SIMD_THRESHOLD`] bytes use the architecture SIMD path when available +/// (NEON / SSSE3); shorter buffers stay on the scalar loop. #[inline] pub fn checksum_add(data: &[u8]) -> u32 { - if data.len() >= SIMD_THRESHOLD { + if data.len() > SIMD_THRESHOLD { checksum_add_fast(data) } else { checksum_add_scalar(data) @@ -177,11 +177,15 @@ pub fn udp_checksum(src_ip: [u8; 4], dst_ip: [u8; 4], udp_datagram: &[u8]) -> u1 /// Ones' complement sum via the fastest available path for this host. #[inline] fn checksum_add_fast(data: &[u8]) -> u32 { - #[cfg(target_arch = "aarch64")] + #[cfg(all(target_arch = "aarch64", target_endian = "little"))] { // SAFETY: NEON is mandatory on AArch64. unsafe { checksum_add_neon(data) } } + #[cfg(all(target_arch = "aarch64", target_endian = "big"))] + { + checksum_add_scalar(data) + } #[cfg(target_arch = "x86_64")] { if is_x86_feature_detected!("ssse3") { @@ -200,26 +204,56 @@ fn checksum_add_fast(data: &[u8]) -> u32 { /// SIMD ones' complement sum for ARM64 NEON (16 bytes/iter, BE halfwords). /// /// # Safety -/// Requires NEON (`#[target_feature(enable = "neon")]`). Always true on AArch64. -#[cfg(target_arch = "aarch64")] +/// Requires NEON (`#[target_feature(enable = "neon")]`) on little-endian +/// AArch64. +#[cfg(all(target_arch = "aarch64", target_endian = "little"))] #[target_feature(enable = "neon")] unsafe fn checksum_add_neon(data: &[u8]) -> u32 { use std::arch::aarch64::*; // SAFETY: NEON available; loads stay within `data`. unsafe { - let mut sum = vdupq_n_u32(0); + let zero = vdupq_n_u32(0); + let mut sum0 = zero; + let mut sum1 = zero; + let mut sum2 = zero; + let mut sum3 = zero; let (chunks, remainder) = data.as_chunks::<16>(); + let (groups, group_remainder) = chunks.as_chunks::<4>(); - for chunk in chunks { - let bytes = vld1q_u8(chunk.as_ptr()); + for group in groups { + let bytes = vld1q_u8(group[0].as_ptr()); // Network order is BE; swap adjacent bytes on LE host. let swapped = vrev16q_u8(bytes); let words = vreinterpretq_u16_u8(swapped); - sum = vpadalq_u16(sum, words); + sum0 = vpadalq_u16(sum0, words); + + let bytes = vld1q_u8(group[1].as_ptr()); + let swapped = vrev16q_u8(bytes); + let words = vreinterpretq_u16_u8(swapped); + sum1 = vpadalq_u16(sum1, words); + + let bytes = vld1q_u8(group[2].as_ptr()); + let swapped = vrev16q_u8(bytes); + let words = vreinterpretq_u16_u8(swapped); + sum2 = vpadalq_u16(sum2, words); + + let bytes = vld1q_u8(group[3].as_ptr()); + let swapped = vrev16q_u8(bytes); + let words = vreinterpretq_u16_u8(swapped); + sum3 = vpadalq_u16(sum3, words); } - let mut scalar_sum = vaddvq_u32(sum); + for chunk in group_remainder { + let bytes = vld1q_u8(chunk.as_ptr()); + let swapped = vrev16q_u8(bytes); + let words = vreinterpretq_u16_u8(swapped); + sum0 = vpadalq_u16(sum0, words); + } + + let sum01 = vaddq_u32(sum0, sum1); + let sum23 = vaddq_u32(sum2, sum3); + let mut scalar_sum = vaddvq_u32(vaddq_u32(sum01, sum23)); let mut i = 0; while i + 1 < remainder.len() { let word = u16::from_be_bytes([remainder[i], remainder[i + 1]]); @@ -287,18 +321,6 @@ pub fn checksum_simd(data: &[u8]) -> u16 { checksum_fold(checksum_add_fast(data)) } -/// Deprecated name kept for external callers that linked the old NEON entry. -/// -/// Prefer [`checksum`] or [`checksum_simd`]. -#[cfg(target_arch = "aarch64")] -#[deprecated(note = "use checksum() or checksum_simd() instead")] -#[target_feature(enable = "neon")] -#[inline] -pub unsafe fn checksum_simd_neon(data: &[u8]) -> u16 { - // SAFETY: NEON mandatory on AArch64. - checksum_fold(unsafe { checksum_add_neon(data) }) -} - #[cfg(test)] mod tests { use super::*; @@ -404,14 +426,26 @@ mod tests { } #[test] - fn test_checksum_add_fast_matches_scalar_long() { - let data: Vec = (0..4096).map(|i| (i % 251) as u8).collect(); - assert_eq!(checksum_add_scalar(&data), checksum_add_fast(&data)); - assert_eq!( - checksum_fold(checksum_add_scalar(&data)), - checksum(&data), - "checksum() must match pure scalar on long buffers" - ); + fn test_checksum_paths_match_scalar() { + for len in 0..=600 { + let data: Vec = (0..len).map(|i| (i % 251) as u8).collect(); + let expected = checksum_add_scalar(&data); + assert_eq!( + checksum_add(&data), + expected, + "dispatch mismatch at len={len}" + ); + assert_eq!( + checksum_add_fast(&data), + expected, + "fast mismatch at len={len}" + ); + assert_eq!( + checksum_simd(&data), + checksum_fold(expected), + "checksum mismatch at len={len}" + ); + } } #[test]