@@ -221,35 +221,76 @@ impl<S: Sip> Hasher<S> {
221
221
self . ntail = 0 ;
222
222
}
223
223
224
- // Specialized write function that is only valid for buffers with len <= 8.
225
- // It's used to force inlining of write_u8 and write_usize, those would normally be inlined
226
- // except for composite types (that includes slices and str hashing because of delimiter).
227
- // Without this extra push the compiler is very reluctant to inline delimiter writes,
228
- // degrading performance substantially for the most common use cases.
224
+ // A specialized write function for values with size <= 8.
225
+ //
226
+ // The hashing of multi-byte integers depends on endianness. E.g.:
227
+ // - little-endian: `write_u32(0xDDCCBBAA)` == `write([0xAA, 0xBB, 0xCC, 0xDD])`
228
+ // - big-endian: `write_u32(0xDDCCBBAA)` == `write([0xDD, 0xCC, 0xBB, 0xAA])`
229
+ //
230
+ // This function does the right thing for little-endian hardware. On
231
+ // big-endian hardware `x` must be byte-swapped first to give the right
232
+ // behaviour. After any byte-swapping, the input must be zero-extended to
233
+ // 64-bits. The caller is responsible for the byte-swapping and
234
+ // zero-extension.
229
235
#[ inline]
230
- fn short_write ( & mut self , msg : & [ u8 ] ) {
231
- debug_assert ! ( msg. len( ) <= 8 ) ;
232
- let length = msg. len ( ) ;
233
- self . length += length;
236
+ fn short_write < T > ( & mut self , _x : T , x : u64 ) {
237
+ let size = mem:: size_of :: < T > ( ) ;
238
+ self . length += size;
234
239
240
+ // The original number must be zero-extended, not sign-extended.
241
+ debug_assert ! ( if size < 8 { x >> ( 8 * size) == 0 } else { true } ) ;
242
+
243
+ // The number of bytes needed to fill `self.tail`.
235
244
let needed = 8 - self . ntail ;
236
- let fill = cmp:: min ( length, needed) ;
237
- if fill == 8 {
238
- self . tail = unsafe { load_int_le ! ( msg, 0 , u64 ) } ;
239
- } else {
240
- self . tail |= unsafe { u8to64_le ( msg, 0 , fill) } << ( 8 * self . ntail ) ;
241
- if length < needed {
242
- self . ntail += length;
243
- return ;
244
- }
245
+
246
+ // SipHash parses the input stream as 8-byte little-endian integers.
247
+ // Inputs are put into `self.tail` until 8 bytes of data have been
248
+ // collected, and then that word is processed.
249
+ //
250
+ // For example, imagine that `self.tail` is 0x0000_00EE_DDCC_BBAA,
251
+ // `self.ntail` is 5 (because 5 bytes have been put into `self.tail`),
252
+ // and `needed` is therefore 3.
253
+ //
254
+ // - Scenario 1, `self.write_u8(0xFF)`: we have already zero-extended
255
+ // the input to 0x0000_0000_0000_00FF. We now left-shift it five
256
+ // bytes, giving 0x0000_FF00_0000_0000. We then bitwise-OR that value
257
+ // into `self.tail`, resulting in 0x0000_FFEE_DDCC_BBAA.
258
+ // (Zero-extension of the original input is critical in this scenario
259
+ // because we don't want the high two bytes of `self.tail` to be
260
+ // touched by the bitwise-OR.) `self.tail` is not yet full, so we
261
+ // return early, after updating `self.ntail` to 6.
262
+ //
263
+ // - Scenario 2, `self.write_u32(0xIIHH_GGFF)`: we have already
264
+ // zero-extended the input to 0x0000_0000_IIHH_GGFF. We now
265
+ // left-shift it five bytes, giving 0xHHGG_FF00_0000_0000. We then
266
+ // bitwise-OR that value into `self.tail`, resulting in
267
+ // 0xHHGG_FFEE_DDCC_BBAA. `self.tail` is now full, and we can use it
268
+ // to update `self.state`. (As mentioned above, this assumes a
269
+ // little-endian machine; on a big-endian machine we would have
270
+ // byte-swapped 0xIIHH_GGFF in the caller, giving 0xFFGG_HHII, and we
271
+ // would then end up bitwise-ORing 0xGGHH_II00_0000_0000 into
272
+ // `self.tail`).
273
+ //
274
+ self . tail |= x << ( 8 * self . ntail ) ;
275
+ if size < needed {
276
+ self . ntail += size;
277
+ return ;
245
278
}
279
+
280
+ // `self.tail` is full, process it.
246
281
self . state . v3 ^= self . tail ;
247
282
S :: c_rounds ( & mut self . state ) ;
248
283
self . state . v0 ^= self . tail ;
249
284
250
- // Buffered tail is now flushed, process new input.
251
- self . ntail = length - needed;
252
- self . tail = unsafe { u8to64_le ( msg, needed, self . ntail ) } ;
285
+ // Continuing scenario 2: we have one byte left over from the input. We
286
+ // set `self.ntail` to 1 and `self.tail` to `0x0000_0000_IIHH_GGFF >>
287
+ // 8*3`, which is 0x0000_0000_0000_00II. (Or on a big-endian machine
288
+ // the prior byte-swapping would leave us with 0x0000_0000_0000_00FF.)
289
+ //
290
+ // The `if` is needed to avoid shifting by 64 bits, which Rust
291
+ // complains about.
292
+ self . ntail = size - needed;
293
+ self . tail = if needed < 8 { x >> ( 8 * needed) } else { 0 } ;
253
294
}
254
295
}
255
296
@@ -280,19 +321,14 @@ impl super::Hasher for SipHasher13 {
280
321
}
281
322
282
323
impl < S : Sip > super :: Hasher for Hasher < S > {
283
- // see short_write comment for explanation
284
324
#[ inline]
285
- fn write_usize ( & mut self , i : usize ) {
286
- let bytes = unsafe {
287
- crate :: slice:: from_raw_parts ( & i as * const usize as * const u8 , mem:: size_of :: < usize > ( ) )
288
- } ;
289
- self . short_write ( bytes) ;
325
+ fn write_u8 ( & mut self , i : u8 ) {
326
+ self . short_write ( i, i as u64 ) ;
290
327
}
291
328
292
- // see short_write comment for explanation
293
329
#[ inline]
294
- fn write_u8 ( & mut self , i : u8 ) {
295
- self . short_write ( & [ i ] ) ;
330
+ fn write_usize ( & mut self , i : usize ) {
331
+ self . short_write ( i , i . to_le ( ) as u64 ) ;
296
332
}
297
333
298
334
#[ inline]
0 commit comments