use crate::Backend;
#[inline]
#[must_use]
pub fn hash_key(key: &str) -> u64 {
hash_bytes(key.as_bytes())
}
#[inline]
#[must_use]
pub fn hash_bytes(bytes: &[u8]) -> u64 {
const K: u64 = 0x517c_c1b7_2722_0a95;
let mut hash: u64 = 0;
let chunks = bytes.chunks_exact(8);
let remainder = chunks.remainder();
for chunk in chunks {
let word =
u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) guarantees 8 bytes"));
hash = hash.rotate_left(5).bitxor(word).wrapping_mul(K);
}
for &byte in remainder {
hash = hash.rotate_left(5).bitxor(u64::from(byte)).wrapping_mul(K);
}
hash
}
#[must_use]
pub fn hash_keys_batch(keys: &[&str]) -> Vec<u64> {
hash_keys_batch_with_backend(keys, Backend::Auto)
}
#[must_use]
pub fn hash_keys_batch_with_backend(keys: &[&str], backend: Backend) -> Vec<u64> {
match backend {
Backend::Auto => {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
return hash_keys_avx2(keys);
}
}
hash_keys_scalar(keys)
}
Backend::AVX2 | Backend::AVX512 => hash_keys_avx2_or_scalar(keys),
Backend::Scalar
| Backend::SSE2
| Backend::AVX
| Backend::NEON
| Backend::WasmSIMD
| Backend::GPU => hash_keys_scalar(keys),
}
}
#[inline]
fn hash_keys_scalar(keys: &[&str]) -> Vec<u64> {
keys.iter().map(|k| hash_key(k)).collect()
}
#[inline]
fn hash_keys_avx2_or_scalar(keys: &[&str]) -> Vec<u64> {
#[cfg(target_arch = "x86_64")]
{
hash_keys_avx2(keys)
}
#[cfg(not(target_arch = "x86_64"))]
{
hash_keys_scalar(keys)
}
}
#[cfg(target_arch = "x86_64")]
fn hash_keys_avx2(keys: &[&str]) -> Vec<u64> {
hash_keys_scalar(keys)
}
use std::ops::BitXor;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_key_deterministic() {
let h1 = hash_key("hello");
let h2 = hash_key("hello");
assert_eq!(h1, h2, "Same key must produce same hash");
}
#[test]
fn test_hash_key_different_keys() {
let h1 = hash_key("hello");
let h2 = hash_key("world");
assert_ne!(h1, h2, "Different keys should produce different hashes");
}
#[test]
fn test_hash_key_empty() {
let h = hash_key("");
assert_eq!(h, 0);
}
#[test]
fn test_hash_key_single_char() {
let h = hash_key("a");
assert_ne!(h, 0);
}
#[test]
fn test_hash_key_long_string() {
let long = "a".repeat(1000);
let h = hash_key(&long);
assert_ne!(h, 0);
}
#[test]
fn test_hash_bytes_matches_key() {
let key = "test_key";
assert_eq!(hash_key(key), hash_bytes(key.as_bytes()));
}
#[test]
fn test_hash_keys_batch_empty() {
let keys: &[&str] = &[];
let hashes = hash_keys_batch(keys);
assert!(hashes.is_empty());
}
#[test]
fn test_hash_keys_batch_single() {
let hashes = hash_keys_batch(&["hello"]);
assert_eq!(hashes.len(), 1);
assert_eq!(hashes[0], hash_key("hello"));
}
#[test]
fn test_hash_keys_batch_multiple() {
let keys = ["a", "b", "c", "d"];
let hashes = hash_keys_batch(&keys);
assert_eq!(hashes.len(), 4);
for (i, key) in keys.iter().enumerate() {
assert_eq!(hashes[i], hash_key(key), "Batch hash must match single hash");
}
}
#[test]
fn test_hash_keys_batch_large() {
let keys: Vec<&str> = (0..100)
.map(|i| {
Box::leak(format!("key{i}").into_boxed_str()) as &str
})
.collect();
let hashes = hash_keys_batch(&keys);
assert_eq!(hashes.len(), 100);
let unique: std::collections::HashSet<_> = hashes.iter().collect();
assert_eq!(unique.len(), 100, "All keys should have unique hashes");
}
#[test]
fn test_backend_parity_scalar_vs_auto() {
let keys = ["foo", "bar", "baz", "qux"];
let scalar = hash_keys_batch_with_backend(&keys, Backend::Scalar);
let auto = hash_keys_batch_with_backend(&keys, Backend::Auto);
assert_eq!(scalar, auto, "Scalar and Auto must produce identical results");
}
#[test]
fn test_hash_distribution() {
let keys: Vec<String> = (0..1000).map(|i| format!("key{i}")).collect();
let refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
let hashes = hash_keys_batch(&refs);
let high_bits_used = hashes.iter().any(|h| h >> 56 != 0);
assert!(high_bits_used, "Hash should use high bits");
let low_nibbles: std::collections::HashSet<_> = hashes.iter().map(|h| h & 0xF).collect();
assert!(low_nibbles.len() >= 8, "Hash should have varied low bits");
}
#[test]
fn test_hash_avalanche_single_bit() {
let h1 = hash_key("aaa");
let h2 = hash_key("aab");
let diff = (h1 ^ h2).count_ones();
assert!(diff >= 15, "Avalanche effect: {} bits differ, expected >=15", diff);
}
#[test]
fn test_backend_avx2_explicit() {
let keys = ["foo", "bar", "baz", "qux"];
let avx2 = hash_keys_batch_with_backend(&keys, Backend::AVX2);
let scalar = hash_keys_batch_with_backend(&keys, Backend::Scalar);
assert_eq!(avx2, scalar, "AVX2 must match Scalar");
}
#[test]
fn test_backend_avx512_explicit() {
let keys = ["foo", "bar", "baz", "qux"];
let avx512 = hash_keys_batch_with_backend(&keys, Backend::AVX512);
let scalar = hash_keys_batch_with_backend(&keys, Backend::Scalar);
assert_eq!(avx512, scalar, "AVX512 must match Scalar");
}
#[test]
fn test_backend_sse2_fallback() {
let keys = ["a", "b", "c"];
let sse2 = hash_keys_batch_with_backend(&keys, Backend::SSE2);
let scalar = hash_keys_batch_with_backend(&keys, Backend::Scalar);
assert_eq!(sse2, scalar, "SSE2 must fall back to Scalar");
}
#[test]
fn test_backend_neon_fallback() {
let keys = ["x", "y", "z"];
let neon = hash_keys_batch_with_backend(&keys, Backend::NEON);
let scalar = hash_keys_batch_with_backend(&keys, Backend::Scalar);
assert_eq!(neon, scalar, "NEON must fall back to Scalar");
}
#[test]
fn test_hash_keys_avx2_or_scalar_coverage() {
let keys = ["test1", "test2"];
let result = hash_keys_batch_with_backend(&keys, Backend::AVX2);
assert_eq!(result.len(), 2);
assert_eq!(result[0], hash_key("test1"));
assert_eq!(result[1], hash_key("test2"));
}
}