XxHash128
Provides an implementation of the XXH128 hash algorithm for generating a 128-bit hash.
using System.Buffers.Binary;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.IO.Hashing
{
public sealed class XxHash128 : NonCryptographicHashAlgorithm
{
[DebuggerDisplay("Low64 = {Low64}, High64 = {High64}")]
private readonly struct Hash128
{
public readonly ulong Low64;
public readonly ulong High64;
public Hash128(ulong low64, ulong high64)
{
Low64 = low64;
High64 = high64;
}
}
private new const int HashLengthInBytes = 16;
private XxHashShared.State _state;
public XxHash128()
: this(0)
{
}
public XxHash128(long seed)
: base(16)
{
XxHashShared.Initialize(ref _state, (ulong)seed);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public static byte[] Hash(byte[] source)
{
return Hash(source, 0);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public static byte[] Hash(byte[] source, long seed)
{
if (source == null)
throw new ArgumentNullException("source");
return Hash(new ReadOnlySpan<byte>(source), seed);
}
[return: System.Runtime.CompilerServices.Nullable(1)]
public static byte[] Hash(ReadOnlySpan<byte> source, long seed = 0)
{
byte[] array = new byte[16];
Hash(source, array, seed);
return array;
}
public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination, long seed = 0)
{
if (!TryHash(source, destination, out int bytesWritten, seed))
NonCryptographicHashAlgorithm.ThrowDestinationTooShort();
return bytesWritten;
}
public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, long seed = 0)
{
if (destination.Length >= 16) {
Hash128 hash = HashToHash128(source, seed);
WriteBigEndian128(ref hash, destination);
bytesWritten = 16;
return true;
}
bytesWritten = 0;
return false;
}
private unsafe static Hash128 HashToHash128(ReadOnlySpan<byte> source, long seed = 0)
{
uint length = (uint)source.Length;
fixed (byte* source2 = &MemoryMarshal.GetReference(source)) {
if (length <= 16)
return HashLength0To16(source2, length, (ulong)seed);
if (length <= 128)
return HashLength17To128(source2, length, (ulong)seed);
if (length <= 240)
return HashLength129To240(source2, length, (ulong)seed);
return HashLengthOver240(source2, length, (ulong)seed);
}
}
public override void Reset()
{
XxHashShared.Reset(ref _state);
}
public override void Append(ReadOnlySpan<byte> source)
{
XxHashShared.Append(ref _state, source);
}
protected override void GetCurrentHashCore(Span<byte> destination)
{
Hash128 hash = GetCurrentHashAsHash128();
WriteBigEndian128(ref hash, destination);
}
private unsafe Hash128 GetCurrentHashAsHash128()
{
if (_state.TotalLength <= 240) {
fixed (byte* pointer = _state.Buffer) {
return HashToHash128(new ReadOnlySpan<byte>(pointer, (int)_state.TotalLength), (long)_state.Seed);
}
}
ulong* accumulators = stackalloc ulong[8];
XxHashShared.CopyAccumulators(ref _state, accumulators);
Hash128 result;
fixed (byte* ptr = _state.Secret) {
XxHashShared.DigestLong(ref _state, accumulators, ptr);
result = new Hash128(XxHashShared.MergeAccumulators(accumulators, ptr + 11, (ulong)((long)_state.TotalLength * -7046029288634856825)), XxHashShared.MergeAccumulators(accumulators, ptr + 192 - 64 - 11, (ulong)(~((long)_state.TotalLength * -4417276706812531889))));
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteBigEndian128([In] [System.Runtime.CompilerServices.IsReadOnly] ref Hash128 hash, Span<byte> destination)
{
ulong value = hash.Low64;
ulong value2 = hash.High64;
if (BitConverter.IsLittleEndian) {
value = BinaryPrimitives.ReverseEndianness(value);
value2 = BinaryPrimitives.ReverseEndianness(value2);
}
ref byte reference = ref MemoryMarshal.GetReference(destination);
Unsafe.WriteUnaligned(ref reference, value2);
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref reference, new IntPtr(8)), value);
}
private unsafe static Hash128 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);
return new Hash128(XxHash64.Avalanche(seed ^ 7507096552062056628), XxHash64.Avalanche((ulong)((long)seed ^ -7613947547284439735)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static Hash128 HashLength1To3(byte* source, uint length, ulong seed)
{
byte num = *source;
byte b = source[length >> 1];
byte b2 = source[length - 1];
int num2 = (num << 16) | (b << 24) | b2 | (int)(length << 8);
uint num3 = System.Numerics.BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness((uint)num2), 13);
ulong num4 = 2267503259 + seed;
ulong num5 = 808198283 - seed;
ulong hash = (uint)num2 ^ num4;
ulong hash2 = num3 ^ num5;
return new Hash128(XxHash64.Avalanche(hash), XxHash64.Avalanche(hash2));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static Hash128 HashLength4To8(byte* source, uint length, ulong seed)
{
seed ^= (ulong)BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
uint num = XxHashShared.ReadUInt32LE(source);
uint num2 = XxHashShared.ReadUInt32LE(source + length - 4);
ulong num3 = num + ((ulong)num2 << 32);
ulong num4 = (ulong)(-4255862940314790740 + (long)seed);
ulong num5 = XxHashShared.Multiply64To128(num3 ^ num4, (ulong)(-7046029288634856825 + (length << 2)), out ulong lower);
num5 += lower << 1;
lower ^= num5 >> 3;
lower = XxHashShared.XorShift(lower, 35);
lower = (ulong)((long)lower * -6939452855193903323);
lower = XxHashShared.XorShift(lower, 28);
num5 = XxHashShared.Avalanche(num5);
return new Hash128(lower, num5);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static Hash128 HashLength9To16(byte* source, uint length, ulong seed)
{
ulong num = 6455697860950631241 - seed;
ulong num2 = (ulong)(-4466874330221494952 + (long)seed);
ulong num3 = XxHashShared.ReadUInt64LE(source);
ulong num4 = XxHashShared.ReadUInt64LE(source + length - 8);
ulong num5 = XxHashShared.Multiply64To128(num3 ^ num4 ^ num, 11400714785074694791, out ulong lower);
lower += (ulong)(length - 1) << 54;
num4 ^= num2;
num5 = (ulong)((long)num5 + ((sizeof(void*) < 8) ? (((long)num4 & -4294967296) + (long)XxHashShared.Multiply32To64((uint)num4, 2246822519)) : ((long)(num4 + XxHashShared.Multiply32To64((uint)num4, 2246822518)))));
lower ^= BinaryPrimitives.ReverseEndianness(num5);
ulong num6 = XxHashShared.Multiply64To128(lower, 14029467366897019727, out ulong lower2);
num6 = (ulong)((long)num6 + (long)num5 * -4417276706812531889);
lower2 = XxHashShared.Avalanche(lower2);
num6 = XxHashShared.Avalanche(num6);
return new Hash128(lower2, num6);
}
private unsafe static Hash128 HashLength17To128(byte* source, uint length, ulong seed)
{
ulong accLow = (ulong)(length * -7046029288634856825);
ulong accHigh = 0;
switch ((length - 1) / 32) {
default:
Mix32Bytes(ref accLow, ref accHigh, source + 48, source + length - 64, 4554437623014685352, 2111919702937427193, 3556072174620004746, 7238261902898274248, seed);
goto case 2;
case 2:
Mix32Bytes(ref accLow, ref accHigh, source + 32, source + length - 48, 14627906620379768892, 11758427054878871688, 5690594596133299313, 15613098826807580984, seed);
goto case 1;
case 1:
Mix32Bytes(ref accLow, ref accHigh, source + 16, source + length - 32, 8711581037947681227, 2410270004345854594, 10242386182634080440, 5487137525590930912, seed);
break;
case 0:
break;
}
Mix32Bytes(ref accLow, ref accHigh, source, source + length - 16, 13712233961653862072, 2066345149520216444, 15823274712020931806, 2262974939099578482, seed);
return AvalancheHash(accLow, accHigh, length, seed);
}
private unsafe static Hash128 HashLength129To240(byte* source, uint length, ulong seed)
{
ulong accLow = (ulong)(length * -7046029288634856825);
ulong accHigh = 0;
Mix32Bytes(ref accLow, ref accHigh, source, source + 16, 13712233961653862072, 2066345149520216444, 15823274712020931806, 2262974939099578482, seed);
Mix32Bytes(ref accLow, ref accHigh, source + 32, source + 32 + 16, 8711581037947681227, 2410270004345854594, 10242386182634080440, 5487137525590930912, seed);
Mix32Bytes(ref accLow, ref accHigh, source + 64, source + 64 + 16, 14627906620379768892, 11758427054878871688, 5690594596133299313, 15613098826807580984, seed);
Mix32Bytes(ref accLow, ref accHigh, source + 96, source + 96 + 16, 4554437623014685352, 2111919702937427193, 3556072174620004746, 7238261902898274248, seed);
accLow = XxHashShared.Avalanche(accLow);
accHigh = XxHashShared.Avalanche(accHigh);
uint num = (length - 128) / 32;
if (num != 0) {
Mix32Bytes(ref accLow, ref accHigh, source + 128, source + 128 + 16, 9295848262624092985, 7914194659941938988, 11835586108195898345, 16607528436649670564, seed);
if (num >= 2) {
Mix32Bytes(ref accLow, ref accHigh, source + 160, source + 160 + 16, 15013455763555273806, 5046485836271438973, 10391458616325699444, 5920048007935066598, seed);
if (num == 3)
Mix32Bytes(ref accLow, ref accHigh, source + 192, source + 192 + 16, 7336514198459093435, 5216419214072683403, 17228863761319568023, 8573350489219836230, seed);
}
}
Mix32Bytes(ref accLow, ref accHigh, source + length - 16, source + length - 32, 5695865814404364607, 6464017090953185821, 8320639771003045937, 16992983559143025252, 0 - seed);
return AvalancheHash(accLow, accHigh, length, seed);
}
private unsafe static Hash128 HashLengthOver240(byte* source, uint length, ulong seed)
{
fixed (byte* ptr = &MemoryMarshal.GetReference(XxHashShared.DefaultSecret)) {
byte* ptr2 = ptr;
if (seed != 0) {
byte* intPtr = stackalloc byte[192];
XxHashShared.DeriveSecretFromSeed(intPtr, seed);
ptr2 = intPtr;
}
ulong* accumulators = stackalloc ulong[8];
XxHashShared.InitializeAccumulators(accumulators);
XxHashShared.HashInternalLoop(accumulators, source, length, ptr2);
return new Hash128(XxHashShared.MergeAccumulators(accumulators, ptr2 + 11, (ulong)(length * -7046029288634856825)), XxHashShared.MergeAccumulators(accumulators, ptr2 + 192 - 64 - 11, (ulong)(~(length * -4417276706812531889))));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Hash128 AvalancheHash(ulong accLow, ulong accHigh, uint length, ulong seed)
{
ulong hash = accLow + accHigh;
ulong hash2 = (ulong)((long)accLow * -7046029288634856825 + (long)accHigh * -8796714831421723037 + (long)(length - seed) * -4417276706812531889);
ulong low = XxHashShared.Avalanche(hash);
hash2 = 0 - XxHashShared.Avalanche(hash2);
return new Hash128(low, hash2);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static void Mix32Bytes(ref ulong accLow, ref ulong accHigh, byte* input1, byte* input2, ulong secret1, ulong secret2, ulong secret3, ulong secret4, ulong seed)
{
accLow += XxHashShared.Mix16Bytes(input1, secret1, secret2, seed);
accLow ^= XxHashShared.ReadUInt64LE(input2) + XxHashShared.ReadUInt64LE(input2 + 8);
accHigh += XxHashShared.Mix16Bytes(input2, secret3, secret4, seed);
accHigh ^= XxHashShared.ReadUInt64LE(input1) + XxHashShared.ReadUInt64LE(input1 + 8);
}
}
}