No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

xxHash3.cs 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. using System.Runtime.CompilerServices;
  2. using Unity.Burst;
  3. #if !NET_DOTS
  4. using Unity.Burst.Intrinsics;
  5. #endif
  6. using Unity.Collections.LowLevel.Unsafe;
  7. using Unity.Mathematics;
  8. using UnityEngine;
  9. #if UNITY_EDITOR
  10. using UnityEditor;
  11. #endif
  12. namespace Unity.Collections
  13. {
  14. /// <summary>
  15. /// A feature complete hashing API based on xxHash3 (https://github.com/Cyan4973/xxHash)
  16. /// </summary>
  17. /// <remarks>
  18. /// Features:
  19. /// - Compute 64bits or 128bits hash keys, based on a private key, with an optional given seed value.
  20. /// - Hash on buffer (with or without a ulong based seed value)
  21. /// - Hash on buffer while copying the data to a destination
  22. /// - Use instances of <see cref="xxHash3.StreamingState"/> to accumulate data to hash in multiple calls, suited for small data, then retrieve the hash key at the end.
  23. /// - xxHash3 has several implementation based on the size to hash to ensure best performances
  24. /// - We currently have two implementations:
  25. /// - A generic one based on Unity.Mathematics, that should always be executed compiled with Burst.
  26. /// - An AVX2 based implementation for platforms supporting it, using Burst intrinsics.
  27. /// - Whether or not the call site is compiled with burst, the hashing function will be executed by Burst(*) to ensure optimal performance.
  28. /// (*) Only when the hashing size justifies such transition.
  29. /// </remarks>
  30. [BurstCompile]
  31. [BurstCompatible]
  32. public static partial class xxHash3
  33. {
  34. #region Public API
  35. /// <summary>
  36. /// Compute a 64bits hash of a memory region
  37. /// </summary>
  38. /// <param name="input">The memory buffer, can't be null</param>
  39. /// <param name="length">The length of the memory buffer, can be zero</param>
  40. /// <returns>The hash result</returns>
  41. public static unsafe uint2 Hash64(void* input, long length)
  42. {
  43. fixed (void* secret = xxHashDefaultKey.kSecret)
  44. {
  45. return ToUint2(Hash64Internal((byte*) input, null, length, (byte*) secret, 0));
  46. }
  47. }
  48. /// <summary>
  49. /// Compute a 64bits hash from the contents of the input struct
  50. /// </summary>
  51. /// <typeparam name="T">The input type.</typeparam>
  52. /// <param name="input">The input struct that will be hashed</param>
  53. /// <returns>The hash result</returns>
  54. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  55. public static unsafe uint2 Hash64<T>(in T input) where T : unmanaged
  56. {
  57. return Hash64(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>());
  58. }
  59. /// <summary>
  60. /// Compute a 64bits hash of a memory region using a given seed value
  61. /// </summary>
  62. /// <param name="input">The memory buffer, can't be null</param>
  63. /// <param name="length">The length of the memory buffer, can be zero</param>
  64. /// <param name="seed">The seed value to alter the hash computation from</param>
  65. /// <returns>The hash result</returns>
  66. public static unsafe uint2 Hash64(void* input, long length, ulong seed)
  67. {
  68. fixed (byte* secret = xxHashDefaultKey.kSecret)
  69. {
  70. return ToUint2(Hash64Internal((byte*) input, null, length, secret, seed));
  71. }
  72. }
  73. /// <summary>
  74. /// Compute a 128bits hash of a memory region
  75. /// </summary>
  76. /// <param name="input">The memory buffer, can't be null</param>
  77. /// <param name="length">The length of the memory buffer, can be zero</param>
  78. /// <returns>The hash result</returns>
  79. public static unsafe uint4 Hash128(void* input, long length)
  80. {
  81. fixed (void* secret = xxHashDefaultKey.kSecret)
  82. {
  83. Hash128Internal((byte*) input, null, length, (byte*) secret, 0, out var result);
  84. return result;
  85. }
  86. }
  87. /// <summary>
  88. /// Compute a 128bits hash from the contents of the input struct
  89. /// </summary>
  90. /// <typeparam name="T">The input type.</typeparam>
  91. /// <param name="input">The input struct that will be hashed</param>
  92. /// <returns>The hash result</returns>
  93. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
  94. public static unsafe uint4 Hash128<T>(in T input) where T : unmanaged
  95. {
  96. return Hash128(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>());
  97. }
  98. /// <summary>
  99. /// Compute a 128bits hash while copying the data to a destination buffer
  100. /// </summary>
  101. /// <param name="input">The memory buffer to compute the hash and copy from, can't be null</param>
  102. /// <param name="destination">The destination buffer, can't be null and must be at least big enough to match the input's length</param>
  103. /// <param name="length">The length of the memory buffer, can be zero</param>
  104. /// <returns>The hash result</returns>
  105. /// <remarks>Use this API to avoid a double memory scan in situations where the hash as to be compute and the data copied at the same time. Performances improvements vary between 30-50% on
  106. /// big data.</remarks>
  107. public static unsafe uint4 Hash128(void* input, void* destination, long length)
  108. {
  109. fixed (byte* secret = xxHashDefaultKey.kSecret)
  110. {
  111. Hash128Internal((byte*) input, (byte*) destination, length, secret, 0, out var result);
  112. return result;
  113. }
  114. }
  115. /// <summary>
  116. /// Compute a 128bits hash of a memory region using a given seed value
  117. /// </summary>
  118. /// <param name="input">The memory buffer, can't be null</param>
  119. /// <param name="length">The length of the memory buffer, can be zero</param>
  120. /// <param name="seed">The seed value to alter the hash computation from</param>
  121. /// <returns>The hash result</returns>
  122. public static unsafe uint4 Hash128(void* input, long length, ulong seed)
  123. {
  124. fixed (byte* secret = xxHashDefaultKey.kSecret)
  125. {
  126. Hash128Internal((byte*) input, null, length, secret, seed, out var result);
  127. return result;
  128. }
  129. }
  130. /// <summary>
  131. /// Compute a 128bits hash while copying the data to a destination buffer using a given seed value
  132. /// </summary>
  133. /// <param name="input">The memory buffer to compute the hash and copy from, can't be null</param>
  134. /// <param name="destination">The destination buffer, can't be null and must be at least big enough to match the input's length</param>
  135. /// <param name="length">The length of the memory buffer, can be zero</param>
  136. /// <param name="seed">The seed value to alter the hash computation from</param>
  137. /// <returns>The hash result</returns>
  138. public static unsafe uint4 Hash128(void* input, void* destination, long length, ulong seed)
  139. {
  140. fixed (byte* secret = xxHashDefaultKey.kSecret)
  141. {
  142. Hash128Internal((byte*) input, (byte*) destination, length, secret, seed, out var result);
  143. return result;
  144. }
  145. }
  146. #endregion
  147. #region Constants
  148. private const int STRIPE_LEN = 64;
  149. private const int ACC_NB = STRIPE_LEN / 8; // Accumulators are ulong sized
  150. private const int SECRET_CONSUME_RATE = 8;
  151. private const int SECRET_KEY_SIZE = 192;
  152. private const int SECRET_KEY_MIN_SIZE = 136;
  153. private const int SECRET_LASTACC_START = 7;
  154. private const int NB_ROUNDS = (SECRET_KEY_SIZE - STRIPE_LEN) / SECRET_CONSUME_RATE;
  155. private const int BLOCK_LEN = STRIPE_LEN * NB_ROUNDS;
  156. private const uint PRIME32_1 = 0x9E3779B1U;
  157. private const uint PRIME32_2 = 0x85EBCA77U;
  158. private const uint PRIME32_3 = 0xC2B2AE3DU;
  159. // static readonly uint PRIME32_4 = 0x27D4EB2FU;
  160. private const uint PRIME32_5 = 0x165667B1U;
  161. private const ulong PRIME64_1 = 0x9E3779B185EBCA87UL;
  162. private const ulong PRIME64_2 = 0xC2B2AE3D27D4EB4FUL;
  163. private const ulong PRIME64_3 = 0x165667B19E3779F9UL;
  164. private const ulong PRIME64_4 = 0x85EBCA77C2B2AE63UL;
  165. private const ulong PRIME64_5 = 0x27D4EB2F165667C5UL;
  166. private const int MIDSIZE_MAX = 240;
  167. private const int MIDSIZE_STARTOFFSET = 3;
  168. private const int MIDSIZE_LASTOFFSET = 17;
  169. private const int SECRET_MERGEACCS_START = 11;
  170. #endregion
  171. private struct ulong2
  172. {
  173. public ulong x;
  174. public ulong y;
  175. public ulong2(ulong x, ulong y)
  176. {
  177. this.x = x;
  178. this.y = y;
  179. }
  180. }
  181. internal static unsafe ulong Hash64Internal(byte* input, byte* dest, long length, byte* secret, ulong seed)
  182. {
  183. if (length < 16)
  184. {
  185. return Hash64Len0To16(input, length, secret, seed);
  186. }
  187. if (length < 128)
  188. {
  189. return Hash64Len17To128(input, length, secret, seed);
  190. }
  191. if (length < MIDSIZE_MAX)
  192. {
  193. return Hash64Len129To240(input, length, secret, seed);
  194. }
  195. if (seed != 0)
  196. {
  197. var newSecret = (byte*) Memory.Unmanaged.Allocate(SECRET_KEY_SIZE, 64, Allocator.Temp);
  198. EncodeSecretKey(newSecret, secret, seed);
  199. var result = Hash64Long(input, dest, length, newSecret);
  200. Memory.Unmanaged.Free(newSecret, Allocator.Temp);
  201. return result;
  202. }
  203. else
  204. {
  205. return Hash64Long(input, dest, length, secret);
  206. }
  207. }
  208. internal static unsafe void Hash128Internal(byte* input, byte* dest, long length, byte* secret, ulong seed,
  209. out uint4 result)
  210. {
  211. if (dest != null && length < MIDSIZE_MAX)
  212. {
  213. UnsafeUtility.MemCpy(dest, input, length);
  214. }
  215. if (length < 16)
  216. {
  217. Hash128Len0To16(input, length, secret, seed, out result);
  218. return;
  219. }
  220. if (length < 128)
  221. {
  222. Hash128Len17To128(input, length, secret, seed, out result);
  223. return;
  224. }
  225. if (length < MIDSIZE_MAX)
  226. {
  227. Hash128Len129To240(input, length, secret, seed, out result);
  228. return;
  229. }
  230. if (seed != 0)
  231. {
  232. var addr = stackalloc byte[SECRET_KEY_SIZE + 31];
  233. // Aligned the allocated address on 32 bytes
  234. var newSecret = (byte*) ((ulong) addr + 31 & 0xFFFFFFFFFFFFFFE0);
  235. EncodeSecretKey(newSecret, secret, seed);
  236. Hash128Long(input, dest, length, newSecret, out result);
  237. }
  238. else
  239. {
  240. Hash128Long(input, dest, length, secret, out result);
  241. }
  242. }
  243. #region 64-bits hash, size dependent implementations
  244. private static unsafe ulong Hash64Len1To3(byte* input, long len, byte* secret, ulong seed)
  245. {
  246. unchecked
  247. {
  248. var c1 = input[0];
  249. var c2 = input[len >> 1];
  250. var c3 = input[len - 1];
  251. var combined = ((uint)c1 << 16) | ((uint)c2 << 24) | ((uint)c3 << 0) | ((uint)len << 8);
  252. ulong bitflip = (Read32LE(secret) ^ Read32LE(secret+4)) + seed;
  253. ulong keyed = (ulong)combined ^ bitflip;
  254. return AvalancheH64(keyed);
  255. }
  256. }
  257. private static unsafe ulong Hash64Len4To8(byte* input, long length, byte* secret, ulong seed)
  258. {
  259. unchecked
  260. {
  261. seed ^= (ulong)Swap32((uint)seed) << 32;
  262. var input1 = Read32LE(input);
  263. var input2 = Read32LE(input + length - 4);
  264. var bitflip = (Read64LE(secret+8) ^ Read64LE(secret+16)) - seed;
  265. var input64 = input2 + (((ulong)input1) << 32);
  266. var keyed = input64 ^ bitflip;
  267. return rrmxmx(keyed, (ulong)length);
  268. }
  269. }
  270. private static unsafe ulong Hash64Len9To16(byte* input, long length, byte* secret, ulong seed)
  271. {
  272. unchecked
  273. {
  274. var bitflip1 = (Read64LE(secret+24) ^ Read64LE(secret+32)) + seed;
  275. var bitflip2 = (Read64LE(secret+40) ^ Read64LE(secret+48)) - seed;
  276. var input_lo = Read64LE(input) ^ bitflip1;
  277. var input_hi = Read64LE(input + length - 8) ^ bitflip2;
  278. var acc = (ulong)length + Swap64(input_lo) + input_hi + Mul128Fold64(input_lo, input_hi);
  279. return Avalanche(acc);
  280. }
  281. }
  282. private static unsafe ulong Hash64Len0To16(byte* input, long length, byte* secret, ulong seed)
  283. {
  284. if (length > 8)
  285. {
  286. return Hash64Len9To16(input, length, secret, seed);
  287. }
  288. if (length >= 4)
  289. {
  290. return Hash64Len4To8(input, length, secret, seed);
  291. }
  292. if (length > 0)
  293. {
  294. return Hash64Len1To3(input, length, secret, seed);
  295. }
  296. return AvalancheH64(seed ^ (Read64LE(secret+56) ^ Read64LE(secret+64)));
  297. }
  298. private static unsafe ulong Hash64Len17To128(byte* input, long length, byte* secret, ulong seed)
  299. {
  300. unchecked
  301. {
  302. var acc = (ulong) length * PRIME64_1;
  303. if (length > 32)
  304. {
  305. if (length > 64)
  306. {
  307. if (length > 96)
  308. {
  309. acc += Mix16(input + 48, secret + 96, seed);
  310. acc += Mix16(input + length - 64, secret + 112, seed);
  311. }
  312. acc += Mix16(input + 32, secret + 64, seed);
  313. acc += Mix16(input + length - 48, secret + 80, seed);
  314. }
  315. acc += Mix16(input + 16, secret + 32, seed);
  316. acc += Mix16(input + length - 32, secret + 48, seed);
  317. }
  318. acc += Mix16(input + 0, secret + 0, seed);
  319. acc += Mix16(input + length - 16, secret + 16, seed);
  320. return Avalanche(acc);
  321. }
  322. }
  323. private static unsafe ulong Hash64Len129To240(byte* input, long length, byte* secret, ulong seed)
  324. {
  325. unchecked
  326. {
  327. var acc = (ulong) length * PRIME64_1;
  328. var nbRounds = (int) length / 16;
  329. for (var i = 0; i < 8; i++)
  330. {
  331. acc += Mix16(input + (16 * i), secret + (16 * i), seed);
  332. }
  333. acc = Avalanche(acc);
  334. for (var i = 8; i < nbRounds; i++)
  335. {
  336. acc += Mix16(input + (16 * i), secret + (16 * (i - 8)) + MIDSIZE_STARTOFFSET, seed);
  337. }
  338. acc += Mix16(input + length - 16, secret + SECRET_KEY_MIN_SIZE - MIDSIZE_LASTOFFSET, seed);
  339. return Avalanche(acc);
  340. }
  341. }
  342. [BurstCompile]
  343. private static unsafe ulong Hash64Long(byte* input, byte* dest, long length, byte* secret)
  344. {
  345. var addr = stackalloc byte[STRIPE_LEN + 31];
  346. var acc = (ulong*) ((ulong) addr + 31 & 0xFFFFFFFFFFFFFFE0); // Aligned the allocated address on 32 bytes
  347. acc[0] = PRIME32_3;
  348. acc[1] = PRIME64_1;
  349. acc[2] = PRIME64_2;
  350. acc[3] = PRIME64_3;
  351. acc[4] = PRIME64_4;
  352. acc[5] = PRIME32_2;
  353. acc[6] = PRIME64_5;
  354. acc[7] = PRIME32_1;
  355. unchecked
  356. {
  357. #if !NET_DOTS
  358. if (X86.Avx2.IsAvx2Supported)
  359. {
  360. Avx2HashLongInternalLoop(acc, input, dest, length, secret, 1);
  361. }
  362. else
  363. #endif
  364. {
  365. DefaultHashLongInternalLoop(acc, input, dest, length, secret, 1);
  366. }
  367. return MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) length * PRIME64_1);
  368. }
  369. }
  370. #endregion
  371. #region 128-bits hash, size dependent implementations
  372. private static unsafe void Hash128Len1To3(byte* input, long length, byte* secret, ulong seed,
  373. out uint4 result)
  374. {
  375. unchecked
  376. {
  377. var c1 = input[0];
  378. var c2 = input[length >> 1];
  379. var c3 = input[length - 1];
  380. var combinedl = ((uint) c1 << 16) + (((uint) c2) << 24) + (((uint) c3) << 0) + (((uint) length) << 8);
  381. var combinedh = RotL32(Swap32(combinedl), 13);
  382. var bitflipl = (Read32LE(secret) ^ Read32LE(secret+4)) + seed;
  383. var bitfliph = (Read32LE(secret+8) ^ Read32LE(secret+12)) - seed;
  384. var keyed_lo = combinedl ^ bitflipl;
  385. var keyed_hi = combinedh ^ bitfliph;
  386. result = ToUint4(AvalancheH64(keyed_lo), AvalancheH64(keyed_hi));
  387. }
  388. }
  389. private static unsafe void Hash128Len4To8(byte* input, long len, byte* secret, ulong seed,
  390. out uint4 result)
  391. {
  392. unchecked
  393. {
  394. seed ^= (ulong)Swap32((uint)seed) << 32;
  395. var input_lo = Read32LE(input);
  396. var input_hi = Read32LE(input + len - 4);
  397. var input_64 = input_lo + ((ulong)input_hi << 32);
  398. var bitflip = (Read64LE(secret+16) ^ Read64LE(secret+24)) + seed;
  399. var keyed = input_64 ^ bitflip;
  400. var low = Common.umul128(keyed, PRIME64_1 + (ulong)(len << 2), out var high);
  401. high += (low << 1);
  402. low ^= (high >> 3);
  403. low = XorShift64(low, 35);
  404. low*= 0x9FB21C651E98DF25UL;
  405. low = XorShift64(low, 28);
  406. high = Avalanche(high);
  407. result = ToUint4(low, high);
  408. }
  409. }
  410. private static unsafe void Hash128Len9To16(byte* input, long len, byte* secret, ulong seed,
  411. out uint4 result)
  412. {
  413. unchecked
  414. {
  415. var bitflipl = (Read64LE(secret+32) ^ Read64LE(secret+40)) - seed;
  416. var bitfliph = (Read64LE(secret+48) ^ Read64LE(secret+56)) + seed;
  417. var input_lo = Read64LE(input);
  418. var input_hi = Read64LE(input + len - 8);
  419. var low = Common.umul128(input_lo ^ input_hi ^ bitflipl, PRIME64_1, out var high);
  420. low += (ulong)(len - 1) << 54;
  421. input_hi ^= bitfliph;
  422. high += input_hi + Mul32To64((uint)input_hi, PRIME32_2 - 1);
  423. low ^= Swap64(high);
  424. var hlow = Common.umul128(low, PRIME64_2, out var hhigh);
  425. hhigh += high * PRIME64_2;
  426. result = ToUint4(Avalanche(hlow), Avalanche(hhigh));
  427. }
  428. }
  429. private static unsafe void Hash128Len0To16(byte* input, long length, byte* secret, ulong seed,
  430. out uint4 result)
  431. {
  432. if (length > 8)
  433. {
  434. Hash128Len9To16(input, length, secret, seed, out result);
  435. return;
  436. }
  437. if (length >= 4)
  438. {
  439. Hash128Len4To8(input, length, secret, seed, out result);
  440. return;
  441. }
  442. if (length > 0)
  443. {
  444. Hash128Len1To3(input, length, secret, seed, out result);
  445. return;
  446. }
  447. var bitflipl = Read64LE(secret+64) ^ Read64LE(secret+72);
  448. var bitfliph = Read64LE(secret+80) ^ Read64LE(secret+88);
  449. var low = AvalancheH64(seed ^ bitflipl);
  450. var hi = AvalancheH64( seed ^ bitfliph);
  451. result = ToUint4(low, hi);
  452. }
  453. private static unsafe void Hash128Len17To128(byte* input, long length, byte* secret, ulong seed,
  454. out uint4 result)
  455. {
  456. unchecked
  457. {
  458. var acc = new ulong2((ulong) length * PRIME64_1, 0);
  459. if (length > 32)
  460. {
  461. if (length > 64)
  462. {
  463. if (length > 96)
  464. {
  465. acc = Mix32(acc, input + 48, input + length - 64, secret + 96, seed);
  466. }
  467. acc = Mix32(acc, input + 32, input + length - 48, secret + 64, seed);
  468. }
  469. acc = Mix32(acc, input + 16, input + length - 32, secret + 32, seed);
  470. }
  471. acc = Mix32(acc, input, input + length - 16, secret, seed);
  472. var low64 = acc.x + acc.y;
  473. var high64 = acc.x * PRIME64_1 + acc.y * PRIME64_4 + ((ulong) length - seed) * PRIME64_2;
  474. result = ToUint4(Avalanche(low64), 0ul - Avalanche(high64));
  475. }
  476. }
  477. private static unsafe void Hash128Len129To240(byte* input, long length, byte* secret, ulong seed,
  478. out uint4 result)
  479. {
  480. unchecked
  481. {
  482. var acc = new ulong2((ulong) length * PRIME64_1, 0);
  483. var nbRounds = length / 32;
  484. int i;
  485. for (i = 0; i < 4; i++)
  486. {
  487. acc = Mix32(acc, input + 32 * i, input + 32 * i + 16, secret + 32 * i, seed);
  488. }
  489. acc.x = Avalanche(acc.x);
  490. acc.y = Avalanche(acc.y);
  491. for (i = 4; i < nbRounds; i++)
  492. {
  493. acc = Mix32(acc, input + 32 * i, input + 32 * i + 16, secret + MIDSIZE_STARTOFFSET + 32 * (i - 4),
  494. seed);
  495. }
  496. acc = Mix32(acc, input + length - 16, input + length - 32,
  497. secret + SECRET_KEY_MIN_SIZE - MIDSIZE_LASTOFFSET - 16, 0UL - seed);
  498. var low64 = acc.x + acc.y;
  499. var high64 = acc.x * PRIME64_1 + acc.y * PRIME64_4 + ((ulong) length - seed) * PRIME64_2;
  500. result = ToUint4(Avalanche(low64), 0ul - Avalanche(high64));
  501. }
  502. }
  503. [BurstCompile]
  504. private static unsafe void Hash128Long(byte* input, byte* dest, long length, byte* secret, out uint4 result)
  505. {
  506. // var acc = stackalloc ulong[ACC_NB];
  507. var addr = stackalloc byte[STRIPE_LEN + 31];
  508. var acc = (ulong*) ((ulong) addr + 31 & 0xFFFFFFFFFFFFFFE0); // Aligned the allocated address on 32 bytes
  509. acc[0] = PRIME32_3;
  510. acc[1] = PRIME64_1;
  511. acc[2] = PRIME64_2;
  512. acc[3] = PRIME64_3;
  513. acc[4] = PRIME64_4;
  514. acc[5] = PRIME32_2;
  515. acc[6] = PRIME64_5;
  516. acc[7] = PRIME32_1;
  517. unchecked
  518. {
  519. #if !NET_DOTS
  520. if (X86.Avx2.IsAvx2Supported)
  521. {
  522. Avx2HashLongInternalLoop(acc, input, dest, length, secret, 0);
  523. }
  524. else
  525. #endif
  526. {
  527. DefaultHashLongInternalLoop(acc, input, dest, length, secret, 0);
  528. }
  529. var low64 = MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) length * PRIME64_1);
  530. var high64 = MergeAcc(acc, secret + SECRET_KEY_SIZE - 64 - SECRET_MERGEACCS_START,
  531. ~((ulong) length * PRIME64_2));
  532. result = ToUint4(low64, high64);
  533. }
  534. }
  535. #endregion
  536. #region Internal helpers
  537. internal static uint2 ToUint2(ulong u)
  538. {
  539. return new uint2((uint)(u & 0xFFFFFFFF), (uint)(u >> 32));
  540. }
  541. internal static uint4 ToUint4(ulong ul0, ulong ul1)
  542. {
  543. return new uint4((uint)(ul0 & 0xFFFFFFFF), (uint)(ul0 >> 32), (uint)(ul1 & 0xFFFFFFFF), (uint)(ul1 >> 32));
  544. }
  545. internal static unsafe void EncodeSecretKey(byte* dst, byte* secret, ulong seed)
  546. {
  547. unchecked
  548. {
  549. var seedInitCount = SECRET_KEY_SIZE / (8 * 2);
  550. for (var i = 0; i < seedInitCount; i++)
  551. {
  552. Write64LE(dst + 16 * i + 0, Read64LE(secret + 16 * i + 0) + seed);
  553. Write64LE(dst + 16 * i + 8, Read64LE(secret + 16 * i + 8) - seed);
  554. }
  555. }
  556. }
  557. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  558. private static unsafe ulong Read64LE(void* addr) => *(ulong*) addr;
  559. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  560. private static unsafe uint Read32LE(void* addr) => *(uint*) addr;
  561. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  562. private static unsafe void Write64LE(void* addr, ulong value) => *(ulong*) addr = value;
  563. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  564. private static unsafe void Read32LE(void* addr, uint value) => *(uint*) addr = value;
  565. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  566. private static ulong Mul32To64(uint x, uint y) => (ulong) x * y;
  567. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  568. private static ulong Swap64(ulong x)
  569. {
  570. return ((x << 56) & 0xff00000000000000UL) |
  571. ((x << 40) & 0x00ff000000000000UL) |
  572. ((x << 24) & 0x0000ff0000000000UL) |
  573. ((x << 8) & 0x000000ff00000000UL) |
  574. ((x >> 8) & 0x00000000ff000000UL) |
  575. ((x >> 24) & 0x0000000000ff0000UL) |
  576. ((x >> 40) & 0x000000000000ff00UL) |
  577. ((x >> 56) & 0x00000000000000ffUL);
  578. }
  579. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  580. private static uint Swap32(uint x)
  581. {
  582. return ((x << 24) & 0xff000000) |
  583. ((x << 8) & 0x00ff0000) |
  584. ((x >> 8) & 0x0000ff00) |
  585. ((x >> 24) & 0x000000ff);
  586. }
  587. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  588. private static uint RotL32(uint x, int r) => (((x) << (r)) | ((x) >> (32 - (r))));
  589. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  590. private static ulong RotL64(ulong x, int r) => (((x) << (r)) | ((x) >> (64 - (r))));
  591. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  592. private static ulong XorShift64(ulong v64, int shift)
  593. {
  594. return v64 ^ (v64 >> shift);
  595. }
  596. #if NET_DOTS
  597. private static class Common
  598. {
  599. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  600. public static ulong umul128(ulong x, ulong y, out ulong high)
  601. {
  602. // Split the inputs into high/low sections.
  603. ulong xLo = (uint)x;
  604. var xHi = x >> 32;
  605. ulong yLo = (uint)y;
  606. var yHi = y >> 32;
  607. // We have to use 4 multiples to compute the full range of the result.
  608. var hi = xHi * yHi;
  609. var m1 = xHi * yLo;
  610. var m2 = yHi * xLo;
  611. var lo = xLo * yLo;
  612. ulong m1Lo = (uint)m1;
  613. var loHi = lo >> 32;
  614. var m1Hi = m1 >> 32;
  615. high = hi + m1Hi + ((loHi + m1Lo + m2) >> 32);
  616. return x * y;
  617. }
  618. }
  619. #endif
  620. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  621. private static ulong Mul128Fold64(ulong lhs, ulong rhs)
  622. {
  623. var lo = Common.umul128(lhs, rhs, out var hi);
  624. return lo ^ hi;
  625. }
  626. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  627. private static unsafe ulong Mix16(byte* input, byte* secret, ulong seed)
  628. {
  629. var input_lo = Read64LE(input);
  630. var input_hi = Read64LE(input + 8);
  631. return Mul128Fold64(
  632. input_lo ^ (Read64LE(secret + 0) + seed),
  633. input_hi ^ (Read64LE(secret + 8) - seed));
  634. }
  635. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  636. private static unsafe ulong2 Mix32(ulong2 acc, byte* input_1, byte* input_2, byte* secret, ulong seed)
  637. {
  638. unchecked
  639. {
  640. var l0 = acc.x + Mix16(input_1, secret + 0, seed);
  641. l0 ^= Read64LE(input_2) + Read64LE(input_2 + 8);
  642. var l1 = acc.y + Mix16(input_2, secret + 16, seed);
  643. l1 ^= Read64LE(input_1) + Read64LE(input_1 + 8);
  644. return new ulong2(l0, l1);
  645. }
  646. }
  647. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  648. private static ulong Avalanche(ulong h64)
  649. {
  650. unchecked
  651. {
  652. h64 = XorShift64(h64, 37);
  653. h64 *= 0x165667919E3779F9UL;
  654. h64 = XorShift64(h64, 32);
  655. return h64;
  656. }
  657. }
  658. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  659. private static ulong AvalancheH64(ulong h64)
  660. {
  661. unchecked
  662. {
  663. h64 ^= h64 >> 33;
  664. h64 *= PRIME64_2;
  665. h64 ^= h64 >> 29;
  666. h64 *= PRIME64_3;
  667. h64 ^= h64 >> 32;
  668. return h64;
  669. }
  670. }
  671. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  672. private static ulong rrmxmx(ulong h64, ulong length)
  673. {
  674. h64 ^= RotL64(h64, 49) ^ RotL64(h64, 24);
  675. h64 *= 0x9FB21C651E98DF25UL;
  676. h64 ^= (h64 >> 35) + length ;
  677. h64 *= 0x9FB21C651E98DF25UL;
  678. return XorShift64(h64, 28);
  679. }
  680. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  681. private static unsafe ulong Mix2Acc(ulong acc0, ulong acc1, byte* secret)
  682. {
  683. return Mul128Fold64(acc0 ^ Read64LE(secret), acc1 ^ Read64LE(secret+8));
  684. }
  685. internal static unsafe ulong MergeAcc(ulong* acc, byte* secret, ulong start)
  686. {
  687. unchecked
  688. {
  689. var result64 = start;
  690. result64 += Mix2Acc(acc[0], acc[1], secret + 0);
  691. result64 += Mix2Acc(acc[2], acc[3], secret + 16);
  692. result64 += Mix2Acc(acc[4], acc[5], secret + 32);
  693. result64 += Mix2Acc(acc[6], acc[7], secret + 48);
  694. return Avalanche(result64);
  695. }
  696. }
  697. #endregion
  698. #region Default Implementation
  699. private static unsafe void DefaultHashLongInternalLoop(ulong* acc, byte* input, byte* dest, long length, byte* secret, int isHash64)
  700. {
  701. // Process packets of 512 bits
  702. var nb_blocks = (length-1) / BLOCK_LEN;
  703. for (int n = 0; n < nb_blocks; n++)
  704. {
  705. DefaultAccumulate(acc, input + n * BLOCK_LEN, dest == null ? null : dest + n * BLOCK_LEN, secret,
  706. NB_ROUNDS, isHash64);
  707. DefaultScrambleAcc(acc, secret + SECRET_KEY_SIZE - STRIPE_LEN);
  708. }
  709. var nbStripes = ((length-1) - (BLOCK_LEN * nb_blocks)) / STRIPE_LEN;
  710. DefaultAccumulate(acc, input + nb_blocks * BLOCK_LEN, dest == null ? null : dest + nb_blocks * BLOCK_LEN,
  711. secret, nbStripes, isHash64);
  712. var p = input + length - STRIPE_LEN;
  713. DefaultAccumulate512(acc, p, null, secret + SECRET_KEY_SIZE - STRIPE_LEN - SECRET_LASTACC_START,
  714. isHash64);
  715. if (dest != null)
  716. {
  717. var remaining = length % STRIPE_LEN;
  718. if (remaining != 0)
  719. {
  720. UnsafeUtility.MemCpy(dest + length - remaining, input + length - remaining, remaining);
  721. }
  722. }
  723. }
  724. internal static unsafe void DefaultAccumulate(ulong* acc, byte* input, byte* dest, byte* secret, long nbStripes, int isHash64)
  725. {
  726. for (int n = 0; n < nbStripes; n++)
  727. {
  728. DefaultAccumulate512(acc, input + n * STRIPE_LEN, dest == null ? null : dest + n * STRIPE_LEN,
  729. secret + n * SECRET_CONSUME_RATE, isHash64);
  730. }
  731. }
  732. internal static unsafe void DefaultAccumulate512(ulong* acc, byte* input, byte* dest, byte* secret, int isHash64)
  733. {
  734. var count = ACC_NB;
  735. for (var i = 0; i < count; i++)
  736. {
  737. var data_val = Read64LE(input + 8 * i);
  738. var data_key = data_val ^ Read64LE(secret + i * 8);
  739. if (dest != null)
  740. {
  741. Write64LE(dest + 8 * i, data_val);
  742. }
  743. acc[i ^ 1] += data_val;
  744. acc[i] += Mul32To64((uint) (data_key & 0xFFFFFFFF), (uint) (data_key >> 32));
  745. }
  746. }
  747. internal static unsafe void DefaultScrambleAcc(ulong* acc, byte* secret)
  748. {
  749. for (var i = 0; i < ACC_NB; i++)
  750. {
  751. var key64 = Read64LE(secret + 8 * i);
  752. var acc64 = acc[i];
  753. acc64 = XorShift64(acc64, 47);
  754. acc64 ^= key64;
  755. acc64 *= PRIME32_1;
  756. acc[i] = acc64;
  757. }
  758. }
  759. #endregion
  760. }
  761. static class xxHashDefaultKey
  762. {
  763. // The default xxHash3 encoding key, other implementations of this algorithm should use the same key to produce identical hashes
  764. public static readonly byte[] kSecret =
  765. {
  766. 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
  767. 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
  768. 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
  769. 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
  770. 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
  771. 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
  772. 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
  773. 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
  774. 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
  775. 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
  776. 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
  777. 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
  778. };
  779. }
  780. }