説明なし
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

xxHash3.StreamingState.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using System;
  2. using System.Diagnostics;
  3. using System.Runtime.InteropServices;
  4. using Unity.Burst;
  5. using Unity.Burst.Intrinsics;
  6. using Unity.Collections.LowLevel.Unsafe;
  7. using Unity.Mathematics;
  8. namespace Unity.Collections
  9. {
  10. [GenerateTestsForBurstCompatibility]
  11. public static partial class xxHash3
  12. {
  13. /// <summary>
  14. /// Type used to compute hash based on multiple data feed
  15. /// </summary>
  16. /// <remarks>
  17. /// Allow to feed the internal hashing accumulators with data through multiple calls to <see cref="Update"/>, then retrieving the final hash value using <see cref="DigestHash64"/> or <see cref="DigestHash128"/>.
  18. /// More info about how to use this class in its constructor.
  19. /// </remarks>
  20. [GenerateTestsForBurstCompatibility]
  21. public struct StreamingState
  22. {
  23. #region Public API
  24. /// <summary>
  25. /// Create a StreamingState object, ready to be used with the streaming API
  26. /// </summary>
  27. /// <param name="isHash64">true if we are computing a 64bits hash value, false if we are computing a 128bits one</param>
  28. /// <param name="seed">A seed value to be used to compute the hash, default is 0</param>
  29. /// <remarks>
  30. /// Once the object is constructed, you can call the <see cref="Update"/> method as many times as you want to accumulate data to hash.
  31. /// When all the data has been sent, call <see cref="DigestHash64"/> or <see cref="DigestHash128"/> to retrieve the corresponding key, the <see cref="StreamingState"/>
  32. /// instance will then be reset, using the same hash key size and same Seed in order to be ready to be used again.
  33. /// </remarks>
  34. public StreamingState(bool isHash64, ulong seed=0)
  35. {
  36. State = default;
  37. Reset(isHash64, seed);
  38. }
  39. /// <summary>
  40. /// Reset the state of the streaming instance using the given seed value.
  41. /// </summary>
  42. /// <param name="isHash64"></param>
  43. /// <param name="seed">The seed value to alter the computed hash value from</param>
  44. /// <remarks> Call this method to start a new streaming session based on this instance</remarks>
  45. public unsafe void Reset(bool isHash64, ulong seed=0UL)
  46. {
  47. // Reset the whole buffer to 0
  48. var size = UnsafeUtility.SizeOf<StreamingStateData>();
  49. UnsafeUtility.MemClear(UnsafeUtility.AddressOf(ref State), size);
  50. // Set back the saved states
  51. State.IsHash64 = isHash64 ? 1 : 0;
  52. // Init the accumulator with the prime numbers
  53. var acc = Acc;
  54. acc[0] = PRIME32_3;
  55. acc[1] = PRIME64_1;
  56. acc[2] = PRIME64_2;
  57. acc[3] = PRIME64_3;
  58. acc[4] = PRIME64_4;
  59. acc[5] = PRIME32_2;
  60. acc[6] = PRIME64_5;
  61. acc[7] = PRIME32_1;
  62. State.Seed = seed;
  63. fixed (byte* secret = xxHashDefaultKey.kSecret)
  64. {
  65. if (seed != 0)
  66. {
  67. // Must encode the secret key if we're using a seed, we store it in the state object
  68. EncodeSecretKey(SecretKey, secret, seed);
  69. }
  70. else
  71. {
  72. // Otherwise just copy it
  73. UnsafeUtility.MemCpy(SecretKey, secret, SECRET_KEY_SIZE);
  74. }
  75. }
  76. }
  77. /// <summary>
  78. /// Add some data to be hashed
  79. /// </summary>
  80. /// <param name="input">The memory buffer, can't be null</param>
  81. /// <param name="length">The length of the data to accumulate, can be zero</param>
  82. /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks>
  83. public unsafe void Update(void* input, int length)
  84. {
  85. var bInput = (byte*) input;
  86. var bEnd = bInput + length;
  87. var isHash64 = State.IsHash64;
  88. var secret = SecretKey;
  89. State.TotalLength += length;
  90. if (State.BufferedSize + length <= INTERNAL_BUFFER_SIZE)
  91. {
  92. UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, length);
  93. State.BufferedSize += length;
  94. return;
  95. }
  96. if (State.BufferedSize != 0)
  97. {
  98. var loadSize = INTERNAL_BUFFER_SIZE - State.BufferedSize;
  99. UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, loadSize);
  100. bInput += loadSize;
  101. ConsumeStripes(Acc, ref State.NbStripesSoFar, Buffer, INTERNAL_BUFFER_STRIPES, secret, isHash64);
  102. State.BufferedSize = 0;
  103. }
  104. if (bInput + INTERNAL_BUFFER_SIZE < bEnd)
  105. {
  106. var limit = bEnd - INTERNAL_BUFFER_SIZE;
  107. do
  108. {
  109. ConsumeStripes(Acc, ref State.NbStripesSoFar, bInput, INTERNAL_BUFFER_STRIPES, secret, isHash64);
  110. bInput += INTERNAL_BUFFER_SIZE;
  111. } while (bInput < limit);
  112. UnsafeUtility.MemCpy(Buffer + INTERNAL_BUFFER_SIZE - STRIPE_LEN, bInput - STRIPE_LEN, STRIPE_LEN);
  113. }
  114. if (bInput < bEnd)
  115. {
  116. var newBufferedSize = bEnd - bInput;
  117. UnsafeUtility.MemCpy(Buffer, bInput, newBufferedSize);
  118. State.BufferedSize = (int) newBufferedSize;
  119. }
  120. }
  121. /// <summary>
  122. /// Add the contents of input struct to the hash.
  123. /// </summary>
  124. /// <typeparam name="T">The input type.</typeparam>
  125. /// <param name="input">The input struct that will be hashed</param>
  126. /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks>
  127. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
  128. public unsafe void Update<T>(in T input) where T : unmanaged
  129. {
  130. Update(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>());
  131. }
  132. /// <summary>
  133. /// Compute the 128bits value based on all the data that have been accumulated
  134. /// </summary>
  135. /// <returns>The hash value</returns>
  136. public unsafe uint4 DigestHash128()
  137. {
  138. CheckKeySize(0);
  139. unchecked
  140. {
  141. var secret = SecretKey;
  142. uint4 hash;
  143. if (State.TotalLength > MIDSIZE_MAX)
  144. {
  145. var acc = stackalloc ulong[ACC_NB];
  146. DigestLong(acc, secret, 0);
  147. var low64 = MergeAcc(acc, secret + SECRET_MERGEACCS_START,
  148. (ulong) State.TotalLength * PRIME64_1);
  149. var high64 = MergeAcc(acc, secret + SECRET_LIMIT - SECRET_MERGEACCS_START,
  150. ~((ulong) State.TotalLength * PRIME64_2));
  151. hash = ToUint4(low64, high64);
  152. }
  153. else
  154. {
  155. hash = Hash128(Buffer, State.TotalLength, State.Seed);
  156. }
  157. Reset(State.IsHash64==1, State.Seed);
  158. return hash;
  159. }
  160. }
  161. /// <summary>
  162. /// Compute the 64bits value based on all the data that have been accumulated
  163. /// </summary>
  164. /// <returns>The hash value</returns>
  165. public unsafe uint2 DigestHash64()
  166. {
  167. CheckKeySize(1);
  168. unchecked
  169. {
  170. var secret = SecretKey;
  171. uint2 hash;
  172. if (State.TotalLength > MIDSIZE_MAX)
  173. {
  174. var acc = stackalloc ulong[ACC_NB];
  175. DigestLong(acc, secret, 1);
  176. hash = ToUint2(MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) State.TotalLength * PRIME64_1));
  177. }
  178. else
  179. {
  180. hash = Hash64(Buffer, State.TotalLength, State.Seed);
  181. }
  182. Reset(State.IsHash64==1, State.Seed);
  183. return hash;
  184. }
  185. }
  186. #endregion
  187. #region Constants
  188. private static readonly int SECRET_LIMIT = SECRET_KEY_SIZE - STRIPE_LEN;
  189. private static readonly int NB_STRIPES_PER_BLOCK = SECRET_LIMIT / SECRET_CONSUME_RATE;
  190. private static readonly int INTERNAL_BUFFER_SIZE = 256;
  191. private static readonly int INTERNAL_BUFFER_STRIPES = INTERNAL_BUFFER_SIZE / STRIPE_LEN;
  192. #endregion
  193. #region Wrapper to internal data storage
  194. unsafe ulong* Acc
  195. {
  196. [DebuggerStepThrough]
  197. get => (ulong*) UnsafeUtility.AddressOf(ref State.Acc);
  198. }
  199. unsafe byte* Buffer
  200. {
  201. [DebuggerStepThrough]
  202. get => (byte*) UnsafeUtility.AddressOf(ref State.Buffer);
  203. }
  204. unsafe byte* SecretKey
  205. {
  206. [DebuggerStepThrough]
  207. get => (byte*) UnsafeUtility.AddressOf(ref State.SecretKey);
  208. }
  209. #endregion
  210. #region Data storage
  211. private StreamingStateData State;
  212. [StructLayout(LayoutKind.Explicit)]
  213. struct StreamingStateData
  214. {
  215. [FieldOffset(0)] public ulong Acc; // 64 bytes
  216. [FieldOffset(64)] public byte Buffer; // 256 bytes
  217. [FieldOffset(320)] public int IsHash64; // 4 bytes
  218. [FieldOffset(324)] public int BufferedSize; // 4 bytes
  219. [FieldOffset(328)] public int NbStripesSoFar; // 4 bytes + 4 padding
  220. [FieldOffset(336)] public long TotalLength; // 8 bytes
  221. [FieldOffset(344)] public ulong Seed; // 8 bytes
  222. [FieldOffset(352)] public byte SecretKey; // 192 bytes
  223. [FieldOffset(540)] public byte _PadEnd;
  224. }
  225. #endregion
  226. #region Internals
  227. private unsafe void DigestLong(ulong* acc, byte* secret, int isHash64)
  228. {
  229. UnsafeUtility.MemCpy(acc, Acc, STRIPE_LEN);
  230. if (State.BufferedSize >= STRIPE_LEN)
  231. {
  232. var totalNbStripes = (State.BufferedSize - 1) / STRIPE_LEN;
  233. ConsumeStripes(acc, ref State.NbStripesSoFar, Buffer, totalNbStripes, secret, isHash64);
  234. if (X86.Avx2.IsAvx2Supported)
  235. {
  236. Avx2Accumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
  237. secret + SECRET_LIMIT - SECRET_LASTACC_START);
  238. }
  239. else
  240. {
  241. DefaultAccumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
  242. secret + SECRET_LIMIT - SECRET_LASTACC_START, isHash64);
  243. }
  244. }
  245. else
  246. {
  247. var lastStripe = stackalloc byte[STRIPE_LEN];
  248. var catchupSize = STRIPE_LEN - State.BufferedSize;
  249. UnsafeUtility.MemCpy(lastStripe, Buffer + INTERNAL_BUFFER_SIZE - catchupSize, catchupSize);
  250. UnsafeUtility.MemCpy(lastStripe + catchupSize, Buffer, State.BufferedSize);
  251. if (X86.Avx2.IsAvx2Supported)
  252. {
  253. Avx2Accumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START);
  254. }
  255. else
  256. {
  257. DefaultAccumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START, isHash64);
  258. }
  259. }
  260. }
  261. private unsafe void ConsumeStripes(ulong* acc, ref int nbStripesSoFar, byte* input, long totalStripes,
  262. byte* secret, int isHash64)
  263. {
  264. if (NB_STRIPES_PER_BLOCK - nbStripesSoFar <= totalStripes)
  265. {
  266. var nbStripes = NB_STRIPES_PER_BLOCK - nbStripesSoFar;
  267. if (X86.Avx2.IsAvx2Supported)
  268. {
  269. Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
  270. Avx2ScrambleAcc(acc, secret + SECRET_LIMIT);
  271. Avx2Accumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
  272. }
  273. else
  274. {
  275. DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
  276. DefaultScrambleAcc(acc, secret + SECRET_LIMIT);
  277. DefaultAccumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
  278. }
  279. nbStripesSoFar = (int) totalStripes - nbStripes;
  280. }
  281. else
  282. {
  283. if (X86.Avx2.IsAvx2Supported)
  284. {
  285. Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
  286. }
  287. else
  288. {
  289. DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
  290. }
  291. nbStripesSoFar += (int) totalStripes;
  292. }
  293. }
  294. [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
  295. void CheckKeySize(int isHash64)
  296. {
  297. if (State.IsHash64 != isHash64)
  298. {
  299. var s = State.IsHash64 != 0 ? "64" : "128";
  300. throw new InvalidOperationException(
  301. $"The streaming state was create for {s} bits hash key, the calling method doesn't support this key size, please use the appropriate API");
  302. }
  303. }
  304. #endregion
  305. }
  306. }
  307. }