]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_data_structures/src/sip128.rs
SipHasher128: use specific struct layout
[rust.git] / compiler / rustc_data_structures / src / sip128.rs
1 //! This is a copy of `core::hash::sip` adapted to providing 128 bit hashes.
2
3 use std::hash::Hasher;
4 use std::mem::{self, MaybeUninit};
5 use std::ptr;
6
7 #[cfg(test)]
8 mod tests;
9
10 const ELEM_SIZE: usize = mem::size_of::<u64>();
11 const BUFFER_SIZE_ELEMS: usize = 8;
12 const BUFFER_SIZE_BYTES: usize = BUFFER_SIZE_ELEMS * ELEM_SIZE;
13 const BUFFER_SIZE_ELEMS_SPILL: usize = BUFFER_SIZE_ELEMS + 1;
14 const BUFFER_SIZE_BYTES_SPILL: usize = BUFFER_SIZE_ELEMS_SPILL * ELEM_SIZE;
15 const BUFFER_SPILL_INDEX: usize = BUFFER_SIZE_ELEMS_SPILL - 1;
16
17 #[derive(Debug, Clone)]
18 #[repr(C)]
19 pub struct SipHasher128 {
20     // The access pattern during hashing consists of accesses to `nbuf` and
21     // `buf` until the buffer is full, followed by accesses to `state` and
22     // `processed`, and then repetition of that pattern until hashing is done.
23     // This is the basis for the ordering of fields below. However, in practice
24     // the cache miss-rate for data access is extremely low regardless of order.
25     nbuf: usize,                                      // how many bytes in buf are valid
26     buf: [MaybeUninit<u64>; BUFFER_SIZE_ELEMS_SPILL], // unprocessed bytes le
27     state: State,                                     // hash State
28     processed: usize,                                 // how many bytes we've processed
29 }
30
31 #[derive(Debug, Clone, Copy)]
32 #[repr(C)]
33 struct State {
34     // v0, v2 and v1, v3 show up in pairs in the algorithm,
35     // and simd implementations of SipHash will use vectors
36     // of v02 and v13. By placing them in this order in the struct,
37     // the compiler can pick up on just a few simd optimizations by itself.
38     v0: u64,
39     v2: u64,
40     v1: u64,
41     v3: u64,
42 }
43
44 macro_rules! compress {
45     ($state:expr) => {{ compress!($state.v0, $state.v1, $state.v2, $state.v3) }};
46     ($v0:expr, $v1:expr, $v2:expr, $v3:expr) => {{
47         $v0 = $v0.wrapping_add($v1);
48         $v1 = $v1.rotate_left(13);
49         $v1 ^= $v0;
50         $v0 = $v0.rotate_left(32);
51         $v2 = $v2.wrapping_add($v3);
52         $v3 = $v3.rotate_left(16);
53         $v3 ^= $v2;
54         $v0 = $v0.wrapping_add($v3);
55         $v3 = $v3.rotate_left(21);
56         $v3 ^= $v0;
57         $v2 = $v2.wrapping_add($v1);
58         $v1 = $v1.rotate_left(17);
59         $v1 ^= $v2;
60         $v2 = $v2.rotate_left(32);
61     }};
62 }
63
64 // Copies up to 8 bytes from source to destination. This performs better than
65 // `ptr::copy_nonoverlapping` on microbenchmarks and may perform better on real
66 // workloads since all of the copies have fixed sizes and avoid calling memcpy.
67 #[inline]
68 unsafe fn copy_nonoverlapping_small(src: *const u8, dst: *mut u8, count: usize) {
69     const COUNT_MAX: usize = 8;
70     debug_assert!(count <= COUNT_MAX);
71
72     if count == COUNT_MAX {
73         ptr::copy_nonoverlapping(src, dst, COUNT_MAX);
74         return;
75     }
76
77     let mut i = 0;
78     if i + 3 < count {
79         ptr::copy_nonoverlapping(src.add(i), dst.add(i), 4);
80         i += 4;
81     }
82
83     if i + 1 < count {
84         ptr::copy_nonoverlapping(src.add(i), dst.add(i), 2);
85         i += 2
86     }
87
88     if i < count {
89         *dst.add(i) = *src.add(i);
90         i += 1;
91     }
92
93     debug_assert_eq!(i, count);
94 }
95
96 // # Implementation
97 //
98 // This implementation uses buffering to reduce the hashing cost for inputs
99 // consisting of many small integers. Buffering simplifies the integration of
100 // integer input--the integer write function typically just appends to the
101 // buffer with a statically sized write, updates metadata, and returns.
102 //
103 // Buffering also prevents alternating between writes that do and do not trigger
104 // the hashing process. Only when the entire buffer is full do we transition
105 // into hashing. This allows us to keep the hash state in registers for longer,
106 // instead of loading and storing it before and after processing each element.
107 //
108 // When a write fills the buffer, a buffer processing function is invoked to
109 // hash all of the buffered input. The buffer processing functions are marked
110 // `#[inline(never)]` so that they aren't inlined into the append functions,
111 // which ensures the more frequently called append functions remain inlineable
112 // and don't include register pushing/popping that would only be made necessary
113 // by inclusion of the complex buffer processing path which uses those
114 // registers.
115 //
116 // The buffer includes a "spill"--an extra element at the end--which simplifies
117 // the integer write buffer processing path. The value that fills the buffer can
118 // be written with a statically sized write that may spill over into the spill.
119 // After the buffer is processed, the part of the value that spilled over can
120 // written from the spill to the beginning of the buffer with another statically
121 // sized write. Due to static sizes, this scheme performs better than copying
122 // the exact number of bytes needed into the end and beginning of the buffer.
123 //
124 // The buffer is uninitialized, which improves performance, but may preclude
125 // efficient implementation of alternative approaches. The improvement is not so
126 // large that an alternative approach should be disregarded because it cannot be
127 // efficiently implemented with an uninitialized buffer. On the other hand, an
128 // uninitialized buffer may become more important should a larger one be used.
129 //
130 // # Platform Dependence
131 //
132 // The SipHash algorithm operates on byte sequences. It parses the input stream
133 // as 8-byte little-endian integers. Therefore, given the same byte sequence, it
134 // produces the same result on big- and little-endian hardware.
135 //
136 // However, the Hasher trait has methods which operate on multi-byte integers.
137 // How they are converted into byte sequences can be endian-dependent (by using
138 // native byte order) or independent (by consistently using either LE or BE byte
139 // order). It can also be `isize` and `usize` size dependent (by using the
140 // native size), or independent (by converting to a common size), supposing the
141 // values can be represented in 32 bits.
142 //
143 // In order to make `SipHasher128` consistent with `SipHasher` in libstd, we
144 // choose to do the integer to byte sequence conversion in the platform-
145 // dependent way. Clients can achieve (nearly) platform-independent hashing by
146 // widening `isize` and `usize` integers to 64 bits on 32-bit systems and
147 // byte-swapping integers on big-endian systems before passing them to the
148 // writing functions. This causes the input byte sequence to look identical on
149 // big- and little- endian systems (supposing `isize` and `usize` values can be
150 // represented in 32 bits), which ensures platform-independent results.
151 impl SipHasher128 {
152     #[inline]
153     pub fn new_with_keys(key0: u64, key1: u64) -> SipHasher128 {
154         let mut hasher = SipHasher128 {
155             nbuf: 0,
156             buf: MaybeUninit::uninit_array(),
157             state: State {
158                 v0: key0 ^ 0x736f6d6570736575,
159                 // The XOR with 0xee is only done on 128-bit algorithm version.
160                 v1: key1 ^ (0x646f72616e646f6d ^ 0xee),
161                 v2: key0 ^ 0x6c7967656e657261,
162                 v3: key1 ^ 0x7465646279746573,
163             },
164             processed: 0,
165         };
166
167         unsafe {
168             // Initialize spill because we read from it in `short_write_process_buffer`.
169             *hasher.buf.get_unchecked_mut(BUFFER_SPILL_INDEX) = MaybeUninit::zeroed();
170         }
171
172         hasher
173     }
174
175     // A specialized write function for values with size <= 8.
176     #[inline]
177     fn short_write<T>(&mut self, x: T) {
178         let size = mem::size_of::<T>();
179         let nbuf = self.nbuf;
180         debug_assert!(size <= 8);
181         debug_assert!(nbuf < BUFFER_SIZE_BYTES);
182         debug_assert!(nbuf + size < BUFFER_SIZE_BYTES_SPILL);
183
184         if nbuf + size < BUFFER_SIZE_BYTES {
185             unsafe {
186                 // The memcpy call is optimized away because the size is known.
187                 let dst = (self.buf.as_mut_ptr() as *mut u8).add(nbuf);
188                 ptr::copy_nonoverlapping(&x as *const _ as *const u8, dst, size);
189             }
190
191             self.nbuf = nbuf + size;
192
193             return;
194         }
195
196         unsafe { self.short_write_process_buffer(x) }
197     }
198
199     // A specialized write function for values with size <= 8 that should only
200     // be called when the write would cause the buffer to fill.
201     //
202     // SAFETY: the write of `x` into `self.buf` starting at byte offset
203     // `self.nbuf` must cause `self.buf` to become fully initialized (and not
204     // overflow) if it wasn't already.
205     #[inline(never)]
206     unsafe fn short_write_process_buffer<T>(&mut self, x: T) {
207         let size = mem::size_of::<T>();
208         let nbuf = self.nbuf;
209         debug_assert!(size <= 8);
210         debug_assert!(nbuf < BUFFER_SIZE_BYTES);
211         debug_assert!(nbuf + size >= BUFFER_SIZE_BYTES);
212         debug_assert!(nbuf + size < BUFFER_SIZE_BYTES_SPILL);
213
214         // Copy first part of input into end of buffer, possibly into spill
215         // element. The memcpy call is optimized away because the size is known.
216         let dst = (self.buf.as_mut_ptr() as *mut u8).add(nbuf);
217         ptr::copy_nonoverlapping(&x as *const _ as *const u8, dst, size);
218
219         // Process buffer.
220         for i in 0..BUFFER_SIZE_ELEMS {
221             let elem = self.buf.get_unchecked(i).assume_init().to_le();
222             self.state.v3 ^= elem;
223             Sip24Rounds::c_rounds(&mut self.state);
224             self.state.v0 ^= elem;
225         }
226
227         // Copy remaining input into start of buffer by copying size - 1
228         // elements from spill (at most size - 1 bytes could have overflowed
229         // into the spill). The memcpy call is optimized away because the size
230         // is known. And the whole copy is optimized away for size == 1.
231         let src = self.buf.get_unchecked(BUFFER_SPILL_INDEX) as *const _ as *const u8;
232         ptr::copy_nonoverlapping(src, self.buf.as_mut_ptr() as *mut u8, size - 1);
233
234         // This function should only be called when the write fills the buffer.
235         // Therefore, when size == 1, the new `self.nbuf` must be zero. The size
236         // is statically known, so the branch is optimized away.
237         self.nbuf = if size == 1 { 0 } else { nbuf + size - BUFFER_SIZE_BYTES };
238         self.processed += BUFFER_SIZE_BYTES;
239     }
240
241     // A write function for byte slices.
242     #[inline]
243     fn slice_write(&mut self, msg: &[u8]) {
244         let length = msg.len();
245         let nbuf = self.nbuf;
246         debug_assert!(nbuf < BUFFER_SIZE_BYTES);
247
248         if nbuf + length < BUFFER_SIZE_BYTES {
249             unsafe {
250                 let dst = (self.buf.as_mut_ptr() as *mut u8).add(nbuf);
251
252                 if length <= 8 {
253                     copy_nonoverlapping_small(msg.as_ptr(), dst, length);
254                 } else {
255                     // This memcpy is *not* optimized away.
256                     ptr::copy_nonoverlapping(msg.as_ptr(), dst, length);
257                 }
258             }
259
260             self.nbuf = nbuf + length;
261
262             return;
263         }
264
265         unsafe { self.slice_write_process_buffer(msg) }
266     }
267
268     // A write function for byte slices that should only be called when the
269     // write would cause the buffer to fill.
270     //
271     // SAFETY: `self.buf` must be initialized up to the byte offset `self.nbuf`,
272     // and `msg` must contain enough bytes to initialize the rest of the element
273     // containing the byte offset `self.nbuf`.
274     #[inline(never)]
275     unsafe fn slice_write_process_buffer(&mut self, msg: &[u8]) {
276         let length = msg.len();
277         let nbuf = self.nbuf;
278         debug_assert!(nbuf < BUFFER_SIZE_BYTES);
279         debug_assert!(nbuf + length >= BUFFER_SIZE_BYTES);
280
281         // Always copy first part of input into current element of buffer.
282         // This function should only be called when the write fills the buffer,
283         // so we know that there is enough input to fill the current element.
284         let valid_in_elem = nbuf % ELEM_SIZE;
285         let needed_in_elem = ELEM_SIZE - valid_in_elem;
286
287         let src = msg.as_ptr();
288         let dst = (self.buf.as_mut_ptr() as *mut u8).add(nbuf);
289         copy_nonoverlapping_small(src, dst, needed_in_elem);
290
291         // Process buffer.
292
293         // Using `nbuf / ELEM_SIZE + 1` rather than `(nbuf + needed_in_elem) /
294         // ELEM_SIZE` to show the compiler that this loop's upper bound is > 0.
295         // We know that is true, because last step ensured we have a full
296         // element in the buffer.
297         let last = nbuf / ELEM_SIZE + 1;
298
299         for i in 0..last {
300             let elem = self.buf.get_unchecked(i).assume_init().to_le();
301             self.state.v3 ^= elem;
302             Sip24Rounds::c_rounds(&mut self.state);
303             self.state.v0 ^= elem;
304         }
305
306         // Process the remaining element-sized chunks of input.
307         let mut processed = needed_in_elem;
308         let input_left = length - processed;
309         let elems_left = input_left / ELEM_SIZE;
310         let extra_bytes_left = input_left % ELEM_SIZE;
311
312         for _ in 0..elems_left {
313             let elem = (msg.as_ptr().add(processed) as *const u64).read_unaligned().to_le();
314             self.state.v3 ^= elem;
315             Sip24Rounds::c_rounds(&mut self.state);
316             self.state.v0 ^= elem;
317             processed += ELEM_SIZE;
318         }
319
320         // Copy remaining input into start of buffer.
321         let src = msg.as_ptr().add(processed);
322         let dst = self.buf.as_mut_ptr() as *mut u8;
323         copy_nonoverlapping_small(src, dst, extra_bytes_left);
324
325         self.nbuf = extra_bytes_left;
326         self.processed += nbuf + processed;
327     }
328
329     #[inline]
330     pub fn finish128(mut self) -> (u64, u64) {
331         debug_assert!(self.nbuf < BUFFER_SIZE_BYTES);
332
333         // Process full elements in buffer.
334         let last = self.nbuf / ELEM_SIZE;
335
336         // Since we're consuming self, avoid updating members for a potential
337         // performance gain.
338         let mut state = self.state;
339
340         for i in 0..last {
341             let elem = unsafe { self.buf.get_unchecked(i).assume_init().to_le() };
342             state.v3 ^= elem;
343             Sip24Rounds::c_rounds(&mut state);
344             state.v0 ^= elem;
345         }
346
347         // Get remaining partial element.
348         let elem = if self.nbuf % ELEM_SIZE != 0 {
349             unsafe {
350                 // Ensure element is initialized by writing zero bytes. At most
351                 // `ELEM_SIZE - 1` are required given the above check. It's safe
352                 // to write this many because we have the spill and we maintain
353                 // `self.nbuf` such that this write will start before the spill.
354                 let dst = (self.buf.as_mut_ptr() as *mut u8).add(self.nbuf);
355                 ptr::write_bytes(dst, 0, ELEM_SIZE - 1);
356                 self.buf.get_unchecked(last).assume_init().to_le()
357             }
358         } else {
359             0
360         };
361
362         // Finalize the hash.
363         let length = self.processed + self.nbuf;
364         let b: u64 = ((length as u64 & 0xff) << 56) | elem;
365
366         state.v3 ^= b;
367         Sip24Rounds::c_rounds(&mut state);
368         state.v0 ^= b;
369
370         state.v2 ^= 0xee;
371         Sip24Rounds::d_rounds(&mut state);
372         let _0 = state.v0 ^ state.v1 ^ state.v2 ^ state.v3;
373
374         state.v1 ^= 0xdd;
375         Sip24Rounds::d_rounds(&mut state);
376         let _1 = state.v0 ^ state.v1 ^ state.v2 ^ state.v3;
377
378         (_0, _1)
379     }
380 }
381
382 impl Hasher for SipHasher128 {
383     #[inline]
384     fn write_u8(&mut self, i: u8) {
385         self.short_write(i);
386     }
387
388     #[inline]
389     fn write_u16(&mut self, i: u16) {
390         self.short_write(i);
391     }
392
393     #[inline]
394     fn write_u32(&mut self, i: u32) {
395         self.short_write(i);
396     }
397
398     #[inline]
399     fn write_u64(&mut self, i: u64) {
400         self.short_write(i);
401     }
402
403     #[inline]
404     fn write_usize(&mut self, i: usize) {
405         self.short_write(i);
406     }
407
408     #[inline]
409     fn write_i8(&mut self, i: i8) {
410         self.short_write(i as u8);
411     }
412
413     #[inline]
414     fn write_i16(&mut self, i: i16) {
415         self.short_write(i as u16);
416     }
417
418     #[inline]
419     fn write_i32(&mut self, i: i32) {
420         self.short_write(i as u32);
421     }
422
423     #[inline]
424     fn write_i64(&mut self, i: i64) {
425         self.short_write(i as u64);
426     }
427
428     #[inline]
429     fn write_isize(&mut self, i: isize) {
430         self.short_write(i as usize);
431     }
432
433     #[inline]
434     fn write(&mut self, msg: &[u8]) {
435         self.slice_write(msg);
436     }
437
438     fn finish(&self) -> u64 {
439         panic!("SipHasher128 cannot provide valid 64 bit hashes")
440     }
441 }
442
443 #[derive(Debug, Clone, Default)]
444 struct Sip24Rounds;
445
446 impl Sip24Rounds {
447     #[inline]
448     fn c_rounds(state: &mut State) {
449         compress!(state);
450         compress!(state);
451     }
452
453     #[inline]
454     fn d_rounds(state: &mut State) {
455         compress!(state);
456         compress!(state);
457         compress!(state);
458         compress!(state);
459     }
460 }