Geen omschrijving
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.

CustomAllocatorTests.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. using System;
  2. using AOT;
  3. using NUnit.Framework;
  4. using Unity.Burst;
  5. using Unity.Collections;
  6. using Unity.Collections.LowLevel.Unsafe;
  7. using Unity.Jobs;
  8. using Unity.Mathematics;
  9. using Unity.Jobs.LowLevel.Unsafe;
  10. using Unity.Collections.Tests;
  11. internal class CustomAllocatorTests : CollectionsTestCommonBase
  12. {
  13. [TestCase(1337, 1337)]
  14. [TestCase(0xFFFF, 0x0000)]
  15. [TestCase(0x0000, 0xFFFF)]
  16. [TestCase(0xFFFF, 0xFFFF)]
  17. [TestCase(0x0000, 0x0000)]
  18. public void AllocatorHandleToAllocatorRoundTripWorks(int i, int v)
  19. {
  20. var Index = (ushort)i;
  21. var Version = (ushort)v;
  22. AllocatorManager.AllocatorHandle srcHandle = new AllocatorManager.AllocatorHandle{ Index = Index, Version = Version };
  23. Allocator srcAllocator = srcHandle.ToAllocator;
  24. AllocatorManager.AllocatorHandle destHandle = AllocatorManager.ConvertToAllocatorHandle(srcAllocator);
  25. Assert.AreEqual(srcHandle.Index, destHandle.Index);
  26. Assert.AreEqual(srcHandle.Version, destHandle.Version);
  27. Allocator destAllocator = destHandle.ToAllocator;
  28. Assert.AreEqual(srcAllocator, destAllocator);
  29. }
  30. [Test]
  31. public void AllocatorVersioningWorks()
  32. {
  33. AllocatorManager.Initialize();
  34. var origin = AllocatorManager.Persistent;
  35. var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
  36. for(var i = 1; i <= 3; ++i)
  37. {
  38. var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
  39. ref var allocator = ref allocatorHelper.Allocator;
  40. allocator.Initialize(storage);
  41. var oldIndex = allocator.Handle.Index;
  42. var oldVersion = allocator.Handle.Version;
  43. allocator.Dispose();
  44. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  45. var newVersion = AllocatorManager.SharedStatics.Version.Ref.Data.ElementAt(oldIndex);
  46. Assert.AreEqual(oldVersion + 1, newVersion);
  47. #endif
  48. allocatorHelper.Dispose();
  49. }
  50. storage.Dispose();
  51. AllocatorManager.Shutdown();
  52. }
  53. [Test]
  54. public void ReleasingChildHandlesWorks()
  55. {
  56. AllocatorManager.Initialize();
  57. var origin = AllocatorManager.Persistent;
  58. var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
  59. var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
  60. ref var allocator = ref allocatorHelper.Allocator;
  61. allocator.Initialize(storage);
  62. var list = NativeList<int>.New(10, ref allocator);
  63. list.Add(0); // put something in the list, so it'll have a size for later
  64. allocator.Dispose(); // ok to tear down the storage that the stack allocator used, too.
  65. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  66. Assert.Throws<ObjectDisposedException>(
  67. () => {
  68. list[0] = 0; // we haven't disposed this list, but it was released automatically already. so this is an error.
  69. });
  70. #endif
  71. storage.Dispose();
  72. allocatorHelper.Dispose();
  73. AllocatorManager.Shutdown();
  74. }
  75. [Test]
  76. public unsafe void ReleasingChildAllocatorsWorks()
  77. {
  78. AllocatorManager.Initialize();
  79. var origin = AllocatorManager.Persistent;
  80. var parentStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
  81. var parentHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
  82. ref var parent = ref parentHelper.Allocator;
  83. parent.Initialize(parentStorage); // and make a stack allocator from it
  84. var childStorage = parent.AllocateBlock(default(byte), 10000); // allocate some space from the parent
  85. var childHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
  86. childHelper.Allocator.Initialize(childStorage); // and make a stack allocator from it
  87. parent.Dispose(); // tear down the parent allocator
  88. #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
  89. Assert.Throws<ArgumentException>(() =>
  90. {
  91. childHelper.Allocator.Allocate(default(byte), 1000); // try to allocate from the child - it should fail.
  92. });
  93. #endif
  94. parentStorage.Dispose();
  95. parentHelper.Dispose();
  96. childHelper.Dispose();
  97. AllocatorManager.Shutdown();
  98. }
  99. [Test]
  100. public void AllocatesAndFreesFromMono()
  101. {
  102. AllocatorManager.Initialize();
  103. const int kLength = 100;
  104. var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
  105. for (int i = 0; i < kLength; ++i)
  106. {
  107. var allocator = AllocatorManager.Persistent;
  108. var block = allocator.AllocateBlock(default(int), i);
  109. if(i != 0)
  110. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  111. Assert.AreEqual(i, block.Range.Items);
  112. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
  113. Assert.AreEqual(expectedAlignment, block.Alignment);
  114. Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
  115. allocator.FreeBlock(ref block);
  116. }
  117. AllocatorManager.Shutdown();
  118. }
  119. [BurstCompile(CompileSynchronously = true)]
  120. struct AllocateJob : IJobParallelFor
  121. {
  122. public NativeArray<AllocatorManager.Block> m_blocks;
  123. public void Execute(int index)
  124. {
  125. var allocator = AllocatorManager.Persistent;
  126. m_blocks[index] = allocator.AllocateBlock(default(int), index);
  127. }
  128. }
  129. [BurstCompile(CompileSynchronously = true)]
  130. struct FreeJob : IJobParallelFor
  131. {
  132. public NativeArray<AllocatorManager.Block> m_blocks;
  133. public void Execute(int index)
  134. {
  135. var temp = m_blocks[index];
  136. temp.Free();
  137. m_blocks[index] = temp;
  138. }
  139. }
  140. [Test]
  141. public void AllocatesAndFreesFromBurst()
  142. {
  143. AllocatorManager.Initialize();
  144. const int kLength = 100;
  145. var blocks = new NativeArray<AllocatorManager.Block>(kLength, Allocator.Persistent);
  146. var allocateJob = new AllocateJob();
  147. allocateJob.m_blocks = blocks;
  148. allocateJob.Schedule(kLength, 1).Complete();
  149. var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
  150. for (int i = 0; i < kLength; ++i)
  151. {
  152. var block = allocateJob.m_blocks[i];
  153. if(i != 0)
  154. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  155. Assert.AreEqual(i, block.Range.Items);
  156. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
  157. Assert.AreEqual(expectedAlignment, block.Alignment);
  158. Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
  159. }
  160. var freeJob = new FreeJob();
  161. freeJob.m_blocks = blocks;
  162. freeJob.Schedule(kLength, 1).Complete();
  163. for (int i = 0; i < kLength; ++i)
  164. {
  165. var block = allocateJob.m_blocks[i];
  166. Assert.AreEqual(IntPtr.Zero, block.Range.Pointer);
  167. Assert.AreEqual(0, block.Range.Items);
  168. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
  169. Assert.AreEqual(expectedAlignment, block.Alignment);
  170. Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
  171. }
  172. blocks.Dispose();
  173. AllocatorManager.Shutdown();
  174. }
  175. // This allocator wraps UnsafeUtility.Malloc, but also initializes memory to some constant value after allocating.
  176. [BurstCompile(CompileSynchronously = true)]
  177. struct ClearToValueAllocator : AllocatorManager.IAllocator
  178. {
  179. public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
  180. public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
  181. public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
  182. internal AllocatorManager.AllocatorHandle m_handle;
  183. internal AllocatorManager.AllocatorHandle m_parent;
  184. public byte m_clearValue;
  185. public void Initialize<T>(byte ClearValue, ref T parent) where T : unmanaged, AllocatorManager.IAllocator
  186. {
  187. m_parent = parent.Handle;
  188. m_clearValue = ClearValue;
  189. #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
  190. parent.Handle.AddChildAllocator(m_handle);
  191. #endif
  192. }
  193. public unsafe int Try(ref AllocatorManager.Block block)
  194. {
  195. var temp = block.Range.Allocator;
  196. block.Range.Allocator = m_parent;
  197. var error = AllocatorManager.Try(ref block);
  198. block.Range.Allocator = temp;
  199. if (error != 0)
  200. return error;
  201. if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
  202. UnsafeUtility.MemSet((void*)block.Range.Pointer, m_clearValue, block.Bytes); // clear to a value.
  203. return 0;
  204. }
  205. [BurstCompile(CompileSynchronously = true)]
  206. [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
  207. public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
  208. {
  209. return ((ClearToValueAllocator*)state)->Try(ref block);
  210. }
  211. public AllocatorManager.TryFunction Function => Try;
  212. public void Dispose()
  213. {
  214. m_handle.Dispose();
  215. }
  216. }
  217. [BurstCompile(CompileSynchronously = true)]
  218. internal struct CountingAllocator : AllocatorManager.IAllocator
  219. {
  220. public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
  221. public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
  222. public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
  223. public AllocatorManager.AllocatorHandle m_handle;
  224. public int AllocationCount;
  225. public long Used;
  226. public bool WasUsed => AllocationCount > 0;
  227. public bool IsUsed => Used > 0;
  228. public void Initialize()
  229. {
  230. AllocationCount = 0;
  231. Used = 0;
  232. #if ENABLE_UNITY_ALLOCATIONS_CHECKS
  233. AllocatorManager.Persistent.Handle.AddChildAllocator(m_handle);
  234. #endif
  235. }
  236. public int Try(ref AllocatorManager.Block block)
  237. {
  238. if (block.Range.Pointer != IntPtr.Zero)
  239. {
  240. Used -= block.AllocatedBytes;
  241. }
  242. var temp = block.Range.Allocator;
  243. block.Range.Allocator = AllocatorManager.Persistent;
  244. var error = AllocatorManager.Try(ref block);
  245. block.Range.Allocator = temp;
  246. if (error != 0)
  247. return error;
  248. if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
  249. {
  250. ++AllocationCount;
  251. Used += block.AllocatedBytes;
  252. }
  253. return 0;
  254. }
  255. [BurstCompile(CompileSynchronously = true)]
  256. [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
  257. public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
  258. {
  259. return ((CountingAllocator*)state)->Try(ref block);
  260. }
  261. public AllocatorManager.TryFunction Function => Try;
  262. public void Dispose()
  263. {
  264. m_handle.Dispose();
  265. }
  266. }
  267. [Test]
  268. public void UserDefinedAllocatorWorks()
  269. {
  270. AllocatorManager.Initialize();
  271. var parent = AllocatorManager.Persistent;
  272. var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
  273. ref var allocator = ref allocatorHelper.Allocator;
  274. allocator.Initialize(0, ref parent);
  275. var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
  276. for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
  277. {
  278. allocator.m_clearValue = ClearValue;
  279. const int kLength = 100;
  280. for (int i = 1; i < kLength; ++i)
  281. {
  282. var block = allocator.AllocateBlock(default(int), i);
  283. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  284. Assert.AreEqual(i, block.Range.Items);
  285. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
  286. Assert.AreEqual(expectedAlignment, block.Alignment);
  287. allocator.FreeBlock(ref block);
  288. }
  289. }
  290. allocator.Dispose();
  291. allocatorHelper.Dispose();
  292. AllocatorManager.Shutdown();
  293. }
  294. // this is testing for the case where we want+ to install a stack allocator that itself allocates from a big hunk
  295. // of memory provided by the default Persistent allocator, and then make allocations on the stack allocator.
  296. [Test]
  297. public void StackAllocatorWorks()
  298. {
  299. AllocatorManager.Initialize();
  300. var origin = AllocatorManager.Persistent;
  301. var backingStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
  302. var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
  303. ref var allocator = ref allocatorHelper.Allocator;
  304. allocator.Initialize(backingStorage);
  305. var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
  306. const int kLength = 100;
  307. for (int i = 1; i < kLength; ++i)
  308. {
  309. var block = allocator.AllocateBlock(default(int), i);
  310. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  311. Assert.AreEqual(i, block.Range.Items);
  312. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
  313. Assert.AreEqual(expectedAlignment, block.Alignment);
  314. allocator.FreeBlock(ref block);
  315. }
  316. allocator.Dispose();
  317. backingStorage.Dispose();
  318. allocatorHelper.Dispose();
  319. AllocatorManager.Shutdown();
  320. }
  321. [Test]
  322. public void CustomAllocatorNativeListWorksWithoutHandles()
  323. {
  324. AllocatorManager.Initialize();
  325. var allocator = AllocatorManager.Persistent;
  326. var list = NativeList<byte>.New(100, ref allocator);
  327. list.Dispose(ref allocator);
  328. AllocatorManager.Shutdown();
  329. }
  330. [Test]
  331. [TestRequiresDotsDebugOrCollectionChecks]
  332. public void CustomAllocatorNativeListThrowsWhenAllocatorIsWrong()
  333. {
  334. AllocatorManager.Initialize();
  335. var allocator0 = AllocatorManager.Persistent;
  336. var list = NativeList<byte>.New(100, ref allocator0);
  337. Assert.Throws<ArgumentOutOfRangeException>(() =>
  338. {
  339. list.Dispose(ref CommonRwdAllocatorHelper.Allocator);
  340. });
  341. list.Dispose(ref allocator0);
  342. AllocatorManager.Shutdown();
  343. }
  344. // this is testing for the case where we want to install a custom allocator that clears memory to a constant
  345. // byte value, and then have an UnsafeList use that custom allocator.
  346. [Test]
  347. public void CustomAllocatorUnsafeListWorks()
  348. {
  349. AllocatorManager.Initialize();
  350. var parent = AllocatorManager.Persistent;
  351. var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
  352. ref var allocator = ref allocatorHelper.Allocator;
  353. allocator.Initialize(0xFE, ref parent);
  354. for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
  355. {
  356. allocator.m_clearValue = ClearValue;
  357. var unsafelist = new UnsafeList<byte>(1, allocator.Handle);
  358. const int kLength = 100;
  359. unsafelist.Resize(kLength);
  360. for (int i = 0; i < kLength; ++i)
  361. Assert.AreEqual(ClearValue, unsafelist[i]);
  362. unsafelist.Dispose();
  363. }
  364. allocator.Dispose();
  365. allocatorHelper.Dispose();
  366. AllocatorManager.Shutdown();
  367. }
  368. [Test]
  369. public unsafe void SlabAllocatorWorks()
  370. {
  371. var SlabSizeInBytes = 256;
  372. var SlabSizeInInts = SlabSizeInBytes / sizeof(int);
  373. var Slabs = 256;
  374. AllocatorManager.Initialize();
  375. var origin = AllocatorManager.Persistent;
  376. var backingStorage = origin.AllocateBlock(default(byte), Slabs * SlabSizeInBytes); // allocate a block of bytes from Malloc.Persistent
  377. var allocatorHelper = new AllocatorHelper<AllocatorManager.SlabAllocator>(AllocatorManager.Persistent);
  378. ref var allocator = ref allocatorHelper.Allocator;
  379. allocator.Initialize(backingStorage, SlabSizeInBytes, Slabs * SlabSizeInBytes);
  380. var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
  381. var block0 = allocator.AllocateBlock(default(int), SlabSizeInInts);
  382. Assert.AreNotEqual(IntPtr.Zero, block0.Range.Pointer);
  383. Assert.AreEqual(SlabSizeInInts, block0.Range.Items);
  384. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block0.BytesPerItem);
  385. Assert.AreEqual(expectedAlignment, block0.Alignment);
  386. Assert.AreEqual(1, allocator.Occupied[0]);
  387. var block1 = allocator.AllocateBlock(default(int), SlabSizeInInts - 1);
  388. Assert.AreNotEqual(IntPtr.Zero, block1.Range.Pointer);
  389. Assert.AreEqual(SlabSizeInInts - 1, block1.Range.Items);
  390. Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block1.BytesPerItem);
  391. Assert.AreEqual(expectedAlignment, block1.Alignment);
  392. Assert.AreEqual(3, allocator.Occupied[0]);
  393. allocator.FreeBlock(ref block0);
  394. Assert.AreEqual(2, allocator.Occupied[0]);
  395. allocator.FreeBlock(ref block1);
  396. Assert.AreEqual(0, allocator.Occupied[0]);
  397. #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
  398. Assert.Throws<ArgumentException>(() =>
  399. {
  400. allocatorHelper.Allocator.AllocateBlock(default(int), 65);
  401. });
  402. #endif
  403. allocator.Dispose();
  404. backingStorage.Dispose();
  405. allocatorHelper.Dispose();
  406. AllocatorManager.Shutdown();
  407. }
  408. [Test]
  409. public unsafe void CollectionHelper_IsAligned()
  410. {
  411. #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
  412. Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x0, 0)); // value is 0
  413. Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 0)); // alignment is 0
  414. Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 3)); // alignment is not pow2
  415. #endif
  416. for (var i = 0; i < 31; ++i)
  417. {
  418. Assert.IsTrue(CollectionHelper.IsAligned((void*)0x80000000, 1<<i));
  419. }
  420. }
  421. [Test]
  422. public void AllocatorManager_AllocateBlock_UsesAlignmentArgument()
  423. {
  424. int sizeOf = 1;
  425. int alignOf = 4096;
  426. int items = 1;
  427. var temp = AllocatorManager.Temp;
  428. var block = temp.AllocateBlock(sizeOf, alignOf, items);
  429. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  430. unsafe
  431. {
  432. Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, alignOf));
  433. }
  434. }
  435. [Test]
  436. public void AllocatorManager_AllocateBlock_AlwaysCacheLineAligned()
  437. {
  438. int sizeOf = 1;
  439. int items = 1;
  440. var temp = AllocatorManager.Temp;
  441. for (int alignment = 1; alignment < 256; ++alignment)
  442. {
  443. var block = temp.AllocateBlock(sizeOf, alignment, items);
  444. Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
  445. unsafe
  446. {
  447. Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, CollectionHelper.CacheLineSize));
  448. }
  449. }
  450. }
  451. [Test]
  452. public void AllocatorManager_Block_DoesNotOverflow()
  453. {
  454. AllocatorManager.Block block = default;
  455. block.BytesPerItem = int.MaxValue;
  456. block.AllocatedItems = 2;
  457. block.Range = new AllocatorManager.Range { Items = 2 };
  458. long ExpectedAllocatedBytes = (long)block.BytesPerItem * (long)block.AllocatedItems;
  459. long ExpectedBytes = (long)block.BytesPerItem * (long)block.Range.Items;
  460. Assert.AreEqual(ExpectedAllocatedBytes, block.AllocatedBytes);
  461. Assert.AreEqual(ExpectedBytes, block.Bytes);
  462. }
  463. }