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); + } + } +}