| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.Collections.Generic; |
| | 6 | | using System.Runtime.CompilerServices; |
| | 7 | |
|
| | 8 | | namespace Azure.Core |
| | 9 | | { |
| | 10 | | /// <summary> |
| | 11 | | /// Copied from https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/HashCode.cs. |
| | 12 | | /// </summary> |
| | 13 | | internal struct HashCodeBuilder |
| | 14 | | { |
| 2 | 15 | | private static readonly uint s_seed = GenerateGlobalSeed(); |
| | 16 | |
|
| | 17 | | private const uint Prime1 = 2654435761U; |
| | 18 | | private const uint Prime2 = 2246822519U; |
| | 19 | | private const uint Prime3 = 3266489917U; |
| | 20 | | private const uint Prime4 = 668265263U; |
| | 21 | | private const uint Prime5 = 374761393U; |
| | 22 | |
|
| | 23 | | private uint _v1, _v2, _v3, _v4; |
| | 24 | | private uint _queue1, _queue2, _queue3; |
| | 25 | | private uint _length; |
| | 26 | |
|
| | 27 | | private static uint GenerateGlobalSeed() |
| | 28 | | { |
| 2 | 29 | | return (uint)new Random().Next(); |
| | 30 | | } |
| | 31 | |
|
| | 32 | | public static int Combine<T1>(T1 value1) |
| | 33 | | { |
| | 34 | | // Provide a way of diffusing bits from something with a limited |
| | 35 | | // input hash space. For example, many enums only have a few |
| | 36 | | // possible hashes, only using the bottom few bits of the code. Some |
| | 37 | | // collections are built on the assumption that hashes are spread |
| | 38 | | // over a larger space, so diffusing the bits may help the |
| | 39 | | // collection work more efficiently. |
| | 40 | |
|
| 0 | 41 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| | 42 | |
|
| 0 | 43 | | uint hash = MixEmptyState(); |
| 0 | 44 | | hash += 4; |
| | 45 | |
|
| 0 | 46 | | hash = QueueRound(hash, hc1); |
| | 47 | |
|
| 0 | 48 | | hash = MixFinal(hash); |
| 0 | 49 | | return (int)hash; |
| | 50 | | } |
| | 51 | |
|
| | 52 | | public static int Combine<T1, T2>(T1 value1, T2 value2) |
| | 53 | | { |
| 0 | 54 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 55 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| | 56 | |
|
| 0 | 57 | | uint hash = MixEmptyState(); |
| 0 | 58 | | hash += 8; |
| | 59 | |
|
| 0 | 60 | | hash = QueueRound(hash, hc1); |
| 0 | 61 | | hash = QueueRound(hash, hc2); |
| | 62 | |
|
| 0 | 63 | | hash = MixFinal(hash); |
| 0 | 64 | | return (int)hash; |
| | 65 | | } |
| | 66 | |
|
| | 67 | | public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3) |
| | 68 | | { |
| 0 | 69 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 70 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 71 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| | 72 | |
|
| 0 | 73 | | uint hash = MixEmptyState(); |
| 0 | 74 | | hash += 12; |
| | 75 | |
|
| 0 | 76 | | hash = QueueRound(hash, hc1); |
| 0 | 77 | | hash = QueueRound(hash, hc2); |
| 0 | 78 | | hash = QueueRound(hash, hc3); |
| | 79 | |
|
| 0 | 80 | | hash = MixFinal(hash); |
| 0 | 81 | | return (int)hash; |
| | 82 | | } |
| | 83 | |
|
| | 84 | | public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4) |
| | 85 | | { |
| 0 | 86 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 87 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 88 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| 0 | 89 | | var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
| | 90 | |
|
| 0 | 91 | | Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
| | 92 | |
|
| 0 | 93 | | v1 = Round(v1, hc1); |
| 0 | 94 | | v2 = Round(v2, hc2); |
| 0 | 95 | | v3 = Round(v3, hc3); |
| 0 | 96 | | v4 = Round(v4, hc4); |
| | 97 | |
|
| 0 | 98 | | uint hash = MixState(v1, v2, v3, v4); |
| 0 | 99 | | hash += 16; |
| | 100 | |
|
| 0 | 101 | | hash = MixFinal(hash); |
| 0 | 102 | | return (int)hash; |
| | 103 | | } |
| | 104 | |
|
| | 105 | | public static int Combine<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) |
| | 106 | | { |
| 0 | 107 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 108 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 109 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| 0 | 110 | | var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
| 0 | 111 | | var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
| | 112 | |
|
| 0 | 113 | | Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
| | 114 | |
|
| 0 | 115 | | v1 = Round(v1, hc1); |
| 0 | 116 | | v2 = Round(v2, hc2); |
| 0 | 117 | | v3 = Round(v3, hc3); |
| 0 | 118 | | v4 = Round(v4, hc4); |
| | 119 | |
|
| 0 | 120 | | uint hash = MixState(v1, v2, v3, v4); |
| 0 | 121 | | hash += 20; |
| | 122 | |
|
| 0 | 123 | | hash = QueueRound(hash, hc5); |
| | 124 | |
|
| 0 | 125 | | hash = MixFinal(hash); |
| 0 | 126 | | return (int)hash; |
| | 127 | | } |
| | 128 | |
|
| | 129 | | public static int Combine<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 valu |
| | 130 | | { |
| 0 | 131 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 132 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 133 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| 0 | 134 | | var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
| 0 | 135 | | var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
| 0 | 136 | | var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
| | 137 | |
|
| 0 | 138 | | Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
| | 139 | |
|
| 0 | 140 | | v1 = Round(v1, hc1); |
| 0 | 141 | | v2 = Round(v2, hc2); |
| 0 | 142 | | v3 = Round(v3, hc3); |
| 0 | 143 | | v4 = Round(v4, hc4); |
| | 144 | |
|
| 0 | 145 | | uint hash = MixState(v1, v2, v3, v4); |
| 0 | 146 | | hash += 24; |
| | 147 | |
|
| 0 | 148 | | hash = QueueRound(hash, hc5); |
| 0 | 149 | | hash = QueueRound(hash, hc6); |
| | 150 | |
|
| 0 | 151 | | hash = MixFinal(hash); |
| 0 | 152 | | return (int)hash; |
| | 153 | | } |
| | 154 | |
|
| | 155 | | public static int Combine<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 |
| | 156 | | { |
| 0 | 157 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 158 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 159 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| 0 | 160 | | var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
| 0 | 161 | | var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
| 0 | 162 | | var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
| 0 | 163 | | var hc7 = (uint)(value7?.GetHashCode() ?? 0); |
| | 164 | |
|
| 0 | 165 | | Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
| | 166 | |
|
| 0 | 167 | | v1 = Round(v1, hc1); |
| 0 | 168 | | v2 = Round(v2, hc2); |
| 0 | 169 | | v3 = Round(v3, hc3); |
| 0 | 170 | | v4 = Round(v4, hc4); |
| | 171 | |
|
| 0 | 172 | | uint hash = MixState(v1, v2, v3, v4); |
| 0 | 173 | | hash += 28; |
| | 174 | |
|
| 0 | 175 | | hash = QueueRound(hash, hc5); |
| 0 | 176 | | hash = QueueRound(hash, hc6); |
| 0 | 177 | | hash = QueueRound(hash, hc7); |
| | 178 | |
|
| 0 | 179 | | hash = MixFinal(hash); |
| 0 | 180 | | return (int)hash; |
| | 181 | | } |
| | 182 | |
|
| | 183 | | public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, |
| | 184 | | { |
| 0 | 185 | | var hc1 = (uint)(value1?.GetHashCode() ?? 0); |
| 0 | 186 | | var hc2 = (uint)(value2?.GetHashCode() ?? 0); |
| 0 | 187 | | var hc3 = (uint)(value3?.GetHashCode() ?? 0); |
| 0 | 188 | | var hc4 = (uint)(value4?.GetHashCode() ?? 0); |
| 0 | 189 | | var hc5 = (uint)(value5?.GetHashCode() ?? 0); |
| 0 | 190 | | var hc6 = (uint)(value6?.GetHashCode() ?? 0); |
| 0 | 191 | | var hc7 = (uint)(value7?.GetHashCode() ?? 0); |
| 0 | 192 | | var hc8 = (uint)(value8?.GetHashCode() ?? 0); |
| | 193 | |
|
| 0 | 194 | | Initialize(out uint v1, out uint v2, out uint v3, out uint v4); |
| | 195 | |
|
| 0 | 196 | | v1 = Round(v1, hc1); |
| 0 | 197 | | v2 = Round(v2, hc2); |
| 0 | 198 | | v3 = Round(v3, hc3); |
| 0 | 199 | | v4 = Round(v4, hc4); |
| | 200 | |
|
| 0 | 201 | | v1 = Round(v1, hc5); |
| 0 | 202 | | v2 = Round(v2, hc6); |
| 0 | 203 | | v3 = Round(v3, hc7); |
| 0 | 204 | | v4 = Round(v4, hc8); |
| | 205 | |
|
| 0 | 206 | | uint hash = MixState(v1, v2, v3, v4); |
| 0 | 207 | | hash += 32; |
| | 208 | |
|
| 0 | 209 | | hash = MixFinal(hash); |
| 0 | 210 | | return (int)hash; |
| | 211 | | } |
| | 212 | |
|
| | 213 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 214 | | private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) |
| | 215 | | { |
| 8 | 216 | | v1 = s_seed + Prime1 + Prime2; |
| 8 | 217 | | v2 = s_seed + Prime2; |
| 8 | 218 | | v3 = s_seed; |
| 8 | 219 | | v4 = s_seed - Prime1; |
| 8 | 220 | | } |
| | 221 | |
|
| | 222 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 223 | | public static uint RotateLeft(uint value, int offset) |
| 64 | 224 | | => (value << offset) | (value >> (64 - offset)); |
| | 225 | |
|
| | 226 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 227 | | private static uint Round(uint hash, uint input) |
| | 228 | | { |
| 32 | 229 | | return RotateLeft(hash + input * Prime2, 13) * Prime1; |
| | 230 | | } |
| | 231 | |
|
| | 232 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 233 | | private static uint QueueRound(uint hash, uint queuedValue) |
| | 234 | | { |
| 0 | 235 | | return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; |
| | 236 | | } |
| | 237 | |
|
| | 238 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 239 | | private static uint MixState(uint v1, uint v2, uint v3, uint v4) |
| | 240 | | { |
| 8 | 241 | | return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); |
| | 242 | | } |
| | 243 | |
|
| | 244 | | private static uint MixEmptyState() |
| | 245 | | { |
| 0 | 246 | | return s_seed + Prime5; |
| | 247 | | } |
| | 248 | |
|
| | 249 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 250 | | private static uint MixFinal(uint hash) |
| | 251 | | { |
| 8 | 252 | | hash ^= hash >> 15; |
| 8 | 253 | | hash *= Prime2; |
| 8 | 254 | | hash ^= hash >> 13; |
| 8 | 255 | | hash *= Prime3; |
| 8 | 256 | | hash ^= hash >> 16; |
| 8 | 257 | | return hash; |
| | 258 | | } |
| | 259 | |
|
| | 260 | | public void Add<T>(T value) |
| | 261 | | { |
| 32 | 262 | | Add(value?.GetHashCode() ?? 0); |
| 32 | 263 | | } |
| | 264 | |
|
| | 265 | | public void Add<T>(T value, IEqualityComparer<T> comparer) |
| | 266 | | { |
| 0 | 267 | | Add(comparer != null ? comparer.GetHashCode(value) : (value?.GetHashCode() ?? 0)); |
| 0 | 268 | | } |
| | 269 | |
|
| | 270 | | private void Add(int value) |
| | 271 | | { |
| | 272 | | // The original xxHash works as follows: |
| | 273 | | // 0. Initialize immediately. We can't do this in a struct (no |
| | 274 | | // default ctor). |
| | 275 | | // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. |
| | 276 | | // 2. Accumulate remaining blocks of length 4 (1 uint) into the |
| | 277 | | // hash. |
| | 278 | | // 3. Accumulate remaining blocks of length 1 into the hash. |
| | 279 | |
|
| | 280 | | // There is no need for #3 as this type only accepts ints. _queue1, |
| | 281 | | // _queue2 and _queue3 are basically a buffer so that when |
| | 282 | | // ToHashCode is called we can execute #2 correctly. |
| | 283 | |
|
| | 284 | | // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see |
| | 285 | | // #0) nd the last place that can be done if you look at the |
| | 286 | | // original code is just before the first block of 16 bytes is mixed |
| | 287 | | // in. The xxHash32 state is never used for streams containing fewer |
| | 288 | | // than 16 bytes. |
| | 289 | |
|
| | 290 | | // To see what's really going on here, have a look at the Combine |
| | 291 | | // methods. |
| | 292 | |
|
| 32 | 293 | | var val = (uint)value; |
| | 294 | |
|
| | 295 | | // Storing the value of _length locally shaves of quite a few bytes |
| | 296 | | // in the resulting machine code. |
| 32 | 297 | | uint previousLength = _length++; |
| 32 | 298 | | uint position = previousLength % 4; |
| | 299 | |
|
| | 300 | | // Switch can't be inlined. |
| | 301 | |
|
| 32 | 302 | | if (position == 0) |
| 8 | 303 | | _queue1 = val; |
| 24 | 304 | | else if (position == 1) |
| 8 | 305 | | _queue2 = val; |
| 16 | 306 | | else if (position == 2) |
| 8 | 307 | | _queue3 = val; |
| | 308 | | else // position == 3 |
| | 309 | | { |
| 8 | 310 | | if (previousLength == 3) |
| 8 | 311 | | Initialize(out _v1, out _v2, out _v3, out _v4); |
| | 312 | |
|
| 8 | 313 | | _v1 = Round(_v1, _queue1); |
| 8 | 314 | | _v2 = Round(_v2, _queue2); |
| 8 | 315 | | _v3 = Round(_v3, _queue3); |
| 8 | 316 | | _v4 = Round(_v4, val); |
| | 317 | | } |
| 8 | 318 | | } |
| | 319 | |
|
| | 320 | | public int ToHashCode() |
| | 321 | | { |
| | 322 | | // Storing the value of _length locally shaves of quite a few bytes |
| | 323 | | // in the resulting machine code. |
| 8 | 324 | | uint length = _length; |
| | 325 | |
|
| | 326 | | // position refers to the *next* queue position in this method, so |
| | 327 | | // position == 1 means that _queue1 is populated; _queue2 would have |
| | 328 | | // been populated on the next call to Add. |
| 8 | 329 | | uint position = length % 4; |
| | 330 | |
|
| | 331 | | // If the length is less than 4, _v1 to _v4 don't contain anything |
| | 332 | | // yet. xxHash32 treats this differently. |
| | 333 | |
|
| 8 | 334 | | uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); |
| | 335 | |
|
| | 336 | | // _length is incremented once per Add(Int32) and is therefore 4 |
| | 337 | | // times too small (xxHash length is in bytes, not ints). |
| | 338 | |
|
| 8 | 339 | | hash += length * 4; |
| | 340 | |
|
| | 341 | | // Mix what remains in the queue |
| | 342 | |
|
| | 343 | | // Switch can't be inlined right now, so use as few branches as |
| | 344 | | // possible by manually excluding impossible scenarios (position > 1 |
| | 345 | | // is always false if position is not > 0). |
| 8 | 346 | | if (position > 0) |
| | 347 | | { |
| 0 | 348 | | hash = QueueRound(hash, _queue1); |
| 0 | 349 | | if (position > 1) |
| | 350 | | { |
| 0 | 351 | | hash = QueueRound(hash, _queue2); |
| 0 | 352 | | if (position > 2) |
| 0 | 353 | | hash = QueueRound(hash, _queue3); |
| | 354 | | } |
| | 355 | | } |
| | 356 | |
|
| 8 | 357 | | hash = MixFinal(hash); |
| 8 | 358 | | return (int)hash; |
| | 359 | | } |
| | 360 | | } |
| | 361 | | } |