aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-26 04:34:16 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commit609abc8b9b3c05a63bef94c2133550b3c07f97b0 (patch)
tree2d1e27f0256c8f411ad55de9ccbb3d598f6c7237 /src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
parentcee712105850ac3385cd0091a923438167433f9f (diff)
Rename Ryujinx.Memory.Tests to Ryujinx.Tests.Memory
Diffstat (limited to 'src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs')
-rw-r--r--src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs439
1 files changed, 0 insertions, 439 deletions
diff --git a/src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs b/src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
deleted file mode 100644
index 38cb4921..00000000
--- a/src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
+++ /dev/null
@@ -1,439 +0,0 @@
-using NUnit.Framework;
-using Ryujinx.Memory.Tracking;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Ryujinx.Memory.Tests
-{
- public class MultiRegionTrackingTests
- {
- private const int RndCnt = 3;
-
- private const ulong MemorySize = 0x8000;
- private const int PageSize = 4096;
-
- private MemoryBlock _memoryBlock;
- private MemoryTracking _tracking;
- private MockVirtualMemoryManager _memoryManager;
-
- [SetUp]
- public void Setup()
- {
- _memoryBlock = new MemoryBlock(MemorySize);
- _memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize);
- _tracking = new MemoryTracking(_memoryManager, PageSize);
- }
-
- [TearDown]
- public void Teardown()
- {
- _memoryBlock.Dispose();
- }
-
- private IMultiRegionHandle GetGranular(bool smart, ulong address, ulong size, ulong granularity)
- {
- return smart ?
- _tracking.BeginSmartGranularTracking(address, size, granularity, 0) :
- (IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, null, granularity, 0);
- }
-
- private void RandomOrder(Random random, List<int> indices, Action<int> action)
- {
- List<int> choices = indices.ToList();
-
- while (choices.Count > 0)
- {
- int choice = random.Next(choices.Count);
- action(choices[choice]);
- choices.RemoveAt(choice);
- }
- }
-
- private int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func<ulong, bool> addressPredicate)
- {
- int regionCount = 0;
- ulong lastAddress = startAddress;
-
- handle.QueryModified(startAddress, size, (address, range) =>
- {
- Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
- Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
- lastAddress = address;
- regionCount++;
- });
-
- return regionCount;
- }
-
- private int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func<ulong, bool> addressPredicate, int sequenceNumber)
- {
- int regionCount = 0;
- ulong lastAddress = startAddress;
-
- handle.QueryModified(startAddress, size, (address, range) =>
- {
- Assert.IsTrue(addressPredicate(address)); // Written pages must be even.
- Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order.
- lastAddress = address;
- regionCount++;
- }, sequenceNumber);
-
- return regionCount;
- }
-
- private void PreparePages(IMultiRegionHandle handle, int pageCount, ulong address = 0)
- {
- Random random = new Random();
-
- // Make sure the list has minimum granularity (smart region changes granularity based on requested ranges)
- RandomOrder(random, Enumerable.Range(0, pageCount).ToList(), (i) =>
- {
- ulong resultAddress = ulong.MaxValue;
- handle.QueryModified((ulong)i * PageSize + address, PageSize, (address, range) =>
- {
- resultAddress = address;
- });
- Assert.AreEqual(resultAddress, (ulong)i * PageSize + address);
- });
- }
-
- [Test]
- public void DirtyRegionOrdering([Values] bool smart)
- {
- const int pageCount = 32;
- IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * pageCount, PageSize);
-
- Random random = new Random();
-
- PreparePages(handle, pageCount);
-
- IEnumerable<int> halfRange = Enumerable.Range(0, pageCount / 2);
- List<int> odd = halfRange.Select(x => x * 2 + 1).ToList();
- List<int> even = halfRange.Select(x => x * 2).ToList();
-
- // Write to all the odd pages.
- RandomOrder(random, odd, (i) =>
- {
- _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
- });
-
- int oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 1);
-
- Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
-
- // Write to all the even pages.
- RandomOrder(random, even, (i) =>
- {
- _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
- });
-
- int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 0);
-
- Assert.AreEqual(evenRegionCount, pageCount / 2);
- }
-
- [Test]
- public void SequenceNumber([Values] bool smart)
- {
- // The sequence number can be used to ignore dirty flags, and defer their consumption until later.
- // If a user consumes a dirty flag with sequence number 1, then there is a write to the protected region,
- // the dirty flag will not be acknowledged until the sequence number is 2.
-
- // This is useful for situations where we know that the data was complete when the sequence number was set.
- // ...essentially, when that data can only be updated on a future sequence number.
-
- const int pageCount = 32;
- IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * pageCount, PageSize);
-
- PreparePages(handle, pageCount);
-
- Random random = new Random();
-
- IEnumerable<int> halfRange = Enumerable.Range(0, pageCount / 2);
- List<int> odd = halfRange.Select(x => x * 2 + 1).ToList();
- List<int> even = halfRange.Select(x => x * 2).ToList();
-
- // Write to all the odd pages.
- RandomOrder(random, odd, (i) =>
- {
- _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true);
- });
-
- int oddRegionCount = 0;
-
- // Track with sequence number 1. Future dirty flags should only be consumed with sequence number != 1.
- // Only track the odd pages, so the even ones don't have their sequence number set.
-
- foreach (int index in odd)
- {
- handle.QueryModified((ulong)index * PageSize, PageSize, (address, range) =>
- {
- oddRegionCount++;
- }, 1);
- }
-
- Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
-
- // Write to all pages.
-
- _tracking.VirtualMemoryEvent(0, PageSize * pageCount, true);
-
- // Only the even regions should be reported for sequence number 1.
-
- int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 0, 1);
-
- Assert.AreEqual(evenRegionCount, pageCount / 2); // Must have written to all even pages.
-
- oddRegionCount = 0;
-
- handle.QueryModified(0, PageSize * pageCount, (address, range) => { oddRegionCount++; }, 1);
-
- Assert.AreEqual(oddRegionCount, 0); // Sequence number has not changed, so found no dirty subregions.
-
- // With sequence number 2, all all pages should be reported as modified.
-
- oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * pageCount, (address) => (address / PageSize) % 2 == 1, 2);
-
- Assert.AreEqual(oddRegionCount, pageCount / 2); // Must have written to all odd pages.
- }
-
- [Test]
- public void SmartRegionTracking()
- {
- // Smart multi region handles dynamically change their tracking granularity based on QueryMemory calls.
- // This can save on reprotects on larger resources.
-
- const int pageCount = 32;
- IMultiRegionHandle handle = GetGranular(true, 0, PageSize * pageCount, PageSize);
-
- // Query some large regions to prep the subdivision of the tracking region.
-
- int[] regionSizes = new int[] { 6, 4, 3, 2, 6, 1 };
- ulong address = 0;
-
- for (int i = 0; i < regionSizes.Length; i++)
- {
- int region = regionSizes[i];
- handle.QueryModified(address, (ulong)(PageSize * region), (address, size) => { });
-
- // There should be a gap between regions,
- // So that they don't combine and we can see the full effects.
- address += (ulong)(PageSize * (region + 1));
- }
-
- // Clear modified.
- handle.QueryModified((address, size) => { });
-
- // Trigger each region with a 1 byte write.
- address = 0;
-
- for (int i = 0; i < regionSizes.Length; i++)
- {
- int region = regionSizes[i];
- _tracking.VirtualMemoryEvent(address, 1, true);
- address += (ulong)(PageSize * (region + 1));
- }
-
- int regionInd = 0;
- ulong expectedAddress = 0;
-
- // Expect each region to trigger in its entirety, in address ascending order.
- handle.QueryModified((address, size) => {
- int region = regionSizes[regionInd++];
-
- Assert.AreEqual(address, expectedAddress);
- Assert.AreEqual(size, (ulong)(PageSize * region));
-
- expectedAddress += (ulong)(PageSize * (region + 1));
- });
- }
-
- [Test]
- public void DisposeMultiHandles([Values] bool smart)
- {
- // Create and initialize two overlapping Multi Region Handles, with PageSize granularity.
- const int pageCount = 32;
- const int overlapStart = 16;
-
- Assert.AreEqual(0, _tracking.GetRegionCount());
-
- IMultiRegionHandle handleLow = GetGranular(smart, 0, PageSize * pageCount, PageSize);
- PreparePages(handleLow, pageCount);
-
- Assert.AreEqual(pageCount, _tracking.GetRegionCount());
-
- IMultiRegionHandle handleHigh = GetGranular(smart, PageSize * overlapStart, PageSize * pageCount, PageSize);
- PreparePages(handleHigh, pageCount, PageSize * overlapStart);
-
- // Combined pages (and assuming overlapStart <= pageCount) should be pageCount after overlapStart.
- int totalPages = overlapStart + pageCount;
-
- Assert.AreEqual(totalPages, _tracking.GetRegionCount());
-
- handleLow.Dispose(); // After disposing one, the pages for the other remain.
-
- Assert.AreEqual(pageCount, _tracking.GetRegionCount());
-
- handleHigh.Dispose(); // After disposing the other, there are no pages left.
-
- Assert.AreEqual(0, _tracking.GetRegionCount());
- }
-
- [Test]
- public void InheritHandles()
- {
- // Test merging the following into a granular region handle:
- // - 3x gap (creates new granular handles)
- // - 3x from multiregion: not dirty, dirty and with action
- // - 2x gap
- // - 3x single page: not dirty, dirty and with action
- // - 3x two page: not dirty, dirty and with action (handle is not reused, but its state is copied to the granular handles)
- // - 1x gap
- // For a total of 18 pages.
-
- bool[] actionsTriggered = new bool[3];
-
- MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0);
- PreparePages(granular, 3, PageSize * 3);
-
- // Write to the second handle in the multiregion.
- _tracking.VirtualMemoryEvent(PageSize * 4, PageSize, true);
-
- // Add an action to the third handle in the multiregion.
- granular.RegisterAction(PageSize * 5, PageSize, (_, _) => { actionsTriggered[0] = true; });
-
- RegionHandle[] singlePages = new RegionHandle[3];
-
- for (int i = 0; i < 3; i++)
- {
- singlePages[i] = _tracking.BeginTracking(PageSize * (8 + (ulong)i), PageSize, 0);
- singlePages[i].Reprotect();
- }
-
- // Write to the second handle.
- _tracking.VirtualMemoryEvent(PageSize * 9, PageSize, true);
-
- // Add an action to the third handle.
- singlePages[2].RegisterAction((_, _) => { actionsTriggered[1] = true; });
-
- RegionHandle[] doublePages = new RegionHandle[3];
-
- for (int i = 0; i < 3; i++)
- {
- doublePages[i] = _tracking.BeginTracking(PageSize * (11 + (ulong)i * 2), PageSize * 2, 0);
- doublePages[i].Reprotect();
- }
-
- // Write to the second handle.
- _tracking.VirtualMemoryEvent(PageSize * 13, PageSize * 2, true);
-
- // Add an action to the third handle.
- doublePages[2].RegisterAction((_, _) => { actionsTriggered[2] = true; });
-
- // Finally, create a granular handle that inherits all these handles.
-
- IEnumerable<IRegionHandle>[] handleGroups = new IEnumerable<IRegionHandle>[]
- {
- granular.GetHandles(),
- singlePages,
- doublePages
- };
-
- MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize, 0);
-
- bool[] expectedDirty = new bool[]
- {
- true, true, true, // Gap.
- false, true, false, // Multi-region.
- true, true, // Gap.
- false, true, false, // Individual handles.
- false, false, true, true, false, false, // Double size handles.
- true // Gap.
- };
-
- for (int i = 0; i < 18; i++)
- {
- bool modified = false;
- combined.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { modified = true; });
-
- Assert.AreEqual(expectedDirty[i], modified);
- }
-
- Assert.AreEqual(new bool[3], actionsTriggered);
-
- _tracking.VirtualMemoryEvent(PageSize * 5, PageSize, false);
- Assert.IsTrue(actionsTriggered[0]);
-
- _tracking.VirtualMemoryEvent(PageSize * 10, PageSize, false);
- Assert.IsTrue(actionsTriggered[1]);
-
- _tracking.VirtualMemoryEvent(PageSize * 15, PageSize, false);
- Assert.IsTrue(actionsTriggered[2]);
-
- // The double page handles should be disposed, as they were split into granular handles.
- foreach (RegionHandle doublePage in doublePages)
- {
- // These should have been disposed.
- bool throws = false;
-
- try
- {
- doublePage.Dispose();
- }
- catch (ObjectDisposedException)
- {
- throws = true;
- }
-
- Assert.IsTrue(throws);
- }
-
- IEnumerable<IRegionHandle> combinedHandles = combined.GetHandles();
-
- Assert.AreEqual(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3));
- Assert.AreEqual(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4));
- Assert.AreEqual(handleGroups[0].ElementAt(2), combinedHandles.ElementAt(5));
-
- Assert.AreEqual(singlePages[0], combinedHandles.ElementAt(8));
- Assert.AreEqual(singlePages[1], combinedHandles.ElementAt(9));
- Assert.AreEqual(singlePages[2], combinedHandles.ElementAt(10));
- }
-
- [Test]
- public void PreciseAction()
- {
- bool actionTriggered = false;
-
- MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0);
- PreparePages(granular, 3, PageSize * 3);
-
- // Add a precise action to the second and third handle in the multiregion.
- granular.RegisterPreciseAction(PageSize * 4, PageSize * 2, (_, _, _) => { actionTriggered = true; return true; });
-
- // Precise write to first handle in the multiregion.
- _tracking.VirtualMemoryEvent(PageSize * 3, PageSize, true, precise: true);
- Assert.IsFalse(actionTriggered); // Action not triggered.
-
- bool firstPageModified = false;
- granular.QueryModified(PageSize * 3, PageSize, (_, _) => { firstPageModified = true; });
- Assert.IsTrue(firstPageModified); // First page is modified.
-
- // Precise write to all handles in the multiregion.
- _tracking.VirtualMemoryEvent(PageSize * 3, PageSize * 3, true, precise: true);
-
- bool[] pagesModified = new bool[3];
-
- for (int i = 3; i < 6; i++)
- {
- int index = i - 3;
- granular.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { pagesModified[index] = true; });
- }
-
- Assert.IsTrue(actionTriggered); // Action triggered.
-
- // Precise writes are ignored on two later handles due to the action returning true.
- Assert.AreEqual(pagesModified, new bool[] { true, false, false });
- }
- }
-}