#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::similar_names)]
#![allow(clippy::unreadable_literal)]
use super::hasher::BloomHasher;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
const PRIME1: u64 = 0x9e3779b97f4a7c15;
const PRIME2: u64 = 0x517cc1b727220a95;
const PRIME3: u64 = 0x85ebca77c2b2ae63;
const SIMD_THRESHOLD: usize = 8;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CpuFeatures {
pub has_avx2: bool,
pub has_neon: bool,
}
impl CpuFeatures {
#[must_use]
pub fn detect() -> Self {
#[cfg(target_arch = "x86_64")]
{
Self {
has_avx2: is_x86_feature_detected!("avx2"),
has_neon: false,
}
}
#[cfg(target_arch = "aarch64")]
{
Self {
has_avx2: false,
has_neon: std::arch::is_aarch64_feature_detected!("neon"),
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
Self {
has_avx2: false,
has_neon: false,
}
}
}
#[must_use]
pub const fn has_simd(self) -> bool {
self.has_avx2 || self.has_neon
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SimdHasher {
seed: u64,
features: CpuFeatures,
}
impl SimdHasher {
#[must_use]
pub fn new() -> Self {
Self::with_seed(0)
}
#[must_use]
pub fn with_seed(seed: u64) -> Self {
Self {
seed,
features: CpuFeatures::detect(),
}
}
#[must_use]
pub const fn features(&self) -> CpuFeatures {
self.features
}
#[inline]
pub fn hash_u64(&self, value: u64) -> u64 {
let mut h = value ^ self.seed;
h = h.wrapping_mul(PRIME1);
h ^= h >> 33;
h = h.wrapping_mul(PRIME2);
h ^= h >> 29;
h = h.wrapping_mul(PRIME3);
h ^= h >> 32;
h
}
pub fn hash_batch_u64(&self, values: &[u64]) -> Vec<u64> {
if values.len() < SIMD_THRESHOLD {
return self.hash_batch_u64_scalar(values);
}
#[cfg(target_arch = "x86_64")]
{
if self.features.has_avx2 {
return unsafe { self.hash_batch_u64_avx2(values) };
}
}
#[cfg(target_arch = "aarch64")]
{
if self.features.has_neon {
return self.hash_batch_u64_scalar(values);
}
}
self.hash_batch_u64_scalar(values)
}
fn hash_batch_u64_scalar(&self, values: &[u64]) -> Vec<u64> {
let mut result = Vec::with_capacity(values.len());
let mut chunks = values.chunks_exact(4);
for chunk in &mut chunks {
result.push(self.hash_u64(chunk[0]));
result.push(self.hash_u64(chunk[1]));
result.push(self.hash_u64(chunk[2]));
result.push(self.hash_u64(chunk[3]));
}
for &value in chunks.remainder() {
result.push(self.hash_u64(value));
}
result
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn hash_batch_u64_avx2(&self, values: &[u64]) -> Vec<u64> {
let mut result = Vec::with_capacity(values.len());
let seed_vec = _mm256_set1_epi64x(self.seed as i64);
let prime1_vec = _mm256_set1_epi64x(PRIME1 as i64);
let prime2_vec = _mm256_set1_epi64x(PRIME2 as i64);
let prime3_vec = _mm256_set1_epi64x(PRIME3 as i64);
let mut chunks = values.chunks_exact(4);
for chunk in &mut chunks {
let v = _mm256_loadu_si256(chunk.as_ptr().cast::<__m256i>());
let v = _mm256_xor_si256(v, seed_vec);
let v = mul_u64x4(v, prime1_vec);
let v = _mm256_xor_si256(v, _mm256_srli_epi64(v, 33));
let v = mul_u64x4(v, prime2_vec);
let v = _mm256_xor_si256(v, _mm256_srli_epi64(v, 29));
let v = mul_u64x4(v, prime3_vec);
let v = _mm256_xor_si256(v, _mm256_srli_epi64(v, 32));
let mut temp = [0u64; 4];
_mm256_storeu_si256(temp.as_mut_ptr().cast::<__m256i>(), v);
result.extend_from_slice(&temp);
}
for &value in chunks.remainder() {
result.push(self.hash_u64(value));
}
result
}
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn mul_u64x4(a: __m256i, b: __m256i) -> __m256i {
let a_hi = _mm256_srli_epi64(a, 32);
let b_hi = _mm256_srli_epi64(b, 32);
let lo_lo = _mm256_mul_epu32(a, b); let lo_hi = _mm256_mul_epu32(a, b_hi); let hi_lo = _mm256_mul_epu32(a_hi, b);
let cross = _mm256_add_epi64(lo_hi, hi_lo);
let cross_shifted = _mm256_slli_epi64(cross, 32);
_mm256_add_epi64(lo_lo, cross_shifted)
}
impl Default for SimdHasher {
fn default() -> Self {
Self::new()
}
}
impl BloomHasher for SimdHasher {
#[inline]
fn hash_bytes(&self, bytes: &[u8]) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut state = FNV_OFFSET ^ self.seed;
for &b in bytes {
state ^= b as u64;
state = state.wrapping_mul(FNV_PRIME);
}
self.hash_u64(state)
}
#[inline]
fn hash_bytes_with_seed(&self, bytes: &[u8], seed: u64) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut state = FNV_OFFSET ^ self.seed.wrapping_add(seed);
for &b in bytes {
state ^= b as u64;
state = state.wrapping_mul(FNV_PRIME);
}
self.hash_u64(state)
}
#[inline]
fn hash_bytes_pair(&self, bytes: &[u8]) -> (u64, u64) {
let h1 = self.hash_bytes(bytes);
let h2 = self.hash_bytes_with_seed(bytes, PRIME2);
(h1, h2)
}
#[inline]
fn name(&self) -> &'static str {
"SimdHasher"
}
#[inline]
fn instance_token(&self) -> u64 {
self.seed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_features_detect() {
let features = CpuFeatures::detect();
#[cfg(target_arch = "x86_64")]
{
let _ = features.has_avx2;
assert!(!features.has_neon);
}
#[cfg(target_arch = "aarch64")]
{
assert!(features.has_neon);
assert!(!features.has_avx2);
}
}
#[test]
fn test_cpu_features_has_simd() {
let features = CpuFeatures::detect();
let _ = features.has_simd();
}
#[test]
fn test_hasher_creation() {
let hasher = SimdHasher::new();
assert_eq!(hasher.seed, 0);
let hasher_seeded = SimdHasher::with_seed(42);
assert_eq!(hasher_seeded.seed, 42);
}
#[test]
fn test_hasher_default() {
let hasher: SimdHasher = Default::default();
assert_eq!(hasher.seed, 0);
}
#[test]
fn test_hasher_features() {
let hasher = SimdHasher::new();
let features = hasher.features();
let _ = features.has_simd();
}
#[test]
fn test_hash_u64_deterministic() {
let hasher = SimdHasher::new();
let value = 0x123456789abcdef0;
let h1 = hasher.hash_u64(value);
let h2 = hasher.hash_u64(value);
assert_eq!(h1, h2);
}
#[test]
fn test_hash_u64_different_inputs() {
let hasher = SimdHasher::new();
let h1 = hasher.hash_u64(1);
let h2 = hasher.hash_u64(2);
assert_ne!(h1, h2);
}
#[test]
fn test_hash_u64_different_seeds() {
let hasher1 = SimdHasher::with_seed(1);
let hasher2 = SimdHasher::with_seed(2);
let h1 = hasher1.hash_u64(42);
let h2 = hasher2.hash_u64(42);
assert_ne!(h1, h2);
}
#[test]
fn test_hash_batch_u64_empty() {
let hasher = SimdHasher::new();
let values: Vec<u64> = vec![];
let hashes = hasher.hash_batch_u64(&values);
assert_eq!(hashes.len(), 0);
}
#[test]
fn test_hash_batch_u64_small_batch() {
let hasher = SimdHasher::new();
let values = vec![1u64, 2, 3, 4];
let batch_hashes = hasher.hash_batch_u64(&values);
assert_eq!(batch_hashes.len(), 4);
for (i, &value) in values.iter().enumerate() {
assert_eq!(batch_hashes[i], hasher.hash_u64(value));
}
}
#[test]
fn test_hash_batch_u64_exact_threshold() {
let hasher = SimdHasher::new();
let values: Vec<u64> = (0..SIMD_THRESHOLD as u64).collect();
let batch_hashes = hasher.hash_batch_u64(&values);
assert_eq!(batch_hashes.len(), SIMD_THRESHOLD);
for (i, &value) in values.iter().enumerate() {
assert_eq!(batch_hashes[i], hasher.hash_u64(value));
}
}
#[test]
fn test_hash_batch_u64_large() {
let hasher = SimdHasher::new();
let values: Vec<u64> = (0..1000).collect();
let hashes = hasher.hash_batch_u64(&values);
assert_eq!(hashes.len(), 1000);
for (i, &value) in values.iter().enumerate() {
assert_eq!(hashes[i], hasher.hash_u64(value));
}
}
#[test]
fn test_hash_batch_u64_not_multiple_of_4() {
let hasher = SimdHasher::new();
let values: Vec<u64> = (0..17).collect();
let hashes = hasher.hash_batch_u64(&values);
assert_eq!(hashes.len(), 17);
for (i, &value) in values.iter().enumerate() {
assert_eq!(hashes[i], hasher.hash_u64(value));
}
}
#[test]
fn test_scalar_matches_simd() {
let hasher = SimdHasher::new();
let values: Vec<u64> = (0..100).collect();
let scalar_result = hasher.hash_batch_u64_scalar(&values);
let simd_result = hasher.hash_batch_u64(&values);
assert_eq!(scalar_result, simd_result);
}
#[test]
fn test_scalar_various_sizes() {
let hasher = SimdHasher::new();
for size in [0usize, 1, 3, 4, 7, 8, 15, 16, 17, 63, 64, 65, 100] {
let values: Vec<u64> = (0..size as u64).collect();
let scalar_result = hasher.hash_batch_u64_scalar(&values);
assert_eq!(scalar_result.len(), size);
for (i, &value) in values.iter().enumerate() {
assert_eq!(scalar_result[i], hasher.hash_u64(value));
}
}
}
#[test]
fn test_avalanche_property() {
let hasher = SimdHasher::new();
let h1 = hasher.hash_u64(0);
let h2 = hasher.hash_u64(1);
let diff = h1 ^ h2;
let changed_bits = diff.count_ones();
assert!(
(20..=44).contains(&changed_bits),
"Avalanche check: {} bits changed (expected 20-44)",
changed_bits
);
}
#[test]
fn test_bloom_hasher_hash_bytes() {
let hasher = SimdHasher::new();
let h1 = hasher.hash_bytes(b"test");
let h2 = hasher.hash_bytes(b"test");
assert_eq!(h1, h2);
let h3 = hasher.hash_bytes(b"different");
assert_ne!(h1, h3);
}
#[test]
fn test_bloom_hasher_hash_bytes_pair() {
let hasher = SimdHasher::new();
let (h1, h2) = hasher.hash_bytes_pair(b"test");
assert_ne!(h1, h2, "Pair should be independent");
let (h1_b, h2_b) = hasher.hash_bytes_pair(b"test");
assert_eq!(h1, h1_b);
assert_eq!(h2, h2_b);
}
#[test]
fn test_bloom_hasher_name() {
let hasher = SimdHasher::new();
assert_eq!(hasher.name(), "SimdHasher");
}
#[test]
fn test_instance_token_reflects_seed() {
let h1 = SimdHasher::with_seed(1);
let h2 = SimdHasher::with_seed(2);
assert_ne!(h1.instance_token(), h2.instance_token());
assert_eq!(h1.instance_token(), 1);
assert_eq!(h2.instance_token(), 2);
}
#[test]
fn test_instance_token_consistent_for_same_seed() {
assert_eq!(
SimdHasher::with_seed(42).instance_token(),
SimdHasher::with_seed(42).instance_token()
);
}
#[test]
fn test_integration_with_strategies() {
use crate::hash::strategies::{DoubleHashing, HashStrategy};
let hasher = SimdHasher::new();
let strategy = DoubleHashing;
let data = b"test";
let (h1, h2) = hasher.hash_bytes_pair(data);
let indices = strategy.generate_indices(h1, h2, 0, 7, 1000);
assert_eq!(indices.len(), 7);
assert!(indices.iter().all(|&idx| idx < 1000));
}
#[test]
fn test_no_collisions_sequential() {
let hasher = SimdHasher::new();
let values: Vec<u64> = (0..1000).collect();
let hashes = hasher.hash_batch_u64(&values);
let unique: std::collections::HashSet<_> = hashes.iter().copied().collect();
assert_eq!(unique.len(), 1000, "Detected hash collisions");
}
#[test]
fn test_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SimdHasher>();
}
#[test]
fn test_copy() {
let hasher1 = SimdHasher::with_seed(42);
let hasher2 = hasher1;
assert_eq!(hasher1.seed, hasher2.seed);
let value = 123u64;
assert_eq!(hasher1.hash_u64(value), hasher2.hash_u64(value));
}
}