@@ -4,7 +4,6 @@ use std::cmp;
4
4
use std:: hash:: Hasher ;
5
5
use std:: mem;
6
6
use std:: ptr;
7
- use std:: slice;
8
7
9
8
#[ cfg( test) ]
10
9
mod tests;
@@ -52,46 +51,17 @@ macro_rules! compress {
52
51
} } ;
53
52
}
54
53
55
- /// Loads an integer of the desired type from a byte stream, in LE order. Uses
56
- /// `copy_nonoverlapping` to let the compiler generate the most efficient way
57
- /// to load it from a possibly unaligned address.
58
- ///
59
- /// Unsafe because: unchecked indexing at i..i+size_of(int_ty)
60
- macro_rules! load_int_le {
61
- ( $buf: expr, $i: expr, $int_ty: ident) => { {
62
- debug_assert!( $i + mem:: size_of:: <$int_ty>( ) <= $buf. len( ) ) ;
63
- let mut data = 0 as $int_ty;
64
- ptr:: copy_nonoverlapping(
65
- $buf. get_unchecked( $i) ,
66
- & mut data as * mut _ as * mut u8 ,
67
- mem:: size_of:: <$int_ty>( ) ,
68
- ) ;
69
- data. to_le( )
70
- } } ;
71
- }
72
-
73
- /// Loads an u64 using up to 7 bytes of a byte slice.
74
- ///
75
- /// Unsafe because: unchecked indexing at start..start+len
54
+ /// Loads up to 8 bytes from a byte-slice into a little-endian u64.
76
55
#[ inline]
77
- unsafe fn u8to64_le ( buf : & [ u8 ] , start : usize , len : usize ) -> u64 {
78
- debug_assert ! ( len < 8 ) ;
79
- let mut i = 0 ; // current byte index (from LSB) in the output u64
80
- let mut out = 0 ;
81
- if i + 3 < len {
82
- out = u64:: from ( load_int_le ! ( buf, start + i, u32 ) ) ;
83
- i += 4 ;
84
- }
85
- if i + 1 < len {
86
- out |= u64:: from ( load_int_le ! ( buf, start + i, u16 ) ) << ( i * 8 ) ;
87
- i += 2
88
- }
89
- if i < len {
90
- out |= u64:: from ( * buf. get_unchecked ( start + i) ) << ( i * 8 ) ;
91
- i += 1 ;
56
+ fn u8to64_le ( buf : & [ u8 ] , start : usize , len : usize ) -> u64 {
57
+ assert ! ( len <= 8 && start + len <= buf. len( ) ) ;
58
+
59
+ let mut out = 0u64 ;
60
+ unsafe {
61
+ let out_ptr = & mut out as * mut _ as * mut u8 ;
62
+ ptr:: copy_nonoverlapping ( buf. as_ptr ( ) . offset ( start as isize ) , out_ptr, len) ;
92
63
}
93
- debug_assert_eq ! ( i, len) ;
94
- out
64
+ out. to_le ( )
95
65
}
96
66
97
67
impl SipHasher128 {
@@ -122,42 +92,76 @@ impl SipHasher128 {
122
92
self . state . v1 ^= 0xee ;
123
93
}
124
94
125
- // Specialized write function that is only valid for buffers with len <= 8.
126
- // It's used to force inlining of write_u8 and write_usize, those would normally be inlined
127
- // except for composite types (that includes slices and str hashing because of delimiter).
128
- // Without this extra push the compiler is very reluctant to inline delimiter writes,
129
- // degrading performance substantially for the most common use cases.
95
+ // A specialized write function for values with size <= 8.
96
+ //
97
+ // The hashing of multi-byte integers depends on endianness. E.g.:
98
+ // - little-endian: `write_u32(0xDDCCBBAA)` == `write([0xAA, 0xBB, 0xCC, 0xDD])`
99
+ // - big-endian: `write_u32(0xDDCCBBAA)` == `write([0xDD, 0xCC, 0xBB, 0xAA])`
100
+ //
101
+ // This function does the right thing for little-endian hardware. On
102
+ // big-endian hardware `x` must be byte-swapped first to give the right
103
+ // behaviour. After any byte-swapping, the input must be zero-extended to
104
+ // 64-bits. The caller is responsible for the byte-swapping and
105
+ // zero-extension.
130
106
#[ inline]
131
- fn short_write ( & mut self , msg : & [ u8 ] ) {
132
- debug_assert ! ( msg. len( ) <= 8 ) ;
133
- let length = msg. len ( ) ;
134
- self . length += length;
107
+ fn short_write < T > ( & mut self , _x : T , x : u64 ) {
108
+ let size = mem:: size_of :: < T > ( ) ;
109
+ self . length += size;
110
+
111
+ // The original number must be zero-extended, not sign-extended.
112
+ debug_assert ! ( if size < 8 { x >> ( 8 * size) == 0 } else { true } ) ;
135
113
114
+ // The number of bytes needed to fill `self.tail`.
136
115
let needed = 8 - self . ntail ;
137
- let fill = cmp:: min ( length, needed) ;
138
- if fill == 8 {
139
- self . tail = unsafe { load_int_le ! ( msg, 0 , u64 ) } ;
140
- } else {
141
- self . tail |= unsafe { u8to64_le ( msg, 0 , fill) } << ( 8 * self . ntail ) ;
142
- if length < needed {
143
- self . ntail += length;
144
- return ;
145
- }
116
+
117
+ // SipHash parses the input stream as 8-byte little-endian integers.
118
+ // Inputs are put into `self.tail` until 8 bytes of data have been
119
+ // collected, and then that word is processed.
120
+ //
121
+ // For example, imagine that `self.tail` is 0x0000_00EE_DDCC_BBAA,
122
+ // `self.ntail` is 5 (because 5 bytes have been put into `self.tail`),
123
+ // and `needed` is therefore 3.
124
+ //
125
+ // - Scenario 1, `self.write_u8(0xFF)`: we have already zero-extended
126
+ // the input to 0x0000_0000_0000_00FF. We now left-shift it five
127
+ // bytes, giving 0x0000_FF00_0000_0000. We then bitwise-OR that value
128
+ // into `self.tail`, resulting in 0x0000_FFEE_DDCC_BBAA.
129
+ // (Zero-extension of the original input is critical in this scenario
130
+ // because we don't want the high two bytes of `self.tail` to be
131
+ // touched by the bitwise-OR.) `self.tail` is not yet full, so we
132
+ // return early, after updating `self.ntail` to 6.
133
+ //
134
+ // - Scenario 2, `self.write_u32(0xIIHH_GGFF)`: we have already
135
+ // zero-extended the input to 0x0000_0000_IIHH_GGFF. We now
136
+ // left-shift it five bytes, giving 0xHHGG_FF00_0000_0000. We then
137
+ // bitwise-OR that value into `self.tail`, resulting in
138
+ // 0xHHGG_FFEE_DDCC_BBAA. `self.tail` is now full, and we can use it
139
+ // to update `self.state`. (As mentioned above, this assumes a
140
+ // little-endian machine; on a big-endian machine we would have
141
+ // byte-swapped 0xIIHH_GGFF in the caller, giving 0xFFGG_HHII, and we
142
+ // would then end up bitwise-ORing 0xGGHH_II00_0000_0000 into
143
+ // `self.tail`).
144
+ //
145
+ self . tail |= x << ( 8 * self . ntail ) ;
146
+ if size < needed {
147
+ self . ntail += size;
148
+ return ;
146
149
}
150
+
151
+ // `self.tail` is full, process it.
147
152
self . state . v3 ^= self . tail ;
148
153
Sip24Rounds :: c_rounds ( & mut self . state ) ;
149
154
self . state . v0 ^= self . tail ;
150
155
151
- // Buffered tail is now flushed, process new input.
152
- self . ntail = length - needed;
153
- self . tail = unsafe { u8to64_le ( msg, needed, self . ntail ) } ;
154
- }
155
-
156
- #[ inline( always) ]
157
- fn short_write_gen < T > ( & mut self , x : T ) {
158
- let bytes =
159
- unsafe { slice:: from_raw_parts ( & x as * const T as * const u8 , mem:: size_of :: < T > ( ) ) } ;
160
- self . short_write ( bytes) ;
156
+ // Continuing scenario 2: we have one byte left over from the input. We
157
+ // set `self.ntail` to 1 and `self.tail` to `0x0000_0000_IIHH_GGFF >>
158
+ // 8*3`, which is 0x0000_0000_0000_00II. (Or on a big-endian machine
159
+ // the prior byte-swapping would leave us with 0x0000_0000_0000_00FF.)
160
+ //
161
+ // The `if` is needed to avoid shifting by 64 bits, which Rust
162
+ // complains about.
163
+ self . ntail = size - needed;
164
+ self . tail = if needed < 8 { x >> ( 8 * needed) } else { 0 } ;
161
165
}
162
166
163
167
#[ inline]
@@ -182,52 +186,52 @@ impl SipHasher128 {
182
186
impl Hasher for SipHasher128 {
183
187
#[ inline]
184
188
fn write_u8 ( & mut self , i : u8 ) {
185
- self . short_write_gen ( i ) ;
189
+ self . short_write ( i , i as u64 ) ;
186
190
}
187
191
188
192
#[ inline]
189
193
fn write_u16 ( & mut self , i : u16 ) {
190
- self . short_write_gen ( i ) ;
194
+ self . short_write ( i , i . to_le ( ) as u64 ) ;
191
195
}
192
196
193
197
#[ inline]
194
198
fn write_u32 ( & mut self , i : u32 ) {
195
- self . short_write_gen ( i ) ;
199
+ self . short_write ( i , i . to_le ( ) as u64 ) ;
196
200
}
197
201
198
202
#[ inline]
199
203
fn write_u64 ( & mut self , i : u64 ) {
200
- self . short_write_gen ( i ) ;
204
+ self . short_write ( i , i . to_le ( ) as u64 ) ;
201
205
}
202
206
203
207
#[ inline]
204
208
fn write_usize ( & mut self , i : usize ) {
205
- self . short_write_gen ( i ) ;
209
+ self . short_write ( i , i . to_le ( ) as u64 ) ;
206
210
}
207
211
208
212
#[ inline]
209
213
fn write_i8 ( & mut self , i : i8 ) {
210
- self . short_write_gen ( i ) ;
214
+ self . short_write ( i , i as u8 as u64 ) ;
211
215
}
212
216
213
217
#[ inline]
214
218
fn write_i16 ( & mut self , i : i16 ) {
215
- self . short_write_gen ( i ) ;
219
+ self . short_write ( i , ( i as u16 ) . to_le ( ) as u64 ) ;
216
220
}
217
221
218
222
#[ inline]
219
223
fn write_i32 ( & mut self , i : i32 ) {
220
- self . short_write_gen ( i ) ;
224
+ self . short_write ( i , ( i as u32 ) . to_le ( ) as u64 ) ;
221
225
}
222
226
223
227
#[ inline]
224
228
fn write_i64 ( & mut self , i : i64 ) {
225
- self . short_write_gen ( i ) ;
229
+ self . short_write ( i , ( i as u64 ) . to_le ( ) as u64 ) ;
226
230
}
227
231
228
232
#[ inline]
229
233
fn write_isize ( & mut self , i : isize ) {
230
- self . short_write_gen ( i ) ;
234
+ self . short_write ( i , ( i as usize ) . to_le ( ) as u64 ) ;
231
235
}
232
236
233
237
#[ inline]
@@ -239,7 +243,7 @@ impl Hasher for SipHasher128 {
239
243
240
244
if self . ntail != 0 {
241
245
needed = 8 - self . ntail ;
242
- self . tail |= unsafe { u8to64_le ( msg, 0 , cmp:: min ( length, needed) ) } << ( 8 * self . ntail ) ;
246
+ self . tail |= u8to64_le ( msg, 0 , cmp:: min ( length, needed) ) << ( 8 * self . ntail ) ;
243
247
if length < needed {
244
248
self . ntail += length;
245
249
return ;
@@ -257,7 +261,7 @@ impl Hasher for SipHasher128 {
257
261
258
262
let mut i = needed;
259
263
while i < len - left {
260
- let mi = unsafe { load_int_le ! ( msg, i, u64 ) } ;
264
+ let mi = u8to64_le ( msg, i, 8 ) ;
261
265
262
266
self . state . v3 ^= mi;
263
267
Sip24Rounds :: c_rounds ( & mut self . state ) ;
@@ -266,7 +270,7 @@ impl Hasher for SipHasher128 {
266
270
i += 8 ;
267
271
}
268
272
269
- self . tail = unsafe { u8to64_le ( msg, i, left) } ;
273
+ self . tail = u8to64_le ( msg, i, left) ;
270
274
self . ntail = left;
271
275
}
272
276
0 commit comments