aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Memory.Tests/TrackingTests.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/TrackingTests.cs
parentcee712105850ac3385cd0091a923438167433f9f (diff)
Rename Ryujinx.Memory.Tests to Ryujinx.Tests.Memory
Diffstat (limited to 'src/Ryujinx.Memory.Tests/TrackingTests.cs')
-rw-r--r--src/Ryujinx.Memory.Tests/TrackingTests.cs509
1 files changed, 0 insertions, 509 deletions
diff --git a/src/Ryujinx.Memory.Tests/TrackingTests.cs b/src/Ryujinx.Memory.Tests/TrackingTests.cs
deleted file mode 100644
index eb679804..00000000
--- a/src/Ryujinx.Memory.Tests/TrackingTests.cs
+++ /dev/null
@@ -1,509 +0,0 @@
-using NUnit.Framework;
-using Ryujinx.Memory.Tracking;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Threading;
-
-namespace Ryujinx.Memory.Tests
-{
- public class TrackingTests
- {
- 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 bool TestSingleWrite(RegionHandle handle, ulong address, ulong size)
- {
- handle.Reprotect();
-
- _tracking.VirtualMemoryEvent(address, size, true);
-
- return handle.Dirty;
- }
-
- [Test]
- public void SingleRegion()
- {
- RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
- (ulong address, ulong size)? readTrackingTriggered = null;
- handle.RegisterAction((address, size) =>
- {
- readTrackingTriggered = (address, size);
- });
-
- bool dirtyInitial = handle.Dirty;
- Assert.True(dirtyInitial); // Handle starts dirty.
-
- handle.Reprotect();
-
- bool dirtyAfterReprotect = handle.Dirty;
- Assert.False(dirtyAfterReprotect); // Handle is no longer dirty.
-
- _tracking.VirtualMemoryEvent(PageSize * 2, 4, true);
- _tracking.VirtualMemoryEvent(PageSize * 2, 4, false);
-
- bool dirtyAfterUnrelatedReadWrite = handle.Dirty;
- Assert.False(dirtyAfterUnrelatedReadWrite); // Not dirtied, as the write was to an unrelated address.
-
- Assert.IsNull(readTrackingTriggered); // Hasn't been triggered yet
-
- _tracking.VirtualMemoryEvent(0, 4, false);
-
- bool dirtyAfterRelatedRead = handle.Dirty;
- Assert.False(dirtyAfterRelatedRead); // Only triggers on write.
- Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
-
- readTrackingTriggered = null;
- _tracking.VirtualMemoryEvent(0, 4, true);
-
- bool dirtyAfterRelatedWrite = handle.Dirty;
- Assert.True(dirtyAfterRelatedWrite); // Dirty flag should now be set.
-
- _tracking.VirtualMemoryEvent(4, 4, true);
- bool dirtyAfterRelatedWrite2 = handle.Dirty;
- Assert.True(dirtyAfterRelatedWrite2); // Dirty flag should still be set.
-
- handle.Reprotect();
-
- bool dirtyAfterReprotect2 = handle.Dirty;
- Assert.False(dirtyAfterReprotect2); // Handle is no longer dirty.
-
- handle.Dispose();
-
- bool dirtyAfterDispose = TestSingleWrite(handle, 0, 4);
- Assert.False(dirtyAfterDispose); // Handle cannot be triggered when disposed
- }
-
- [Test]
- public void OverlappingRegions()
- {
- RegionHandle allHandle = _tracking.BeginTracking(0, PageSize * 16, 0);
- allHandle.Reprotect();
-
- (ulong address, ulong size)? readTrackingTriggeredAll = null;
- Action registerReadAction = () =>
- {
- readTrackingTriggeredAll = null;
- allHandle.RegisterAction((address, size) =>
- {
- readTrackingTriggeredAll = (address, size);
- });
- };
- registerReadAction();
-
- // Create 16 page sized handles contained within the allHandle.
- RegionHandle[] containedHandles = new RegionHandle[16];
-
- for (int i = 0; i < 16; i++)
- {
- containedHandles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0);
- containedHandles[i].Reprotect();
- }
-
- for (int i = 0; i < 16; i++)
- {
- // No handles are dirty.
- Assert.False(allHandle.Dirty);
- Assert.IsNull(readTrackingTriggeredAll);
- for (int j = 0; j < 16; j++)
- {
- Assert.False(containedHandles[j].Dirty);
- }
-
- _tracking.VirtualMemoryEvent((ulong)i * PageSize, 1, true);
-
- // Only the handle covering the entire range and the relevant contained handle are dirty.
- Assert.True(allHandle.Dirty);
- Assert.AreEqual(readTrackingTriggeredAll, ((ulong)i * PageSize, 1UL)); // Triggered read tracking
- for (int j = 0; j < 16; j++)
- {
- if (j == i)
- {
- Assert.True(containedHandles[j].Dirty);
- }
- else
- {
- Assert.False(containedHandles[j].Dirty);
- }
- }
-
- // Clear flags and reset read action.
- registerReadAction();
- allHandle.Reprotect();
- containedHandles[i].Reprotect();
- }
- }
-
- [Test]
- public void PageAlignment(
- [Values(1ul, 512ul, 2048ul, 4096ul, 65536ul)] [Random(1ul, 65536ul, RndCnt)] ulong address,
- [Values(1ul, 4ul, 1024ul, 4096ul, 65536ul)] [Random(1ul, 65536ul, RndCnt)] ulong size)
- {
- ulong alignedStart = (address / PageSize) * PageSize;
- ulong alignedEnd = ((address + size + PageSize - 1) / PageSize) * PageSize;
- ulong alignedSize = alignedEnd - alignedStart;
-
- RegionHandle handle = _tracking.BeginTracking(address, size, 0);
-
- // Anywhere inside the pages the region is contained on should trigger.
-
- bool originalRangeTriggers = TestSingleWrite(handle, address, size);
- Assert.True(originalRangeTriggers);
-
- bool alignedRangeTriggers = TestSingleWrite(handle, alignedStart, alignedSize);
- Assert.True(alignedRangeTriggers);
-
- bool alignedStartTriggers = TestSingleWrite(handle, alignedStart, 1);
- Assert.True(alignedStartTriggers);
-
- bool alignedEndTriggers = TestSingleWrite(handle, alignedEnd - 1, 1);
- Assert.True(alignedEndTriggers);
-
- // Outside the tracked range should not trigger.
-
- bool alignedBeforeTriggers = TestSingleWrite(handle, alignedStart - 1, 1);
- Assert.False(alignedBeforeTriggers);
-
- bool alignedAfterTriggers = TestSingleWrite(handle, alignedEnd, 1);
- Assert.False(alignedAfterTriggers);
- }
-
- [Test, Explicit, Timeout(1000)]
- public void Multithreading()
- {
- // Multithreading sanity test
- // Multiple threads can easily read/write memory regions from any existing handle.
- // Handles can also be owned by different threads, though they should have one owner thread.
- // Handles can be created and disposed at any time, by any thread.
-
- // This test should not throw or deadlock due to invalid state.
-
- const int threadCount = 1;
- const int handlesPerThread = 16;
- long finishedTime = 0;
-
- RegionHandle[] handles = new RegionHandle[threadCount * handlesPerThread];
- Random globalRand = new Random();
-
- for (int i = 0; i < handles.Length; i++)
- {
- handles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0);
- handles[i].Reprotect();
- }
-
- List<Thread> testThreads = new List<Thread>();
-
- // Dirty flag consumer threads
- int dirtyFlagReprotects = 0;
- for (int i = 0; i < threadCount; i++)
- {
- int randSeed = i;
- testThreads.Add(new Thread(() =>
- {
- int handleBase = randSeed * handlesPerThread;
- while (Stopwatch.GetTimestamp() < finishedTime)
- {
- Random random = new Random(randSeed);
- RegionHandle handle = handles[handleBase + random.Next(handlesPerThread)];
-
- if (handle.Dirty)
- {
- handle.Reprotect();
- Interlocked.Increment(ref dirtyFlagReprotects);
- }
- }
- }));
- }
-
- // Write trigger threads
- int writeTriggers = 0;
- for (int i = 0; i < threadCount; i++)
- {
- int randSeed = i;
- testThreads.Add(new Thread(() =>
- {
- Random random = new Random(randSeed);
- ulong handleBase = (ulong)(randSeed * handlesPerThread * PageSize);
- while (Stopwatch.GetTimestamp() < finishedTime)
- {
- _tracking.VirtualMemoryEvent(handleBase + (ulong)random.Next(PageSize * handlesPerThread), PageSize / 2, true);
- Interlocked.Increment(ref writeTriggers);
- }
- }));
- }
-
- // Handle create/delete threads
- int handleLifecycles = 0;
- for (int i = 0; i < threadCount; i++)
- {
- int randSeed = i;
- testThreads.Add(new Thread(() =>
- {
- int maxAddress = threadCount * handlesPerThread * PageSize;
- Random random = new Random(randSeed + 512);
- while (Stopwatch.GetTimestamp() < finishedTime)
- {
- RegionHandle handle = _tracking.BeginTracking((ulong)random.Next(maxAddress), (ulong)random.Next(65536), 0);
-
- handle.Dispose();
-
- Interlocked.Increment(ref handleLifecycles);
- }
- }));
- }
-
- finishedTime = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 2; // Run for 500ms;
-
- foreach (Thread thread in testThreads)
- {
- thread.Start();
- }
-
- foreach (Thread thread in testThreads)
- {
- thread.Join();
- }
-
- Assert.Greater(dirtyFlagReprotects, 10);
- Assert.Greater(writeTriggers, 10);
- Assert.Greater(handleLifecycles, 10);
- }
-
- [Test]
- public void ReadActionThreadConsumption()
- {
- // Read actions should only be triggered once for each registration.
- // The implementation should use an interlocked exchange to make sure other threads can't get the action.
-
- RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
-
- int triggeredCount = 0;
- int registeredCount = 0;
- int signalThreadsDone = 0;
- bool isRegistered = false;
-
- Action registerReadAction = () =>
- {
- registeredCount++;
- handle.RegisterAction((address, size) =>
- {
- isRegistered = false;
- Interlocked.Increment(ref triggeredCount);
- });
- };
-
- const int threadCount = 16;
- const int iterationCount = 10000;
- Thread[] signalThreads = new Thread[threadCount];
-
- for (int i = 0; i < threadCount; i++)
- {
- int randSeed = i;
- signalThreads[i] = new Thread(() =>
- {
- Random random = new Random(randSeed);
- for (int j = 0; j < iterationCount; j++)
- {
- _tracking.VirtualMemoryEvent((ulong)random.Next(PageSize), 4, false);
- }
- Interlocked.Increment(ref signalThreadsDone);
- });
- }
-
- for (int i = 0; i < threadCount; i++)
- {
- signalThreads[i].Start();
- }
-
- while (signalThreadsDone != -1)
- {
- if (signalThreadsDone == threadCount)
- {
- signalThreadsDone = -1;
- }
-
- if (!isRegistered)
- {
- isRegistered = true;
- registerReadAction();
- }
- }
-
- // The action should trigger exactly once for every registration,
- // then we register once after all the threads signalling it cease.
- Assert.AreEqual(registeredCount, triggeredCount + 1);
- }
-
- [Test]
- public void DisposeHandles()
- {
- // Ensure that disposed handles correctly remove their virtual and physical regions.
-
- RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
- handle.Reprotect();
-
- Assert.AreEqual(1, _tracking.GetRegionCount());
-
- handle.Dispose();
-
- Assert.AreEqual(0, _tracking.GetRegionCount());
-
- // Two handles, small entirely contains big.
- // We expect there to be three regions after creating both, one for the small region and two covering the big one around it.
- // Regions are always split to avoid overlapping, which is why there are three instead of two.
-
- RegionHandle handleSmall = _tracking.BeginTracking(PageSize, PageSize, 0);
- RegionHandle handleBig = _tracking.BeginTracking(0, PageSize * 4, 0);
-
- Assert.AreEqual(3, _tracking.GetRegionCount());
-
- // After disposing the big region, only the small one will remain.
- handleBig.Dispose();
-
- Assert.AreEqual(1, _tracking.GetRegionCount());
-
- handleSmall.Dispose();
-
- Assert.AreEqual(0, _tracking.GetRegionCount());
- }
-
- [Test]
- public void ReadAndWriteProtection()
- {
- MemoryPermission protection = MemoryPermission.ReadAndWrite;
-
- _memoryManager.OnProtect += (va, size, newProtection) =>
- {
- Assert.AreEqual((0, PageSize), (va, size)); // Should protect the exact region all the operations use.
- protection = newProtection;
- };
-
- RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
-
- // After creating the handle, there is no protection yet.
- Assert.AreEqual(MemoryPermission.ReadAndWrite, protection);
-
- bool dirtyInitial = handle.Dirty;
- Assert.True(dirtyInitial); // Handle starts dirty.
-
- handle.Reprotect();
-
- // After a reprotect, there is write protection, which will set a dirty flag when any write happens.
- Assert.AreEqual(MemoryPermission.Read, protection);
-
- (ulong address, ulong size)? readTrackingTriggered = null;
- handle.RegisterAction((address, size) =>
- {
- readTrackingTriggered = (address, size);
- });
-
- // Registering an action adds read/write protection.
- Assert.AreEqual(MemoryPermission.None, protection);
-
- bool dirtyAfterReprotect = handle.Dirty;
- Assert.False(dirtyAfterReprotect); // Handle is no longer dirty.
-
- // First we should read, which will trigger the action. This _should not_ remove write protection on the memory.
-
- _tracking.VirtualMemoryEvent(0, 4, false);
-
- bool dirtyAfterRead = handle.Dirty;
- Assert.False(dirtyAfterRead); // Not dirtied, as this was a read.
-
- Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered.
-
- Assert.AreEqual(MemoryPermission.Read, protection); // Write protection is still present.
-
- readTrackingTriggered = null;
-
- // Now, perform a write.
-
- _tracking.VirtualMemoryEvent(0, 4, true);
-
- bool dirtyAfterWriteAfterRead = handle.Dirty;
- Assert.True(dirtyAfterWriteAfterRead); // Should be dirty.
-
- Assert.AreEqual(MemoryPermission.ReadAndWrite, protection); // All protection is now be removed from the memory.
-
- Assert.IsNull(readTrackingTriggered); // Read tracking was removed when the action fired, as it can only fire once.
-
- handle.Dispose();
- }
-
- [Test]
- public void PreciseAction()
- {
- RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
-
- (ulong address, ulong size, bool write)? preciseTriggered = null;
- handle.RegisterPreciseAction((address, size, write) =>
- {
- preciseTriggered = (address, size, write);
-
- return true;
- });
-
- (ulong address, ulong size)? readTrackingTriggered = null;
- handle.RegisterAction((address, size) =>
- {
- readTrackingTriggered = (address, size);
- });
-
- handle.Reprotect();
-
- _tracking.VirtualMemoryEvent(0, 4, false, precise: true);
-
- Assert.IsNull(readTrackingTriggered); // Hasn't been triggered - precise action returned true.
- Assert.AreEqual(preciseTriggered, (0UL, 4UL, false)); // Precise action was triggered.
-
- _tracking.VirtualMemoryEvent(0, 4, true, precise: true);
-
- Assert.IsNull(readTrackingTriggered); // Still hasn't been triggered.
- bool dirtyAfterPreciseActionTrue = handle.Dirty;
- Assert.False(dirtyAfterPreciseActionTrue); // Not dirtied - precise action returned true.
- Assert.AreEqual(preciseTriggered, (0UL, 4UL, true)); // Precise action was triggered.
-
- // Handle is now dirty.
- handle.Reprotect(true);
- preciseTriggered = null;
-
- _tracking.VirtualMemoryEvent(4, 4, true, precise: true);
- Assert.AreEqual(preciseTriggered, (4UL, 4UL, true)); // Precise action was triggered even though handle was dirty.
-
- handle.Reprotect();
- handle.RegisterPreciseAction((address, size, write) =>
- {
- preciseTriggered = (address, size, write);
-
- return false; // Now, we return false, which indicates that the regular read/write behaviours should trigger.
- });
-
- _tracking.VirtualMemoryEvent(8, 4, true, precise: true);
-
- Assert.AreEqual(readTrackingTriggered, (8UL, 4UL)); // Read action triggered, as precise action returned false.
- bool dirtyAfterPreciseActionFalse = handle.Dirty;
- Assert.True(dirtyAfterPreciseActionFalse); // Dirtied, as precise action returned false.
- Assert.AreEqual(preciseTriggered, (8UL, 4UL, true)); // Precise action was triggered.
- }
- }
-}