// Partial code copied from: // https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Private.CoreLib/src/System/HashCode.cs #nullable disable using System; using System.Runtime.CompilerServices; #if HARFBUZZ namespace HarfBuzzSharp #else namespace SkiaSharp #endif { internal unsafe struct HashCode { private static readonly uint s_seed = GenerateGlobalSeed (); private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 668265263U; private const uint Prime5 = 374761393U; private uint _v1, _v2, _v3, _v4; private uint _queue1, _queue2, _queue3; private uint _length; private static unsafe uint GenerateGlobalSeed () { var rnd = new Random (); var result = rnd.Next (); return unchecked((uint)result); } [MethodImpl (MethodImplOptions.AggressiveInlining)] private static void Initialize (out uint v1, out uint v2, out uint v3, out uint v4) { v1 = s_seed + Prime1 + Prime2; v2 = s_seed + Prime2; v3 = s_seed; v4 = s_seed - Prime1; } [MethodImpl (MethodImplOptions.AggressiveInlining)] private static uint Round (uint hash, uint input) => RotateLeft (hash + input * Prime2, 13) * Prime1; [MethodImpl (MethodImplOptions.AggressiveInlining)] private static uint QueueRound (uint hash, uint queuedValue) => RotateLeft (hash + queuedValue * Prime3, 17) * Prime4; [MethodImpl (MethodImplOptions.AggressiveInlining)] private static uint MixState (uint v1, uint v2, uint v3, uint v4) => RotateLeft (v1, 1) + RotateLeft (v2, 7) + RotateLeft (v3, 12) + RotateLeft (v4, 18); [MethodImpl (MethodImplOptions.AggressiveInlining)] private static uint RotateLeft (uint value, int offset) => (value << offset) | (value >> (32 - offset)); private static uint MixEmptyState () => s_seed + Prime5; [MethodImpl (MethodImplOptions.AggressiveInlining)] private static uint MixFinal (uint hash) { hash ^= hash >> 15; hash *= Prime2; hash ^= hash >> 13; hash *= Prime3; hash ^= hash >> 16; return hash; } public void Add (void* value) => Add (value == null ? 0 : ((IntPtr)value).GetHashCode ()); public void Add (T value) => Add (value?.GetHashCode () ?? 0); private void Add (int value) { uint val = (uint)value; // Storing the value of _length locally shaves of quite a few bytes // in the resulting machine code. uint previousLength = _length++; uint position = previousLength % 4; // Switch can't be inlined. if (position == 0) _queue1 = val; else if (position == 1) _queue2 = val; else if (position == 2) _queue3 = val; else // position == 3 { if (previousLength == 3) Initialize (out _v1, out _v2, out _v3, out _v4); _v1 = Round (_v1, _queue1); _v2 = Round (_v2, _queue2); _v3 = Round (_v3, _queue3); _v4 = Round (_v4, val); } } public int ToHashCode () { // Storing the value of _length locally shaves of quite a few bytes // in the resulting machine code. uint length = _length; // position refers to the *next* queue position in this method, so // position == 1 means that _queue1 is populated; _queue2 would have // been populated on the next call to Add. uint position = length % 4; // If the length is less than 4, _v1 to _v4 don't contain anything // yet. xxHash32 treats this differently. uint hash = length < 4 ? MixEmptyState () : MixState (_v1, _v2, _v3, _v4); // _length is incremented once per Add(Int32) and is therefore 4 // times too small (xxHash length is in bytes, not ints). hash += length * 4; // Mix what remains in the queue // Switch can't be inlined right now, so use as few branches as // possible by manually excluding impossible scenarios (position > 1 // is always false if position is not > 0). if (position > 0) { hash = QueueRound (hash, _queue1); if (position > 1) { hash = QueueRound (hash, _queue2); if (position > 2) hash = QueueRound (hash, _queue3); } } hash = MixFinal (hash); return (int)hash; } } }