From d8c384b073d7abee69393d4f9bea8371aec36ca5 Mon Sep 17 00:00:00 2001
From: Jim Horvath <38920027+jhorv@users.noreply.github.com>
Date: Wed, 7 Feb 2024 13:59:42 -0500
Subject: [PATCH] add PagedMemoryRange enumerator types, use them in
IVirtualMemoryManager implementations to consolidate page-handling logic and
add a new capability - the coalescing of pages for consolidating memory
copies and segmentation.
---
src/Ryujinx.Common/Memory/ByteMemoryPool.cs | 2 +-
src/Ryujinx.Memory/IVirtualMemoryManager.cs | 2 +-
.../PagedMemoryRangeCoalescingEnumerator.cs | 59 ++++++++
.../Range/PagedMemoryRangeEnumerator.cs | 68 ++++++++++
...gedMemoryRangeCoalescingEnumeratorTests.cs | 93 +++++++++++++
.../PagedMemoryRangeEnumeratorTests.cs | 127 ++++++++++++++++++
6 files changed, 349 insertions(+), 2 deletions(-)
create mode 100644 src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs
create mode 100644 src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
create mode 100644 src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs
create mode 100644 src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs
diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
index 53bbee4fa6..c7bcb84076 100644
--- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
+++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
@@ -78,7 +78,7 @@ namespace Ryujinx.Common.Memory
return buffer;
}
-
+
///
/// Copies into a newly rented byte memory buffer.
///
diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
index a94be42f73..c3bea626dd 100644
--- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -123,7 +123,7 @@ namespace Ryujinx.Memory
writableRegion.Memory.Span.Fill(value);
}
}
-
+
///
/// Gets a read-only sequence of read-only memory blocks from CPU mapped memory.
///
diff --git a/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs b/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs
new file mode 100644
index 0000000000..65a52a3026
--- /dev/null
+++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace Ryujinx.Memory.Range
+{
+ public struct PagedMemoryRangeCoalescingEnumerator
+ {
+ private PagedMemoryRangeEnumerator _enumerator;
+ private MemoryRange? _current;
+
+ public PagedMemoryRangeCoalescingEnumerator(ulong startAddress, int size, int pageSize, Func mapAddress)
+ {
+ _enumerator = new PagedMemoryRangeEnumerator(startAddress, size, pageSize, mapAddress);
+ }
+
+ public MemoryRange Current => _current!.Value;
+
+ ///
+ /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
+ ///
+ public PagedMemoryRangeCoalescingEnumerator GetEnumerator() => this;
+
+ public bool MoveNext()
+ {
+ if (_current is null)
+ {
+ if (_enumerator.MoveNext() == false)
+ {
+ _current = null;
+ return false;
+ }
+ }
+
+ if (_enumerator.HasCurrent)
+ {
+ MemoryRange combinedRange = _enumerator.Current;
+
+ while (_enumerator.MoveNext())
+ {
+ MemoryRange nextRange = _enumerator.Current;
+
+ if (combinedRange.EndAddress == nextRange.Address)
+ {
+ combinedRange = new MemoryRange(combinedRange.Address, combinedRange.Size + nextRange.Size);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ _current = combinedRange;
+ return true;
+ }
+
+ _current = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs b/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
new file mode 100644
index 0000000000..7f3d76d3cf
--- /dev/null
+++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
@@ -0,0 +1,68 @@
+using System;
+
+namespace Ryujinx.Memory.Range
+{
+ public struct PagedMemoryRangeEnumerator
+ {
+ private readonly ulong _startAddress;
+ private readonly int _size;
+ private readonly int _pageSize;
+ private readonly ulong _pageMask;
+ private readonly Func _mapAddress;
+ private int _offset;
+ private MemoryRange? _current;
+
+ public PagedMemoryRangeEnumerator(ulong startAddress, int size, int pageSize, Func mapAddress)
+ {
+ _startAddress = startAddress;
+ _size = size;
+ _pageSize = pageSize;
+ _pageMask = (ulong)pageSize - 1;
+ _mapAddress = mapAddress;
+ _offset = 0;
+ }
+
+ public MemoryRange Current => _current!.Value;
+
+ internal bool HasCurrent => _current.HasValue;
+
+ ///
+ /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
+ ///
+ public PagedMemoryRangeEnumerator GetEnumerator() => this;
+
+ public bool MoveNext()
+ {
+ if (_offset == 0 && (_startAddress & _pageMask) != 0)
+ {
+ ulong rangeAddress = _mapAddress(_startAddress);
+
+ int rangeSize = Math.Min(_size, _pageSize - (int)(_startAddress & _pageMask));
+
+ SetCurrent(rangeAddress, rangeSize);
+
+ return true;
+ }
+
+ if (_offset < _size)
+ {
+ ulong rangeAddress = _mapAddress(_startAddress + (ulong)_offset);
+
+ int rangeSize = Math.Min(_size - _offset, _pageSize);
+
+ SetCurrent(rangeAddress, rangeSize);
+
+ return true;
+ }
+
+ _current = null;
+ return false;
+ }
+
+ private void SetCurrent(ulong address, int size)
+ {
+ _current = new MemoryRange(address, (ulong)size);
+ _offset += size;
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs
new file mode 100644
index 0000000000..be01975fdd
--- /dev/null
+++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs
@@ -0,0 +1,93 @@
+using NUnit.Framework;
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Tests.Memory.RangeTests
+{
+ public class PagedMemoryRangeCoalescingEnumeratorTests
+ {
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For3AlignedPages_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 12;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For2PagesWithPartialFirstAndLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 6;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For4PagesWithPartialFirst_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeCoalescingEnumerator_For4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(1, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(Size, results[0].Size);
+ }
+ }
+}
diff --git a/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs
new file mode 100644
index 0000000000..cadc64f829
--- /dev/null
+++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs
@@ -0,0 +1,127 @@
+using NUnit.Framework;
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Tests.Memory.RangeTests
+{
+ public class PagedMemoryRangeEnumeratorTests
+ {
+ [Test]
+ public void PagedMemoryRangeEnumerator_For3AlignedPages_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 12;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(3, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize, results[0].Size);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For2PagesWithPartialFirstAndLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 6;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(2, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(PageSize - StartAddress, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For4PagesWithPartialFirst_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 2;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(4, results.Count);
+
+ Assert.AreEqual(StartAddress, results[0].Address);
+ Assert.AreEqual(PageSize - StartAddress, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ Assert.AreEqual(results[2].Address, results[1].EndAddress);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ Assert.AreEqual(results[3].Address, results[2].EndAddress);
+
+ Assert.AreEqual(12, results[3].Address);
+ Assert.AreEqual(PageSize, results[3].Size);
+ }
+
+ [Test]
+ public void PagedMemoryRangeEnumerator_For4PagesWithPartialLast_HasCorrectResults()
+ {
+ // Arrange
+ const int StartAddress = 0;
+ const int Size = 14;
+ const int PageSize = 4;
+ var enumerator = new PagedMemoryRangeEnumerator(StartAddress, Size, PageSize, x => x);
+
+ // Act
+ var results = new List();
+ foreach (var memoryRange in enumerator)
+ results.Add(memoryRange);
+
+ // Assert
+ Assert.AreEqual(4, results.Count);
+
+ Assert.AreEqual(0, results[0].Address);
+ Assert.AreEqual(PageSize, results[0].Size);
+ Assert.AreEqual(results[1].Address, results[0].EndAddress);
+
+ Assert.AreEqual(4, results[1].Address);
+ Assert.AreEqual(PageSize, results[1].Size);
+ Assert.AreEqual(results[2].Address, results[1].EndAddress);
+
+ Assert.AreEqual(8, results[2].Address);
+ Assert.AreEqual(PageSize, results[2].Size);
+ Assert.AreEqual(results[3].Address, results[2].EndAddress);
+
+ Assert.AreEqual(12, results[3].Address);
+ Assert.AreEqual(Size % PageSize, results[3].Size);
+ }
+ }
+}