Coverage Report

Created: 2025-09-19 09:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/gh-runner/action-runner3/_work/feoxdb/feoxdb/src/utils/hash.rs
Line
Count
Source
1
use std::hash::Hasher;
2
3
const C1: u32 = 0xcc9e2d51;
4
const C2: u32 = 0x1b873593;
5
const R1: u32 = 15;
6
const R2: u32 = 13;
7
const M: u32 = 5;
8
const N: u32 = 0xe6546b64;
9
10
#[inline(always)]
11
12.3k
pub fn murmur3_32(key: &[u8], seed: u32) -> u32 {
12
12.3k
    let mut h = seed;
13
12.3k
    let mut chunks = key.chunks_exact(4);
14
15
25.3k
    for chunk in 
chunks12.3k
.
by_ref12.3k
() {
16
25.3k
        let mut k = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
17
25.3k
        k = k.wrapping_mul(C1);
18
25.3k
        k = k.rotate_left(R1);
19
25.3k
        k = k.wrapping_mul(C2);
20
25.3k
21
25.3k
        h ^= k;
22
25.3k
        h = h.rotate_left(R2);
23
25.3k
        h = h.wrapping_mul(M).wrapping_add(N);
24
25.3k
    }
25
26
12.3k
    let remainder = chunks.remainder();
27
12.3k
    if !remainder.is_empty() {
28
3.09k
        let mut k = 0u32;
29
5.33k
        for (i, &byte) in 
remainder3.09k
.
iter3.09k
().
enumerate3.09k
() {
30
5.33k
            k |= (byte as u32) << (i * 8);
31
5.33k
        }
32
33
3.09k
        k = k.wrapping_mul(C1);
34
3.09k
        k = k.rotate_left(R1);
35
3.09k
        k = k.wrapping_mul(C2);
36
3.09k
        h ^= k;
37
9.23k
    }
38
39
12.3k
    h ^= key.len() as u32;
40
12.3k
    h ^= h >> 16;
41
12.3k
    h = h.wrapping_mul(0x85ebca6b);
42
12.3k
    h ^= h >> 13;
43
12.3k
    h = h.wrapping_mul(0xc2b2ae35);
44
12.3k
    h ^= h >> 16;
45
46
12.3k
    h
47
12.3k
}
48
49
/// Compute hash of a key using the best available method.
50
///
51
/// On x86_64 CPUs with AES-NI support, uses hardware-accelerated hashing.
52
/// Otherwise falls back to MurmurHash3.
53
1.01k
pub fn hash_key(key: &[u8]) -> u32 {
54
    #[cfg(target_arch = "x86_64")]
55
    {
56
1.01k
        if let Some(hash) = simd::hash_key_aes_safe(key) {
57
1.01k
            return hash;
58
0
        }
59
    }
60
0
    murmur3_32(key, 0)
61
1.01k
}
62
63
#[cfg(target_arch = "x86_64")]
64
pub mod simd {
65
    use std::arch::x86_64::*;
66
67
    /// Compute hash using AES-NI instructions for better performance.
68
    ///
69
    /// Returns None if AES-NI is not supported on this CPU.
70
1.01k
    pub fn hash_key_aes_safe(key: &[u8]) -> Option<u32> {
71
1.01k
        if is_x86_feature_detected!("aes") {
72
1.01k
            unsafe { Some(hash_key_aes_unsafe(key)) }
73
        } else {
74
0
            None
75
        }
76
1.01k
    }
77
78
    /// Compute hash using AES-NI instructions for better performance.
79
    ///
80
    /// # Safety
81
    ///
82
    /// This function requires the CPU to support AES-NI instructions.
83
    /// The caller must ensure that the CPU supports AES-NI before calling this function.
84
    /// This is typically checked using `is_x86_feature_detected!("aes")`.
85
    #[target_feature(enable = "aes")]
86
    #[inline]
87
1.01k
    unsafe fn hash_key_aes_unsafe(key: &[u8]) -> u32 {
88
        // Use a non-zero initial seed for better distribution
89
1.01k
        let seed = _mm_set_epi32(
90
1.01k
            0x9747b28c_u32 as i32,
91
1.01k
            0xc2b2ae35_u32 as i32,
92
1.01k
            0x85ebca6b_u32 as i32,
93
1.01k
            0xe6546b64_u32 as i32,
94
        );
95
1.01k
        let mut hash = seed;
96
97
        // Process full 16-byte chunks
98
1.01k
        let mut chunks = key.chunks_exact(16);
99
1.01k
        for 
chunk0
in chunks.by_ref() {
100
0
            let data = _mm_loadu_si128(chunk.as_ptr() as *const __m128i);
101
0
            // Mix the data with XOR before AES round
102
0
            hash = _mm_xor_si128(hash, data);
103
0
            // Apply AES round
104
0
            hash = _mm_aesenc_si128(hash, seed);
105
0
        }
106
107
        // Handle remainder
108
1.01k
        let remainder = chunks.remainder();
109
1.01k
        if !remainder.is_empty() {
110
1.01k
            let mut buffer = [0u8; 16];
111
1.01k
            buffer[..remainder.len()].copy_from_slice(remainder);
112
1.01k
            // Mix in the length of remainder for better distribution
113
1.01k
            buffer[15] = remainder.len() as u8;
114
1.01k
115
1.01k
            let data = _mm_loadu_si128(buffer.as_ptr() as *const __m128i);
116
1.01k
            hash = _mm_xor_si128(hash, data);
117
1.01k
            hash = _mm_aesenc_si128(hash, seed);
118
1.01k
        
}0
119
120
        // Final mixing: incorporate key length
121
1.01k
        let len_vec = _mm_set1_epi32(key.len() as i32);
122
1.01k
        hash = _mm_xor_si128(hash, len_vec);
123
1.01k
        hash = _mm_aesenc_si128(hash, seed);
124
125
        // Extract and mix all 4 32-bit parts for better distribution
126
1.01k
        let a = _mm_extract_epi32(hash, 0) as u32;
127
1.01k
        let b = _mm_extract_epi32(hash, 1) as u32;
128
1.01k
        let c = _mm_extract_epi32(hash, 2) as u32;
129
1.01k
        let d = _mm_extract_epi32(hash, 3) as u32;
130
131
        // Final mixing similar to MurmurHash3 finalizer
132
1.01k
        let mut h = a ^ b ^ c ^ d;
133
1.01k
        h ^= h >> 16;
134
1.01k
        h = h.wrapping_mul(0x85ebca6b);
135
1.01k
        h ^= h >> 13;
136
1.01k
        h = h.wrapping_mul(0xc2b2ae35);
137
1.01k
        h ^= h >> 16;
138
139
1.01k
        h
140
1.01k
    }
141
}
142
143
pub struct MurmurHasher {
144
    state: u32,
145
}
146
147
impl Default for MurmurHasher {
148
1
    fn default() -> Self {
149
1
        Self::new()
150
1
    }
151
}
152
153
impl MurmurHasher {
154
7
    pub fn new() -> Self {
155
7
        Self { state: 0 }
156
7
    }
157
158
3
    pub fn with_seed(seed: u32) -> Self {
159
3
        Self { state: seed }
160
3
    }
161
}
162
163
impl Hasher for MurmurHasher {
164
12
    fn write(&mut self, bytes: &[u8]) {
165
12
        self.state = murmur3_32(bytes, self.state);
166
12
    }
167
168
10
    fn finish(&self) -> u64 {
169
10
        self.state as u64
170
10
    }
171
}