From 568613d21d271010572d1c9fee7b10dc4fc0708f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Aug 2023 12:25:11 +0200 Subject: [PATCH] Added polyfil of System.HashCode. --- src/Avalonia.Base/Utilities/HashCode.cs | 149 ++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/Avalonia.Base/Utilities/HashCode.cs diff --git a/src/Avalonia.Base/Utilities/HashCode.cs b/src/Avalonia.Base/Utilities/HashCode.cs new file mode 100644 index 0000000000..c68c42a9c7 --- /dev/null +++ b/src/Avalonia.Base/Utilities/HashCode.cs @@ -0,0 +1,149 @@ +// Taken from: +// https://github.com/mono/SkiaSharp/blob/main/binding/Binding.Shared/HashCode.cs +// Partial code copied from: +// https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Private.CoreLib/src/System/HashCode.cs + +#if NETSTANDARD2_0 +#nullable disable + +using System.Runtime.CompilerServices; + +namespace System; + +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; + } +} +#endif