MultiRegionTrackingTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. using NUnit.Framework;
  2. using Ryujinx.Memory.Tracking;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. namespace Ryujinx.Memory.Tests
  7. {
  8. public class MultiRegionTrackingTests
  9. {
  10. private const int RndCnt = 3;
  11. private const ulong MemorySize = 0x8000;
  12. private const int PageSize = 4096;
  13. private MemoryBlock _memoryBlock;
  14. private MemoryTracking _tracking;
  15. private MockVirtualMemoryManager _memoryManager;
  16. [SetUp]
  17. public void Setup()
  18. {
  19. _memoryBlock = new MemoryBlock(MemorySize);
  20. _memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize);
  21. _tracking = new MemoryTracking(_memoryManager, _memoryBlock, PageSize);
  22. }
  23. [TearDown]
  24. public void Teardown()
  25. {
  26. _memoryBlock.Dispose();
  27. }
  28. private IMultiRegionHandle GetGranular(bool smart, ulong address, ulong size, ulong granularity)
  29. {
  30. return smart ?
  31. _tracking.BeginSmartGranularTracking(address, size, granularity) :
  32. (IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, granularity);
  33. }
  34. private void RandomOrder(Random random, List<int> indices, Action<int> action)
  35. {
  36. List<int> choices = indices.ToList();
  37. while (choices.Count > 0)
  38. {
  39. int choice = random.Next(choices.Count);
  40. action(choices[choice]);
  41. choices.RemoveAt(choice);
  42. }
  43. }
  44. private int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func<ulong, bool> addressPredicate)
  45. {
  46. int regionCount = 0;
  47. ulong lastAddress = startAddress;
  48. handle.QueryModified(startAddress, size, (address, range) =>
  49. {
  50. Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
  51. Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
  52. lastAddress = address;
  53. regionCount++;
  54. });
  55. return regionCount;
  56. }
  57. private int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func<ulong, bool> addressPredicate, int sequenceNumber)
  58. {
  59. int regionCount = 0;
  60. ulong lastAddress = startAddress;
  61. handle.QueryModified(startAddress, size, (address, range) =>
  62. {
  63. Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
  64. Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
  65. lastAddress = address;
  66. regionCount++;
  67. }, sequenceNumber);
  68. return regionCount;
  69. }
  70. private void PreparePages(IMultiRegionHandle handle, int pageCount, ulong address = 0)
  71. {
  72. Random random = new Random();
  73. // Make sure the list has minimum granularity (smart region changes granularity based on requested ranges)
  74. RandomOrder(random, Enumerable.Range(0, pageCount).ToList(), (i) =>
  75. {
  76. ulong resultAddress = ulong.MaxValue;
  77. handle.QueryModified((ulong)i * PageSize + address, PageSize, (address, range) =>
  78. {
  79. resultAddress = address;
  80. });
  81. Assert.AreEqual(resultAddress, (ulong)i * PageSize + address);
  82. });
  83. }
  84. [Test]
  85. public void DirtyRegionOrdering([Values] bool smart)
  86. {
  87. const int pageCount = 32;
  88. IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * pageCount, PageSize);
  89. Random random = new Random();
  90. PreparePages(handle, pageCount);
  91. IEnumerable<int> halfRange = Enumerable.Range(0, pageCount / 2);
  92. List<int> odd = halfRange.Select(x => x * 2 + 1).ToList();
  93. List<int> even = halfRange.Select(x => x * 2).ToList();
  94. // Write to all the odd pages.
  95. RandomOrder(random, odd, (i) =>
  96. {
  97. _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
  98. });
  99. int oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 1);
  100. Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
  101. // Write to all the even pages.
  102. RandomOrder(random, even, (i) =>
  103. {
  104. _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
  105. });
  106. int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 0);
  107. Assert.AreEqual(evenRegionCount, pageCount / 2);
  108. }
  109. [Test]
  110. public void SequenceNumber([Values] bool smart)
  111. {
  112. // The sequence number can be used to ignore dirty flags, and defer their consumption until later.
  113. // If a user consumes a dirty flag with sequence number 1, then there is a write to the protected region,
  114. // the dirty flag will not be acknowledged until the sequence number is 2.
  115. // This is useful for situations where we know that the data was complete when the sequence number was set.
  116. // ...essentially, when that data can only be updated on a future sequence number.
  117. const int pageCount = 32;
  118. IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * pageCount, PageSize);
  119. PreparePages(handle, pageCount);
  120. Random random = new Random();
  121. IEnumerable<int> halfRange = Enumerable.Range(0, pageCount / 2);
  122. List<int> odd = halfRange.Select(x => x * 2 + 1).ToList();
  123. List<int> even = halfRange.Select(x => x * 2).ToList();
  124. // Write to all the odd pages.
  125. RandomOrder(random, odd, (i) =>
  126. {
  127. _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
  128. });
  129. int oddRegionCount = 0;
  130. // Track with sequence number 1. Future dirty flags should only be consumed with sequence number != 1.
  131. // Only track the odd pages, so the even ones don't have their sequence number set.
  132. foreach (int index in odd)
  133. {
  134. handle.QueryModified((ulong)index * PageSize, PageSize, (address, range) =>
  135. {
  136. oddRegionCount++;
  137. }, 1);
  138. }
  139. Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
  140. // Write to all pages.
  141. _tracking.VirtualMemoryEvent(0, PageSize * pageCount, true);
  142. // Only the even regions should be reported for sequence number 1.
  143. int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 0, 1);
  144. Assert.AreEqual(evenRegionCount, pageCount / 2); // Must have written to all even pages.
  145. oddRegionCount = 0;
  146. handle.QueryModified(0, PageSize * pageCount, (address, range) => { oddRegionCount++; }, 1);
  147. Assert.AreEqual(oddRegionCount, 0); // Sequence number has not changed, so found no dirty subregions.
  148. // With sequence number 2, all all pages should be reported as modified.
  149. oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 1, 2);
  150. Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
  151. }
  152. [Test]
  153. public void SmartRegionTracking()
  154. {
  155. // Smart multi region handles dynamically change their tracking granularity based on QueryMemory calls.
  156. // This can save on reprotects on larger resources.
  157. const int pageCount = 32;
  158. IMultiRegionHandle handle = GetGranular(true, 0, PageSize * pageCount, PageSize);
  159. // Query some large regions to prep the subdivision of the tracking region.
  160. int[] regionSizes = new int[] { 6, 4, 3, 2, 6, 1 };
  161. ulong address = 0;
  162. for (int i = 0; i < regionSizes.Length; i++)
  163. {
  164. int region = regionSizes[i];
  165. handle.QueryModified(address, (ulong)(PageSize * region), (address, size) => { });
  166. // There should be a gap between regions,
  167. // So that they don't combine and we can see the full effects.
  168. address += (ulong)(PageSize * (region + 1));
  169. }
  170. // Clear modified.
  171. handle.QueryModified((address, size) => { });
  172. // Trigger each region with a 1 byte write.
  173. address = 0;
  174. for (int i = 0; i < regionSizes.Length; i++)
  175. {
  176. int region = regionSizes[i];
  177. _tracking.VirtualMemoryEvent(address, 1, true);
  178. address += (ulong)(PageSize * (region + 1));
  179. }
  180. int regionInd = 0;
  181. ulong expectedAddress = 0;
  182. // Expect each region to trigger in its entirety, in address ascending order.
  183. handle.QueryModified((address, size) => {
  184. int region = regionSizes[regionInd++];
  185. Assert.AreEqual(address, expectedAddress);
  186. Assert.AreEqual(size, (ulong)(PageSize * region));
  187. expectedAddress += (ulong)(PageSize * (region + 1));
  188. });
  189. }
  190. [Test]
  191. public void DisposeMultiHandles([Values] bool smart)
  192. {
  193. // Create and initialize two overlapping Multi Region Handles, with PageSize granularity.
  194. const int pageCount = 32;
  195. const int overlapStart = 16;
  196. Assert.AreEqual((0, 0), _tracking.GetRegionCounts());
  197. IMultiRegionHandle handleLow = GetGranular(smart, 0, PageSize * pageCount, PageSize);
  198. PreparePages(handleLow, pageCount);
  199. Assert.AreEqual((pageCount, pageCount), _tracking.GetRegionCounts());
  200. IMultiRegionHandle handleHigh = GetGranular(smart, PageSize * overlapStart, PageSize * pageCount, PageSize);
  201. PreparePages(handleHigh, pageCount, PageSize * overlapStart);
  202. // Combined pages (and assuming overlapStart <= pageCount) should be pageCount after overlapStart.
  203. int totalPages = overlapStart + pageCount;
  204. Assert.AreEqual((totalPages, totalPages), _tracking.GetRegionCounts());
  205. handleLow.Dispose(); // After disposing one, the pages for the other remain.
  206. Assert.AreEqual((pageCount, pageCount), _tracking.GetRegionCounts());
  207. handleHigh.Dispose(); // After disposing the other, there are no pages left.
  208. Assert.AreEqual((0, 0), _tracking.GetRegionCounts());
  209. }
  210. }
  211. }