| | | 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 | | { |
| | 0 | 216 | | v1 = s_seed + Prime1 + Prime2; |
| | 0 | 217 | | v2 = s_seed + Prime2; |
| | 0 | 218 | | v3 = s_seed; |
| | 0 | 219 | | v4 = s_seed - Prime1; |
| | 0 | 220 | | } |
| | | 221 | | |
| | | 222 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 223 | | public static uint RotateLeft(uint value, int offset) |
| | 8 | 224 | | => (value << offset) | (value >> (64 - offset)); |
| | | 225 | | |
| | | 226 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 227 | | private static uint Round(uint hash, uint input) |
| | | 228 | | { |
| | 0 | 229 | | return RotateLeft(hash + input * Prime2, 13) * Prime1; |
| | | 230 | | } |
| | | 231 | | |
| | | 232 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 233 | | private static uint QueueRound(uint hash, uint queuedValue) |
| | | 234 | | { |
| | 8 | 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 | | { |
| | 0 | 241 | | return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); |
| | | 242 | | } |
| | | 243 | | |
| | | 244 | | private static uint MixEmptyState() |
| | | 245 | | { |
| | 4 | 246 | | return s_seed + Prime5; |
| | | 247 | | } |
| | | 248 | | |
| | | 249 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 250 | | private static uint MixFinal(uint hash) |
| | | 251 | | { |
| | 4 | 252 | | hash ^= hash >> 15; |
| | 4 | 253 | | hash *= Prime2; |
| | 4 | 254 | | hash ^= hash >> 13; |
| | 4 | 255 | | hash *= Prime3; |
| | 4 | 256 | | hash ^= hash >> 16; |
| | 4 | 257 | | return hash; |
| | | 258 | | } |
| | | 259 | | |
| | | 260 | | public void Add<T>(T value) |
| | | 261 | | { |
| | 4 | 262 | | Add(value?.GetHashCode() ?? 0); |
| | 4 | 263 | | } |
| | | 264 | | |
| | | 265 | | public void Add<T>(T value, IEqualityComparer<T> comparer) |
| | | 266 | | { |
| | 4 | 267 | | Add(comparer != null ? comparer.GetHashCode(value) : (value?.GetHashCode() ?? 0)); |
| | 4 | 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 | | |
| | 8 | 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. |
| | 8 | 297 | | uint previousLength = _length++; |
| | 8 | 298 | | uint position = previousLength % 4; |
| | | 299 | | |
| | | 300 | | // Switch can't be inlined. |
| | | 301 | | |
| | 8 | 302 | | if (position == 0) |
| | 4 | 303 | | _queue1 = val; |
| | 4 | 304 | | else if (position == 1) |
| | 4 | 305 | | _queue2 = val; |
| | 0 | 306 | | else if (position == 2) |
| | 0 | 307 | | _queue3 = val; |
| | | 308 | | else // position == 3 |
| | | 309 | | { |
| | 0 | 310 | | if (previousLength == 3) |
| | 0 | 311 | | Initialize(out _v1, out _v2, out _v3, out _v4); |
| | | 312 | | |
| | 0 | 313 | | _v1 = Round(_v1, _queue1); |
| | 0 | 314 | | _v2 = Round(_v2, _queue2); |
| | 0 | 315 | | _v3 = Round(_v3, _queue3); |
| | 0 | 316 | | _v4 = Round(_v4, val); |
| | | 317 | | } |
| | 0 | 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. |
| | 4 | 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. |
| | 4 | 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 | | |
| | 4 | 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 | | |
| | 4 | 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). |
| | 4 | 346 | | if (position > 0) |
| | | 347 | | { |
| | 4 | 348 | | hash = QueueRound(hash, _queue1); |
| | 4 | 349 | | if (position > 1) |
| | | 350 | | { |
| | 4 | 351 | | hash = QueueRound(hash, _queue2); |
| | 4 | 352 | | if (position > 2) |
| | 0 | 353 | | hash = QueueRound(hash, _queue3); |
| | | 354 | | } |
| | | 355 | | } |
| | | 356 | | |
| | 4 | 357 | | hash = MixFinal(hash); |
| | 4 | 358 | | return (int)hash; |
| | | 359 | | } |
| | | 360 | | } |
| | | 361 | | } |