1pub use crate::serialize::{build_perfect_hash_index, build_perfect_hash_index_with_positions};
4
5use crate::hash::hash128;
6use crate::utils::{CorruptionKind, ReadError};
7
8#[derive(Clone, Copy, Debug)]
9pub struct PerfectHashView<'a> {
11 data: &'a [u8],
12 bitmap: &'a [u8],
13 table: &'a [u8],
14 section: usize,
15 section_magic: u64,
16 len: usize,
17 table_width: usize,
18}
19
20impl<'a> PerfectHashView<'a> {
21 #[inline(always)]
23 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
24 let header = read_u32_le(data).ok_or(ReadError::new(CorruptionKind::Truncated))?;
25 let len = (header & 0x0fff_ffff) as usize;
26 if len <= 1 {
27 let data = data
28 .get(..4)
29 .ok_or(ReadError::new(CorruptionKind::Truncated))?;
30 return Ok(Self {
31 data,
32 bitmap: &data[..0],
33 table: &data[..0],
34 section: 0,
35 section_magic: 0,
36 len,
37 table_width: 0,
38 });
39 }
40
41 let (section, bitmap_size, table_width, bytes) = perfect_hash_layout(len);
42 let data = data
43 .get(..bytes)
44 .ok_or(ReadError::new(CorruptionKind::Truncated))?;
45 let bitmap = data
46 .get(8..8 + bitmap_size)
47 .ok_or(ReadError::new(CorruptionKind::Truncated))?;
48 let table = data
49 .get(8 + bitmap_size..)
50 .ok_or(ReadError::new(CorruptionKind::Truncated))?;
51
52 Ok(Self {
53 data,
54 bitmap,
55 table,
56 section,
57 section_magic: fast_mod_magic(section as u32),
58 len,
59 table_width,
60 })
61 }
62
63 #[inline(always)]
65 pub fn data_size(&self) -> usize {
66 self.data.len()
67 }
68
69 #[inline(always)]
71 pub fn len(&self) -> usize {
72 self.len
73 }
74
75 #[inline(always)]
76 pub fn is_empty(&self) -> bool {
77 self.len == 0
78 }
79
80 #[inline(always)]
85 pub fn locate(&self, key: &[u8]) -> Option<usize> {
86 if self.len == 0 {
87 return None;
88 }
89 if self.len == 1 {
90 return Some(0);
91 }
92
93 let seed = read_u32_le(self.data.get(4..8)?)? as u64;
94 let code = hash128(key, seed);
95 let slots = [
96 fast_mod_u32(code[0], self.section as u32, self.section_magic) as usize,
97 fast_mod_u32(code[1], self.section as u32, self.section_magic) as usize + self.section,
98 fast_mod_u32(code[2], self.section as u32, self.section_magic) as usize
99 + self.section * 2,
100 ];
101 self.locate_slots(slots)
102 }
103
104 #[inline(always)]
105 fn locate_slots(&self, slots: [usize; 3]) -> Option<usize> {
106 let m = bit2(self.bitmap, slots[0])?
107 + bit2(self.bitmap, slots[1])?
108 + bit2(self.bitmap, slots[2])?;
109 let slot = slots[(m % 3) as usize];
110 let a = slot >> 5;
111 let b = slot & 31;
112
113 let off = read_offset(self.table, self.table_width, a)?;
114
115 let block = read_u64_le(self.bitmap.get(a * 8..a * 8 + 8)?)? | (u64::MAX << (b << 1));
116 Some(off + count_valid_slot(block))
117 }
118}
119
120#[inline(always)]
121fn perfect_hash_layout(len: usize) -> (usize, usize, usize, usize) {
122 let section = ((len * 105).saturating_add(255) / 256).max(10);
123 let bitmap_size = ((section * 3 + 31) & !31) / 4;
124 let table_width = if len > u16::MAX as usize {
125 4
126 } else if len > u8::MAX as usize {
127 2
128 } else if len > 24 {
129 1
130 } else {
131 0
132 };
133 let bytes = 8 + bitmap_size + bitmap_size / 8 * table_width;
134 (section, bitmap_size, table_width, bytes)
135}
136
137#[inline(always)]
138fn read_offset(table: &[u8], table_width: usize, index: usize) -> Option<usize> {
139 match table_width {
140 4 => {
141 let start = index * 4;
142 Some(read_u32_le(table.get(start..start + 4)?)? as usize)
143 }
144 2 => {
145 let start = index * 2;
146 Some(u16::from_le_bytes(table.get(start..start + 2)?.try_into().ok()?) as usize)
147 }
148 1 => Some(*table.get(index)? as usize),
149 0 => Some(0),
150 _ => None,
151 }
152}
153
154#[inline(always)]
155fn read_u32_le(bytes: &[u8]) -> Option<u32> {
156 Some(u32::from_le_bytes(bytes.get(..4)?.try_into().ok()?))
157}
158
159#[inline(always)]
160fn read_u64_le(bytes: &[u8]) -> Option<u64> {
161 Some(u64::from_le_bytes(bytes.get(..8)?.try_into().ok()?))
162}
163
164#[inline(always)]
165fn bit2(vec: &[u8], pos: usize) -> Option<u32> {
166 Some(((vec.get(pos >> 2)? >> ((pos & 3) << 1)) & 3) as u32)
167}
168
169#[inline(always)]
170fn fast_mod_magic(divisor: u32) -> u64 {
171 u64::MAX / divisor as u64 + 1
172}
173
174#[inline(always)]
175fn fast_mod_u32(value: u32, divisor: u32, magic: u64) -> u32 {
176 let low = magic.wrapping_mul(value as u64);
177 (((low as u128) * divisor as u128) >> 64) as u32
178}
179
180#[inline(always)]
181fn count_valid_slot(v: u64) -> usize {
182 let invalid = ((v & 0x5555_5555_5555_5555) & (v >> 1)).count_ones() as usize;
183 32 - invalid
184}
185
186#[cfg(test)]
187mod tests {
188 use std::collections::HashSet;
189
190 use super::{PerfectHashView, build_perfect_hash_index, fast_mod_magic, fast_mod_u32};
191
192 fn run_case(size: usize) {
193 let keys = (0..size).map(|i| i.to_string()).collect::<Vec<_>>();
194 let index = build_perfect_hash_index(&keys).expect("perfect hash index should build");
195 let view = PerfectHashView::new(&index).expect("perfect hash view should decode");
196
197 assert_eq!(view.len(), keys.len());
198 assert_eq!(view.data_size(), index.len());
199
200 let mut seen = HashSet::new();
201 for key in &keys {
202 let pos = view
203 .locate(key.as_bytes())
204 .expect("every inserted key should resolve");
205 assert!(pos < keys.len());
206 assert!(seen.insert(pos), "duplicate slot {pos} for key {key}");
207 }
208 }
209
210 #[test]
211 fn tiny_cases_match_cpp_coverage() {
212 for size in [0usize, 1, 2, 24] {
213 run_case(size);
214 }
215 }
216
217 #[test]
218 fn small_cases_match_cpp_coverage() {
219 for size in [200usize, 255, 1000] {
220 run_case(size);
221 }
222 }
223
224 #[test]
225 fn big_cases_match_cpp_coverage() {
226 for size in [65_535usize, 100_000] {
227 run_case(size);
228 }
229 }
230
231 #[test]
232 fn rejects_truncated_indices() {
233 assert!(PerfectHashView::new(&[]).is_err());
234 assert!(PerfectHashView::new(&[0, 0, 0]).is_err());
235
236 let index = build_perfect_hash_index(&["a", "b", "c"]).unwrap();
237 for len in 0..index.len() {
238 assert!(PerfectHashView::new(&index[..len]).is_err());
239 }
240 }
241
242 #[test]
243 fn fast_mod_matches_remainder() {
244 let divisors = [10u32, 11, 17, 31, 45, 255, 256, 1000, 65535, 100_000];
245 let values = [
246 0u32,
247 1,
248 2,
249 3,
250 7,
251 31,
252 32,
253 255,
254 256,
255 1024,
256 65_535,
257 1_000_000,
258 u32::MAX - 1,
259 u32::MAX,
260 ];
261
262 for divisor in divisors {
263 let magic = fast_mod_magic(divisor);
264 for value in values {
265 assert_eq!(fast_mod_u32(value, divisor, magic), value % divisor);
266 }
267 }
268 }
269}