/ src / Ryujinx.Common / XXHash128.cs
XXHash128.cs
  1  using System;
  2  using System.Buffers.Binary;
  3  using System.Diagnostics;
  4  using System.Numerics;
  5  using System.Runtime.CompilerServices;
  6  using System.Runtime.Intrinsics;
  7  using System.Runtime.Intrinsics.X86;
  8  
  9  namespace Ryujinx.Common
 10  {
 11      public static class XXHash128
 12      {
 13          private const int StripeLen = 64;
 14          private const int AccNb = StripeLen / sizeof(ulong);
 15          private const int SecretConsumeRate = 8;
 16          private const int SecretLastAccStart = 7;
 17          private const int SecretMergeAccsStart = 11;
 18          private const int SecretSizeMin = 136;
 19          private const int MidSizeStartOffset = 3;
 20          private const int MidSizeLastOffset = 17;
 21  
 22          private const uint Prime32_1 = 0x9E3779B1U;
 23          private const uint Prime32_2 = 0x85EBCA77U;
 24          private const uint Prime32_3 = 0xC2B2AE3DU;
 25          private const uint Prime32_4 = 0x27D4EB2FU;
 26          private const uint Prime32_5 = 0x165667B1U;
 27  
 28          private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
 29          private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
 30          private const ulong Prime64_3 = 0x165667B19E3779F9UL;
 31          private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
 32          private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
 33  
 34          private static readonly ulong[] _xxh3InitAcc = {
 35              Prime32_3,
 36              Prime64_1,
 37              Prime64_2,
 38              Prime64_3,
 39              Prime64_4,
 40              Prime32_2,
 41              Prime64_5,
 42              Prime32_1,
 43          };
 44  
 45          private static ReadOnlySpan<byte> Xxh3KSecret => new byte[]
 46          {
 47              0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
 48              0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
 49              0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
 50              0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
 51              0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
 52              0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
 53              0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
 54              0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
 55              0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
 56              0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
 57              0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
 58              0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
 59          };
 60  
 61          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 62          private static ulong Mult32To64(ulong x, ulong y)
 63          {
 64              return (uint)x * (ulong)(uint)y;
 65          }
 66  
 67          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 68          private static Hash128 Mult64To128(ulong lhs, ulong rhs)
 69          {
 70              ulong high = Math.BigMul(lhs, rhs, out ulong low);
 71  
 72              return new Hash128
 73              {
 74                  Low = low,
 75                  High = high,
 76              };
 77          }
 78  
 79          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 80          private static ulong Mul128Fold64(ulong lhs, ulong rhs)
 81          {
 82              Hash128 product = Mult64To128(lhs, rhs);
 83  
 84              return product.Low ^ product.High;
 85          }
 86  
 87          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88          private static ulong XorShift64(ulong v64, int shift)
 89          {
 90              Debug.Assert(0 <= shift && shift < 64);
 91  
 92              return v64 ^ (v64 >> shift);
 93          }
 94  
 95          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 96          private static ulong Xxh3Avalanche(ulong h64)
 97          {
 98              h64 = XorShift64(h64, 37);
 99              h64 *= 0x165667919E3779F9UL;
100              h64 = XorShift64(h64, 32);
101  
102              return h64;
103          }
104  
105          [MethodImpl(MethodImplOptions.AggressiveInlining)]
106          private static ulong Xxh64Avalanche(ulong h64)
107          {
108              h64 ^= h64 >> 33;
109              h64 *= Prime64_2;
110              h64 ^= h64 >> 29;
111              h64 *= Prime64_3;
112              h64 ^= h64 >> 32;
113  
114              return h64;
115          }
116  
117          [MethodImpl(MethodImplOptions.AggressiveInlining)]
118          private unsafe static void Xxh3Accumulate512(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
119          {
120              if (Avx2.IsSupported)
121              {
122                  fixed (ulong* pAcc = acc)
123                  {
124                      fixed (byte* pInput = input, pSecret = secret)
125                      {
126                          Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
127                          Vector256<byte>* xInput = (Vector256<byte>*)pInput;
128                          Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
129  
130                          for (ulong i = 0; i < StripeLen / 32; i++)
131                          {
132                              Vector256<byte> dataVec = xInput[i];
133                              Vector256<byte> keyVec = xSecret[i];
134                              Vector256<byte> dataKey = Avx2.Xor(dataVec, keyVec);
135                              Vector256<uint> dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
136                              Vector256<ulong> product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
137                              Vector256<uint> dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
138                              Vector256<ulong> sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
139                              xAcc[i] = Avx2.Add(product, sum);
140                          }
141                      }
142                  }
143              }
144              else if (Sse2.IsSupported)
145              {
146                  fixed (ulong* pAcc = acc)
147                  {
148                      fixed (byte* pInput = input, pSecret = secret)
149                      {
150                          Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
151                          Vector128<byte>* xInput = (Vector128<byte>*)pInput;
152                          Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
153  
154                          for (ulong i = 0; i < StripeLen / 16; i++)
155                          {
156                              Vector128<byte> dataVec = xInput[i];
157                              Vector128<byte> keyVec = xSecret[i];
158                              Vector128<byte> dataKey = Sse2.Xor(dataVec, keyVec);
159                              Vector128<uint> dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
160                              Vector128<ulong> product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
161                              Vector128<uint> dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
162                              Vector128<ulong> sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
163                              xAcc[i] = Sse2.Add(product, sum);
164                          }
165                      }
166                  }
167              }
168              else
169              {
170                  for (int i = 0; i < AccNb; i++)
171                  {
172                      ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]);
173                      ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
174                      acc[i ^ 1] += dataVal;
175                      acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
176                  }
177              }
178          }
179  
180          [MethodImpl(MethodImplOptions.AggressiveInlining)]
181          private unsafe static void Xxh3ScrambleAcc(Span<ulong> acc, ReadOnlySpan<byte> secret)
182          {
183              if (Avx2.IsSupported)
184              {
185                  fixed (ulong* pAcc = acc)
186                  {
187                      fixed (byte* pSecret = secret)
188                      {
189                          Vector256<uint> prime32 = Vector256.Create(Prime32_1);
190                          Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
191                          Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
192  
193                          for (ulong i = 0; i < StripeLen / 32; i++)
194                          {
195                              Vector256<ulong> accVec = xAcc[i];
196                              Vector256<ulong> shifted = Avx2.ShiftRightLogical(accVec, 47);
197                              Vector256<ulong> dataVec = Avx2.Xor(accVec, shifted);
198  
199                              Vector256<byte> keyVec = xSecret[i];
200                              Vector256<uint> dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
201  
202                              Vector256<uint> dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
203                              Vector256<ulong> prodLo = Avx2.Multiply(dataKey, prime32);
204                              Vector256<ulong> prodHi = Avx2.Multiply(dataKeyHi, prime32);
205  
206                              xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
207                          }
208                      }
209                  }
210              }
211              else if (Sse2.IsSupported)
212              {
213                  fixed (ulong* pAcc = acc)
214                  {
215                      fixed (byte* pSecret = secret)
216                      {
217                          Vector128<uint> prime32 = Vector128.Create(Prime32_1);
218                          Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
219                          Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
220  
221                          for (ulong i = 0; i < StripeLen / 16; i++)
222                          {
223                              Vector128<ulong> accVec = xAcc[i];
224                              Vector128<ulong> shifted = Sse2.ShiftRightLogical(accVec, 47);
225                              Vector128<ulong> dataVec = Sse2.Xor(accVec, shifted);
226  
227                              Vector128<byte> keyVec = xSecret[i];
228                              Vector128<uint> dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
229  
230                              Vector128<uint> dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
231                              Vector128<ulong> prodLo = Sse2.Multiply(dataKey, prime32);
232                              Vector128<ulong> prodHi = Sse2.Multiply(dataKeyHi, prime32);
233  
234                              xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
235                          }
236                      }
237                  }
238              }
239              else
240              {
241                  for (int i = 0; i < AccNb; i++)
242                  {
243                      ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
244                      ulong acc64 = acc[i];
245                      acc64 = XorShift64(acc64, 47);
246                      acc64 ^= key64;
247                      acc64 *= Prime32_1;
248                      acc[i] = acc64;
249                  }
250              }
251          }
252  
253          [MethodImpl(MethodImplOptions.AggressiveInlining)]
254          private static void Xxh3Accumulate(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, int nbStripes)
255          {
256              for (int n = 0; n < nbStripes; n++)
257              {
258                  ReadOnlySpan<byte> inData = input[(n * StripeLen)..];
259                  Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]);
260              }
261          }
262  
263          private static void Xxh3HashLongInternalLoop(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
264          {
265              int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
266              int blockLen = StripeLen * nbStripesPerBlock;
267              int nbBlocks = (input.Length - 1) / blockLen;
268  
269              Debug.Assert(secret.Length >= SecretSizeMin);
270  
271              for (int n = 0; n < nbBlocks; n++)
272              {
273                  Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock);
274                  Xxh3ScrambleAcc(acc, secret[^StripeLen..]);
275              }
276  
277              Debug.Assert(input.Length > StripeLen);
278  
279              int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
280              Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
281              Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes);
282  
283              ReadOnlySpan<byte> p = input[^StripeLen..];
284              Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]);
285          }
286  
287          [MethodImpl(MethodImplOptions.AggressiveInlining)]
288          private static ulong Xxh3Mix2Accs(Span<ulong> acc, ReadOnlySpan<byte> secret)
289          {
290              return Mul128Fold64(
291                  acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
292                  acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]));
293          }
294  
295          [MethodImpl(MethodImplOptions.AggressiveInlining)]
296          private static ulong Xxh3MergeAccs(Span<ulong> acc, ReadOnlySpan<byte> secret, ulong start)
297          {
298              ulong result64 = start;
299  
300              for (int i = 0; i < 4; i++)
301              {
302                  result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]);
303              }
304  
305              return Xxh3Avalanche(result64);
306          }
307  
308          [SkipLocalsInit]
309          private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
310          {
311              Span<ulong> acc = stackalloc ulong[AccNb];
312              _xxh3InitAcc.CopyTo(acc);
313  
314              Xxh3HashLongInternalLoop(acc, input, secret);
315  
316              Debug.Assert(acc.Length == 8);
317              Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
318  
319              return new Hash128
320              {
321                  Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1),
322                  High = Xxh3MergeAccs(
323                      acc,
324                      secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..],
325                      ~((ulong)input.Length * Prime64_2)),
326              };
327          }
328  
329          private static Hash128 Xxh3Len1To3128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
330          {
331              Debug.Assert(1 <= input.Length && input.Length <= 3);
332  
333              byte c1 = input[0];
334              byte c2 = input[input.Length >> 1];
335              byte c3 = input[^1];
336  
337              uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
338              uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
339              ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed;
340              ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed;
341              ulong keyedLo = combinedL ^ bitFlipL;
342              ulong keyedHi = combinedH ^ bitFlipH;
343  
344              return new Hash128
345              {
346                  Low = Xxh64Avalanche(keyedLo),
347                  High = Xxh64Avalanche(keyedHi),
348              };
349          }
350  
351          private static Hash128 Xxh3Len4To8128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
352          {
353              Debug.Assert(4 <= input.Length && input.Length <= 8);
354  
355              seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
356  
357              uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
358              uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]);
359              ulong input64 = inputLo + ((ulong)inputHi << 32);
360              ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed;
361              ulong keyed = input64 ^ bitFlip;
362  
363              Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
364  
365              m128.High += m128.Low << 1;
366              m128.Low ^= m128.High >> 3;
367  
368              m128.Low = XorShift64(m128.Low, 35);
369              m128.Low *= 0x9FB21C651E98DF25UL;
370              m128.Low = XorShift64(m128.Low, 28);
371              m128.High = Xxh3Avalanche(m128.High);
372  
373              return m128;
374          }
375  
376          private static Hash128 Xxh3Len9To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
377          {
378              Debug.Assert(9 <= input.Length && input.Length <= 16);
379  
380              ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed;
381              ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed;
382              ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
383              ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]);
384  
385              Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
386              m128.Low += ((ulong)input.Length - 1) << 54;
387              inputHi ^= bitFlipH;
388              m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
389              m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
390  
391              Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
392              h128.High += m128.High * Prime64_2;
393              h128.Low = Xxh3Avalanche(h128.Low);
394              h128.High = Xxh3Avalanche(h128.High);
395  
396              return h128;
397          }
398  
399          private static Hash128 Xxh3Len0To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
400          {
401              Debug.Assert(input.Length <= 16);
402  
403              if (input.Length > 8)
404              {
405                  return Xxh3Len9To16128b(input, secret, seed);
406              }
407  
408              if (input.Length >= 4)
409              {
410                  return Xxh3Len4To8128b(input, secret, seed);
411              }
412  
413              if (input.Length != 0)
414              {
415                  return Xxh3Len1To3128b(input, secret, seed);
416              }
417  
418              Hash128 h128 = new();
419              ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]);
420              ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]);
421              h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
422              h128.High = Xxh64Avalanche(seed ^ bitFlipH);
423  
424              return h128;
425          }
426  
427          private static ulong Xxh3Mix16b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
428          {
429              ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
430              ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
431  
432              return Mul128Fold64(
433                  inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
434                  inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed));
435          }
436  
437          private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> input2, ReadOnlySpan<byte> secret, ulong seed)
438          {
439              acc.Low += Xxh3Mix16b(input, secret, seed);
440              acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]);
441              acc.High += Xxh3Mix16b(input2, secret[16..], seed);
442              acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
443  
444              return acc;
445          }
446  
447          private static Hash128 Xxh3Len17To128128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
448          {
449              Debug.Assert(secret.Length >= SecretSizeMin);
450              Debug.Assert(16 < input.Length && input.Length <= 128);
451  
452              Hash128 acc = new()
453              {
454                  Low = (ulong)input.Length * Prime64_1,
455                  High = 0,
456              };
457  
458              if (input.Length > 32)
459              {
460                  if (input.Length > 64)
461                  {
462                      if (input.Length > 96)
463                      {
464                          acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed);
465                      }
466                      acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed);
467                  }
468                  acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed);
469              }
470              acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed);
471  
472              Hash128 h128 = new()
473              {
474                  Low = acc.Low + acc.High,
475                  High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
476              };
477              h128.Low = Xxh3Avalanche(h128.Low);
478              h128.High = 0UL - Xxh3Avalanche(h128.High);
479  
480              return h128;
481          }
482  
483          private static Hash128 Xxh3Len129To240128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
484          {
485              Debug.Assert(secret.Length >= SecretSizeMin);
486              Debug.Assert(128 < input.Length && input.Length <= 240);
487  
488              Hash128 acc = new();
489  
490              int nbRounds = input.Length / 32;
491              acc.Low = (ulong)input.Length * Prime64_1;
492              acc.High = 0;
493  
494              for (int i = 0; i < 4; i++)
495              {
496                  acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed);
497              }
498  
499              acc.Low = Xxh3Avalanche(acc.Low);
500              acc.High = Xxh3Avalanche(acc.High);
501              Debug.Assert(nbRounds >= 4);
502  
503              for (int i = 4; i < nbRounds; i++)
504              {
505                  acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed);
506              }
507  
508              acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed);
509  
510              Hash128 h128 = new()
511              {
512                  Low = acc.Low + acc.High,
513                  High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
514              };
515              h128.Low = Xxh3Avalanche(h128.Low);
516              h128.High = 0UL - Xxh3Avalanche(h128.High);
517  
518              return h128;
519          }
520  
521          private static Hash128 Xxh3128bitsInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
522          {
523              Debug.Assert(secret.Length >= SecretSizeMin);
524  
525              if (input.Length <= 16)
526              {
527                  return Xxh3Len0To16128b(input, secret, seed);
528              }
529  
530              if (input.Length <= 128)
531              {
532                  return Xxh3Len17To128128b(input, secret, seed);
533              }
534  
535              if (input.Length <= 240)
536              {
537                  return Xxh3Len129To240128b(input, secret, seed);
538              }
539  
540              return Xxh3HashLong128bInternal(input, secret);
541          }
542  
543          public static Hash128 ComputeHash(ReadOnlySpan<byte> input)
544          {
545              return Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
546          }
547      }
548  }