@@ -137,7 +137,35 @@ impl Hasher for StableHasher {
137
137
// platforms. This is important for symbol hashes when cross compiling,
138
138
// for example. Sign extending here is preferable as it means that the
139
139
// same negative number hashes the same on both 32 and 64 bit platforms.
140
- self . state . write_i64 ( ( i as i64 ) . to_le ( ) ) ;
140
+ let value = ( i as i64 ) . to_le ( ) as u64 ;
141
+
142
+ // Cold path
143
+ #[ cold]
144
+ #[ inline( never) ]
145
+ fn hash_value ( state : & mut SipHasher128 , value : u64 ) {
146
+ state. write_u8 ( 0xFF ) ;
147
+ state. write_u64 ( value) ;
148
+ }
149
+
150
+ // `isize` values often seem to have a small (positive) numeric value in practice.
151
+ // To exploit this, if the value is small, we will hash a smaller amount of bytes.
152
+ // However, we cannot just skip the leading zero bytes, as that would produce the same hash
153
+ // e.g. if you hash two values that have the same bit pattern when they are swapped.
154
+ // See https://github.com/rust-lang/rust/pull/93014 for context.
155
+ //
156
+ // Therefore, we employ the following strategy:
157
+ // 1) When we encounter a value that fits within a single byte (the most common case), we
158
+ // hash just that byte. This is the most common case that is being optimized. However, we do
159
+ // not do this for the value 0xFF, as that is a reserved prefix (a bit like in UTF-8).
160
+ // 2) When we encounter a larger value, we hash a "marker" 0xFF and then the corresponding
161
+ // 8 bytes. Since this prefix cannot occur when we hash a single byte, when we hash two
162
+ // `isize`s that fit within a different amount of bytes, they should always produce a different
163
+ // byte stream for the hasher.
164
+ if value < 0xFF {
165
+ self . state . write_u8 ( value as u8 ) ;
166
+ } else {
167
+ hash_value ( & mut self . state , value) ;
168
+ }
141
169
}
142
170
}
143
171
0 commit comments