diff --git a/src/libraries/Common/src/System/IO/Hashing/XxHash3.Common.cs b/src/libraries/Common/src/System/IO/Hashing/XxHash3.Common.cs new file mode 100644 index 00000000000000..7ccc9a8a5a00eb --- /dev/null +++ b/src/libraries/Common/src/System/IO/Hashing/XxHash3.Common.cs @@ -0,0 +1,285 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Based on the XXH3 implementation from https://github.com/Cyan4973/xxHash. + +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static System.IO.Hashing.XxHashShared; + +namespace System.IO.Hashing +{ + /// Provides an implementation of the XXH3 hash algorithm for generating a 64-bit hash. + /// + /// For methods that persist the computed numerical hash value as bytes, + /// the value is written in the Big Endian byte order. + /// +#if NET + [SkipLocalsInit] +#endif +#if SYSTEM_PRIVATE_CORELIB + internal +#else + public +#endif + sealed unsafe partial class XxHash3 + { + /// Computes the XXH3 hash of the provided data. + /// The data to hash. + /// The seed value for this hash computation. + /// The computed XXH3 hash. +#if !SYSTEM_PRIVATE_CORELIB + [CLSCompliant(false)] +#endif + public static ulong HashToUInt64(ReadOnlySpan source, long seed = 0) + { + uint length = (uint)source.Length; + fixed (byte* sourcePtr = &MemoryMarshal.GetReference(source)) + { + if (length <= 16) + { + return HashLength0To16(sourcePtr, length, (ulong)seed); + } + + if (length <= 128) + { + return HashLength17To128(sourcePtr, length, (ulong)seed); + } + + if (length <= MidSizeMaxBytes) + { + return HashLength129To240(sourcePtr, length, (ulong)seed); + } + + return HashLengthOver240(sourcePtr, length, (ulong)seed); + } + } + + +#if SYSTEM_PRIVATE_CORELIB + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int NonRandomizedHashToInt32(byte* sourcePtr, uint length) + { + switch (length) + { + case 0: + return (int)Avalanche(DefaultSecretUInt64_7 ^ DefaultSecretUInt64_8); + + case 1: + case 2: + case 3: + return (int)HashLength1To3(sourcePtr, length, 0UL); + + case 4: + case 5: + case 6: + case 7: + case 8: + return (int)HashLength4To8(sourcePtr, length, 0UL); + + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + return (int)HashLength9To16(sourcePtr, length, 0UL); + + default: + { + if (length <= 128) + { + return (int)HashLength17To128(sourcePtr, length, 0UL); + } + if (length <= MidSizeMaxBytes) + { + return (int)HashLength129To240(sourcePtr, length, 0UL); + } + return (int)HashLengthOver240(sourcePtr, length, 0UL); + } + } + } +#endif + + private static ulong HashLength0To16(byte* source, uint length, ulong seed) + { + if (length > 8) + { + return HashLength9To16(source, length, seed); + } + + if (length >= 4) + { + return HashLength4To8(source, length, seed); + } + + if (length != 0) + { + return HashLength1To3(source, length, seed); + } + + const ulong SecretXor = DefaultSecretUInt64_7 ^ DefaultSecretUInt64_8; + return Avalanche(seed ^ SecretXor); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong HashLength1To3(byte* source, uint length, ulong seed) + { + Debug.Assert(length >= 1 && length <= 3); + + // When source.Length == 1, c1 == source[0], c2 == source[0], c3 == source[0] + // When source.Length == 2, c1 == source[0], c2 == source[1], c3 == source[1] + // When source.Length == 3, c1 == source[0], c2 == source[1], c3 == source[2] + byte c1 = *source; + byte c2 = source[length >> 1]; + byte c3 = source[length - 1]; + + uint combined = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | (length << 8); + + const uint SecretXor = unchecked((uint)DefaultSecretUInt64_0) ^ (uint)(DefaultSecretUInt64_0 >> 32); + return Avalanche(combined ^ (SecretXor + seed)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong HashLength4To8(byte* source, uint length, ulong seed) + { + Debug.Assert(length >= 4 && length <= 8); + + seed ^= (ulong)BinaryPrimitives.ReverseEndianness((uint)seed) << 32; + + uint inputLow = ReadUInt32LE(source); + uint inputHigh = ReadUInt32LE(source + length - sizeof(uint)); + + const ulong SecretXor = DefaultSecretUInt64_1 ^ DefaultSecretUInt64_2; + ulong bitflip = SecretXor - seed; + ulong input64 = inputHigh + (((ulong)inputLow) << 32); + + return Rrmxmx(input64 ^ bitflip, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong HashLength9To16(byte* source, uint length, ulong seed) + { + Debug.Assert(length >= 9 && length <= 16); + + const ulong SecretXorL = DefaultSecretUInt64_3 ^ DefaultSecretUInt64_4; + const ulong SecretXorR = DefaultSecretUInt64_5 ^ DefaultSecretUInt64_6; + ulong bitflipLow = SecretXorL + seed; + ulong bitflipHigh = SecretXorR - seed; + + ulong inputLow = ReadUInt64LE(source) ^ bitflipLow; + ulong inputHigh = ReadUInt64LE(source + length - sizeof(ulong)) ^ bitflipHigh; + + return FastAvalanche( + length + + BinaryPrimitives.ReverseEndianness(inputLow) + + inputHigh + + Multiply64To128ThenFold(inputLow, inputHigh)); + } + + private static ulong HashLength17To128(byte* source, uint length, ulong seed) + { + Debug.Assert(length >= 17 && length <= 128); + + ulong hash = length * Prime64_1; + + switch ((length - 1) / 32) + { + default: // case 3 + hash += Mix16Bytes(source + 48, DefaultSecretUInt64_12, DefaultSecretUInt64_13, seed); + hash += Mix16Bytes(source + length - 64, DefaultSecretUInt64_14, DefaultSecretUInt64_15, seed); + goto case 2; + case 2: + hash += Mix16Bytes(source + 32, DefaultSecretUInt64_8, DefaultSecretUInt64_9, seed); + hash += Mix16Bytes(source + length - 48, DefaultSecretUInt64_10, DefaultSecretUInt64_11, seed); + goto case 1; + case 1: + hash += Mix16Bytes(source + 16, DefaultSecretUInt64_4, DefaultSecretUInt64_5, seed); + hash += Mix16Bytes(source + length - 32, DefaultSecretUInt64_6, DefaultSecretUInt64_7, seed); + goto case 0; + case 0: + hash += Mix16Bytes(source, DefaultSecretUInt64_0, DefaultSecretUInt64_1, seed); + hash += Mix16Bytes(source + length - 16, DefaultSecretUInt64_2, DefaultSecretUInt64_3, seed); + break; + } + + return FastAvalanche(hash); + } + + private static ulong HashLength129To240(byte* source, uint length, ulong seed) + { + Debug.Assert(length >= 129 && length <= 240); + + ulong hash = length * Prime64_1; + + hash += Mix16Bytes(source + (16 * 0), DefaultSecretUInt64_0, DefaultSecretUInt64_1, seed); + hash += Mix16Bytes(source + (16 * 1), DefaultSecretUInt64_2, DefaultSecretUInt64_3, seed); + hash += Mix16Bytes(source + (16 * 2), DefaultSecretUInt64_4, DefaultSecretUInt64_5, seed); + hash += Mix16Bytes(source + (16 * 3), DefaultSecretUInt64_6, DefaultSecretUInt64_7, seed); + hash += Mix16Bytes(source + (16 * 4), DefaultSecretUInt64_8, DefaultSecretUInt64_9, seed); + hash += Mix16Bytes(source + (16 * 5), DefaultSecretUInt64_10, DefaultSecretUInt64_11, seed); + hash += Mix16Bytes(source + (16 * 6), DefaultSecretUInt64_12, DefaultSecretUInt64_13, seed); + hash += Mix16Bytes(source + (16 * 7), DefaultSecretUInt64_14, DefaultSecretUInt64_15, seed); + + hash = FastAvalanche(hash); + + switch ((length - (16 * 8)) / 16) + { + default: // case 7 + Debug.Assert((length - 16 * 8) / 16 == 7); + hash += Mix16Bytes(source + (16 * 14), DefaultSecret3UInt64_12, DefaultSecret3UInt64_13, seed); + goto case 6; + case 6: + hash += Mix16Bytes(source + (16 * 13), DefaultSecret3UInt64_10, DefaultSecret3UInt64_11, seed); + goto case 5; + case 5: + hash += Mix16Bytes(source + (16 * 12), DefaultSecret3UInt64_8, DefaultSecret3UInt64_9, seed); + goto case 4; + case 4: + hash += Mix16Bytes(source + (16 * 11), DefaultSecret3UInt64_6, DefaultSecret3UInt64_7, seed); + goto case 3; + case 3: + hash += Mix16Bytes(source + (16 * 10), DefaultSecret3UInt64_4, DefaultSecret3UInt64_5, seed); + goto case 2; + case 2: + hash += Mix16Bytes(source + (16 * 9), DefaultSecret3UInt64_2, DefaultSecret3UInt64_3, seed); + goto case 1; + case 1: + hash += Mix16Bytes(source + (16 * 8), DefaultSecret3UInt64_0, DefaultSecret3UInt64_1, seed); + goto case 0; + case 0: + hash += Mix16Bytes(source + length - 16, 0x7378D9C97E9FC831, 0xEBD33483ACC5EA64, seed); // DefaultSecret[119], DefaultSecret[127] + break; + } + + return FastAvalanche(hash); + } + + private static ulong HashLengthOver240(byte* source, uint length, ulong seed) + { + Debug.Assert(length > 240); + + fixed (byte* defaultSecret = &MemoryMarshal.GetReference(DefaultSecret)) + { + byte* secret = defaultSecret; + if (seed != 0) + { + byte* customSecret = stackalloc byte[SecretLengthBytes]; + DeriveSecretFromSeed(customSecret, seed); + secret = customSecret; + } + + ulong* accumulators = stackalloc ulong[AccumulatorCount]; + InitializeAccumulators(accumulators); + + HashInternalLoop(accumulators, source, length, secret); + + return MergeAccumulators(accumulators, secret + 11, length * Prime64_1); + } + } + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHashShared.cs b/src/libraries/Common/src/System/IO/Hashing/XxHashShared.cs similarity index 98% rename from src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHashShared.cs rename to src/libraries/Common/src/System/IO/Hashing/XxHashShared.cs index cafa80dbbf112e..1d9ad2cc155112 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHashShared.cs +++ b/src/libraries/Common/src/System/IO/Hashing/XxHashShared.cs @@ -14,7 +14,9 @@ namespace System.IO.Hashing { +#if !SYSTEM_PRIVATE_CORELIB /// Shared implementation of the XXH3 hash algorithm for 64-bit in and version. +#endif #if NET [SkipLocalsInit] #endif @@ -426,7 +428,7 @@ public static ulong MergeAccumulators(ulong* accumulators, byte* secret, ulong s result64 += Multiply64To128ThenFold(accumulators[4] ^ ReadUInt64LE(secret + 32), accumulators[5] ^ ReadUInt64LE(secret + 40)); result64 += Multiply64To128ThenFold(accumulators[6] ^ ReadUInt64LE(secret + 48), accumulators[7] ^ ReadUInt64LE(secret + 56)); - return Avalanche(result64); + return FastAvalanche(result64); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -439,9 +441,20 @@ public static ulong Mix16Bytes(byte* source, ulong secretLow, ulong secretHigh, [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Multiply32To64(uint v1, uint v2) => (ulong)v1 * v2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong Avalanche(ulong hash) + { + hash ^= hash >> 33; + hash *= Prime64_2; + hash ^= hash >> 29; + hash *= Prime64_3; + hash ^= hash >> 32; + return hash; + } + /// "This is a fast avalanche stage, suitable when input bits are already partially mixed." [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Avalanche(ulong hash) + public static ulong FastAvalanche(ulong hash) { hash = XorShift(hash, 37); hash *= 0x165667919E3779F9; diff --git a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj index d41f622de28498..6739eaab0d1c7f 100644 --- a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj +++ b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj @@ -19,12 +19,17 @@ System.IO.Hashing.XxHash32 + + Common\System\IO\Hashing\XxHash3.Common.cs + - + + Common\System\IO\Hashing\XxHashShared.cs + diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs index 23ff9fce02a671..dad458ff48874c 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs @@ -246,7 +246,7 @@ private static Hash128 HashLength0To16(byte* source, uint length, ulong seed) const ulong BitFlipL = DefaultSecretUInt64_8 ^ DefaultSecretUInt64_9; const ulong BitFlipH = DefaultSecretUInt64_10 ^ DefaultSecretUInt64_11; - return new Hash128(XxHash64.Avalanche(seed ^ BitFlipL), XxHash64.Avalanche(seed ^ BitFlipH)); + return new Hash128(Avalanche(seed ^ BitFlipL), Avalanche(seed ^ BitFlipH)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -271,7 +271,7 @@ private static Hash128 HashLength1To3(byte* source, uint length, ulong seed) ulong keyedLo = combinedl ^ bitflipl; ulong keyedHi = combinedh ^ bitfliph; - return new Hash128(XxHash64.Avalanche(keyedLo), XxHash64.Avalanche(keyedHi)); + return new Hash128(Avalanche(keyedLo), Avalanche(keyedHi)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -295,7 +295,7 @@ private static Hash128 HashLength4To8(byte* source, uint length, ulong seed) m128Low = XorShift(m128Low, 35); m128Low *= 0x9FB21C651E98DF25UL; m128Low = XorShift(m128Low, 28); - m128High = Avalanche(m128High); + m128High = FastAvalanche(m128High); return new Hash128(m128Low, m128High); } @@ -322,8 +322,8 @@ private static Hash128 HashLength9To16(byte* source, uint length, ulong seed) ulong h128High = Multiply64To128(m128Low, Prime64_2, out ulong h128Low); h128High += m128High * (ulong)Prime64_2; - h128Low = Avalanche(h128Low); - h128High = Avalanche(h128High); + h128Low = FastAvalanche(h128Low); + h128High = FastAvalanche(h128High); return new Hash128(h128Low, h128High); } @@ -365,8 +365,8 @@ private static Hash128 HashLength129To240(byte* source, uint length, ulong seed) Mix32Bytes(ref accLow, ref accHigh, source + (32 * 2), source + (32 * 2) + 16, DefaultSecretUInt64_8, DefaultSecretUInt64_9, DefaultSecretUInt64_10, DefaultSecretUInt64_11, seed); Mix32Bytes(ref accLow, ref accHigh, source + (32 * 3), source + (32 * 3) + 16, DefaultSecretUInt64_12, DefaultSecretUInt64_13, DefaultSecretUInt64_14, DefaultSecretUInt64_15, seed); - accLow = Avalanche(accLow); - accHigh = Avalanche(accHigh); + accLow = FastAvalanche(accLow); + accHigh = FastAvalanche(accHigh); uint bound = ((length - (32 * 4)) / 32); if (bound != 0) @@ -418,8 +418,8 @@ private static Hash128 AvalancheHash(ulong accLow, ulong accHigh, uint length, u ulong h128High = (accLow * Prime64_1) + (accHigh * Prime64_4) + ((length - seed) * Prime64_2); - h128Low = Avalanche(h128Low); - h128High = 0ul - Avalanche(h128High); + h128Low = FastAvalanche(h128Low); + h128High = 0ul - FastAvalanche(h128High); return new Hash128(h128Low, h128High); } diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs index c7985efc0e66b7..07e591a845df29 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs @@ -16,10 +16,7 @@ namespace System.IO.Hashing /// For methods that persist the computed numerical hash value as bytes, /// the value is written in the Big Endian byte order. /// -#if NET - [SkipLocalsInit] -#endif - public sealed unsafe class XxHash3 : NonCryptographicHashAlgorithm + public sealed unsafe partial class XxHash3 : NonCryptographicHashAlgorithm { /// XXH3 produces 8-byte hashes. private new const int HashLengthInBytes = 8; @@ -119,35 +116,6 @@ public static bool TryHash(ReadOnlySpan source, Span destination, ou return false; } - /// Computes the XXH3 hash of the provided data. - /// The data to hash. - /// The seed value for this hash computation. - /// The computed XXH3 hash. - [CLSCompliant(false)] - public static ulong HashToUInt64(ReadOnlySpan source, long seed = 0) - { - uint length = (uint)source.Length; - fixed (byte* sourcePtr = &MemoryMarshal.GetReference(source)) - { - if (length <= 16) - { - return HashLength0To16(sourcePtr, length, (ulong)seed); - } - - if (length <= 128) - { - return HashLength17To128(sourcePtr, length, (ulong)seed); - } - - if (length <= MidSizeMaxBytes) - { - return HashLength129To240(sourcePtr, length, (ulong)seed); - } - - return HashLengthOver240(sourcePtr, length, (ulong)seed); - } - } - /// Resets the hash computation to the initial state. public override void Reset() { @@ -198,182 +166,5 @@ public ulong GetCurrentHashAsUInt64() return current; } - - private static ulong HashLength0To16(byte* source, uint length, ulong seed) - { - if (length > 8) - { - return HashLength9To16(source, length, seed); - } - - if (length >= 4) - { - return HashLength4To8(source, length, seed); - } - - if (length != 0) - { - return HashLength1To3(source, length, seed); - } - - const ulong SecretXor = DefaultSecretUInt64_7 ^ DefaultSecretUInt64_8; - return XxHash64.Avalanche(seed ^ SecretXor); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong HashLength1To3(byte* source, uint length, ulong seed) - { - Debug.Assert(length >= 1 && length <= 3); - - // When source.Length == 1, c1 == source[0], c2 == source[0], c3 == source[0] - // When source.Length == 2, c1 == source[0], c2 == source[1], c3 == source[1] - // When source.Length == 3, c1 == source[0], c2 == source[1], c3 == source[2] - byte c1 = *source; - byte c2 = source[length >> 1]; - byte c3 = source[length - 1]; - - uint combined = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | (length << 8); - - const uint SecretXor = unchecked((uint)DefaultSecretUInt64_0) ^ (uint)(DefaultSecretUInt64_0 >> 32); - return XxHash64.Avalanche(combined ^ (SecretXor + seed)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong HashLength4To8(byte* source, uint length, ulong seed) - { - Debug.Assert(length >= 4 && length <= 8); - - seed ^= (ulong)BinaryPrimitives.ReverseEndianness((uint)seed) << 32; - - uint inputLow = ReadUInt32LE(source); - uint inputHigh = ReadUInt32LE(source + length - sizeof(uint)); - - const ulong SecretXor = DefaultSecretUInt64_1 ^ DefaultSecretUInt64_2; - ulong bitflip = SecretXor - seed; - ulong input64 = inputHigh + (((ulong)inputLow) << 32); - - return Rrmxmx(input64 ^ bitflip, length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong HashLength9To16(byte* source, uint length, ulong seed) - { - Debug.Assert(length >= 9 && length <= 16); - - const ulong SecretXorL = DefaultSecretUInt64_3 ^ DefaultSecretUInt64_4; - const ulong SecretXorR = DefaultSecretUInt64_5 ^ DefaultSecretUInt64_6; - ulong bitflipLow = SecretXorL + seed; - ulong bitflipHigh = SecretXorR - seed; - - ulong inputLow = ReadUInt64LE(source) ^ bitflipLow; - ulong inputHigh = ReadUInt64LE(source + length - sizeof(ulong)) ^ bitflipHigh; - - return Avalanche( - length + - BinaryPrimitives.ReverseEndianness(inputLow) + - inputHigh + - Multiply64To128ThenFold(inputLow, inputHigh)); - } - - private static ulong HashLength17To128(byte* source, uint length, ulong seed) - { - Debug.Assert(length >= 17 && length <= 128); - - ulong hash = length * Prime64_1; - - switch ((length - 1) / 32) - { - default: // case 3 - hash += Mix16Bytes(source + 48, DefaultSecretUInt64_12, DefaultSecretUInt64_13, seed); - hash += Mix16Bytes(source + length - 64, DefaultSecretUInt64_14, DefaultSecretUInt64_15, seed); - goto case 2; - case 2: - hash += Mix16Bytes(source + 32, DefaultSecretUInt64_8, DefaultSecretUInt64_9, seed); - hash += Mix16Bytes(source + length - 48, DefaultSecretUInt64_10, DefaultSecretUInt64_11, seed); - goto case 1; - case 1: - hash += Mix16Bytes(source + 16, DefaultSecretUInt64_4, DefaultSecretUInt64_5, seed); - hash += Mix16Bytes(source + length - 32, DefaultSecretUInt64_6, DefaultSecretUInt64_7, seed); - goto case 0; - case 0: - hash += Mix16Bytes(source, DefaultSecretUInt64_0, DefaultSecretUInt64_1, seed); - hash += Mix16Bytes(source + length - 16, DefaultSecretUInt64_2, DefaultSecretUInt64_3, seed); - break; - } - - return Avalanche(hash); - } - - private static ulong HashLength129To240(byte* source, uint length, ulong seed) - { - Debug.Assert(length >= 129 && length <= 240); - - ulong hash = length * Prime64_1; - - hash += Mix16Bytes(source + (16 * 0), DefaultSecretUInt64_0, DefaultSecretUInt64_1, seed); - hash += Mix16Bytes(source + (16 * 1), DefaultSecretUInt64_2, DefaultSecretUInt64_3, seed); - hash += Mix16Bytes(source + (16 * 2), DefaultSecretUInt64_4, DefaultSecretUInt64_5, seed); - hash += Mix16Bytes(source + (16 * 3), DefaultSecretUInt64_6, DefaultSecretUInt64_7, seed); - hash += Mix16Bytes(source + (16 * 4), DefaultSecretUInt64_8, DefaultSecretUInt64_9, seed); - hash += Mix16Bytes(source + (16 * 5), DefaultSecretUInt64_10, DefaultSecretUInt64_11, seed); - hash += Mix16Bytes(source + (16 * 6), DefaultSecretUInt64_12, DefaultSecretUInt64_13, seed); - hash += Mix16Bytes(source + (16 * 7), DefaultSecretUInt64_14, DefaultSecretUInt64_15, seed); - - hash = Avalanche(hash); - - switch ((length - (16 * 8)) / 16) - { - default: // case 7 - Debug.Assert((length - 16 * 8) / 16 == 7); - hash += Mix16Bytes(source + (16 * 14), DefaultSecret3UInt64_12, DefaultSecret3UInt64_13, seed); - goto case 6; - case 6: - hash += Mix16Bytes(source + (16 * 13), DefaultSecret3UInt64_10, DefaultSecret3UInt64_11, seed); - goto case 5; - case 5: - hash += Mix16Bytes(source + (16 * 12), DefaultSecret3UInt64_8, DefaultSecret3UInt64_9, seed); - goto case 4; - case 4: - hash += Mix16Bytes(source + (16 * 11), DefaultSecret3UInt64_6, DefaultSecret3UInt64_7, seed); - goto case 3; - case 3: - hash += Mix16Bytes(source + (16 * 10), DefaultSecret3UInt64_4, DefaultSecret3UInt64_5, seed); - goto case 2; - case 2: - hash += Mix16Bytes(source + (16 * 9), DefaultSecret3UInt64_2, DefaultSecret3UInt64_3, seed); - goto case 1; - case 1: - hash += Mix16Bytes(source + (16 * 8), DefaultSecret3UInt64_0, DefaultSecret3UInt64_1, seed); - goto case 0; - case 0: - hash += Mix16Bytes(source + length - 16, 0x7378D9C97E9FC831, 0xEBD33483ACC5EA64, seed); // DefaultSecret[119], DefaultSecret[127] - break; - } - - return Avalanche(hash); - } - - private static ulong HashLengthOver240(byte* source, uint length, ulong seed) - { - Debug.Assert(length > 240); - - fixed (byte* defaultSecret = &MemoryMarshal.GetReference(DefaultSecret)) - { - byte* secret = defaultSecret; - if (seed != 0) - { - byte* customSecret = stackalloc byte[SecretLengthBytes]; - DeriveSecretFromSeed(customSecret, seed); - secret = customSecret; - } - - ulong* accumulators = stackalloc ulong[AccumulatorCount]; - InitializeAccumulators(accumulators); - - HashInternalLoop(accumulators, source, length, secret); - - return MergeAccumulators(accumulators, secret + 11, length * Prime64_1); - } - } } } diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.State.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.State.cs index 9462928e1bbc1c..cf6c6d2e9a275c 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.State.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.State.cs @@ -14,17 +14,6 @@ namespace System.IO.Hashing { public sealed partial class XxHash64 { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ulong Avalanche(ulong hash) - { - hash ^= hash >> 33; - hash *= Prime64_2; - hash ^= hash >> 29; - hash *= Prime64_3; - hash ^= hash >> 32; - return hash; - } - private struct State { private ulong _acc1; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index cea0562929595a..d4eee49d31b81a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1531,6 +1531,12 @@ Common\System\Threading\Tasks\TaskToAsyncResult.cs + + Common\System\IO\Hashing\XxHash3.Common.cs + + + Common\System\IO\Hashing\XxHashShared.cs + diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs index f9261106b34c73..a101373d18f9ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs @@ -827,36 +827,23 @@ internal static int GetHashCodeOrdinalIgnoreCase(ReadOnlySpan value) // restructure the comparison so that for odd-length spans, we simulate the null terminator and include // it in the hash computation exactly as does str.GetNonRandomizedHashCode(). - internal unsafe int GetNonRandomizedHashCode() + internal int GetNonRandomizedHashCode() { - fixed (char* src = &_firstChar) - { - Debug.Assert(src[Length] == '\0', "src[Length] == '\\0'"); - Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary"); - - uint hash1 = (5381 << 16) + 5381; - uint hash2 = hash1; - - uint* ptr = (uint*)src; - int length = Length; - - while (length > 2) - { - length -= 4; - hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ ptr[0]; - hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ptr[1]; - ptr += 2; - } - - if (length > 0) - { - hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ptr[0]; - } + return GetNonRandomizedHashCode(new ReadOnlySpan(ref _firstChar, _stringLength)); + } - return (int)(hash1 + (hash2 * 1566083941)); + // Use XxHash3 on platforms that accelerate it, such as AMD64 and ARM64. +#if !MONO && (TARGET_AMD64 || TARGET_ARM64) + [MethodImpl(MethodImplOptions.NoInlining)] + internal static unsafe int GetNonRandomizedHashCode(ReadOnlySpan span) + { + fixed (char* src = &MemoryMarshal.GetReference(span)) + { + uint byteLength = (uint)span.Length * 2; // never overflows + return System.IO.Hashing.XxHash3.NonRandomizedHashToInt32((byte*)src, byteLength); } } - +#else internal static unsafe int GetNonRandomizedHashCode(ReadOnlySpan span) { uint hash1 = (5381 << 16) + 5381; @@ -913,6 +900,7 @@ internal static unsafe int GetNonRandomizedHashCode(ReadOnlySpan span) return (int)(hash1 + (hash2 * 1_566_083_941)); } +#endif // We "normalize to lowercase" every char by ORing with 0x0020. This casts // a very wide net because it will change, e.g., '^' to '~'. But that should