diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs index 6fd6a98aa7..c7bcb84076 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs @@ -93,6 +93,20 @@ namespace Ryujinx.Common.Memory return copy; } + /// + /// Copies into a newly rented byte memory buffer. + /// + /// The byte buffer to copy + /// A wrapping the rented memory with copied to it + public static IMemoryOwner RentCopy(ReadOnlySpan buffer) + { + var copy = RentImpl(buffer.Length); + + buffer.CopyTo(copy.Memory.Span); + + return copy; + } + private static ByteMemoryPoolBuffer RentImpl(int length) { if ((uint)length > Array.MaxLength) diff --git a/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs b/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs new file mode 100644 index 0000000000..9c4cd1ff36 --- /dev/null +++ b/src/Ryujinx.Common/Memory/BytesReadOnlySequenceSegment.cs @@ -0,0 +1,26 @@ +using System; +using System.Buffers; + +namespace Ryujinx.Common.Memory +{ + /// + /// A concrete implementation of , + /// with methods to help build a full sequence. + /// + public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment + { + public BytesReadOnlySequenceSegment(Memory memory) => Memory = memory; + + public BytesReadOnlySequenceSegment Append(Memory memory) + { + var nextSegment = new BytesReadOnlySequenceSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = nextSegment; + + return nextSegment; + } + } +} diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 102cedc94e..4155bec89c 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -134,6 +134,16 @@ namespace Ryujinx.Memory /// Throw for unhandled invalid or unmapped memory accesses ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false); + /// + /// Gets a read-only sequence of read-only memory blocks from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the memory + /// A read-only sequence of read-only memory of the data + /// Throw for unhandled invalid or unmapped memory accesses + ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false); + /// /// Gets a read-only span of data 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..232bfd8ea8 --- /dev/null +++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeCoalescingEnumerator.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; + +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 readonly MemoryRange Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current!.Value; + } + + /// + /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop. + /// + public readonly 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..2c1b15df96 --- /dev/null +++ b/src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs @@ -0,0 +1,78 @@ +using System; +using System.Runtime.CompilerServices; + +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 readonly MemoryRange Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current!.Value; + } + + internal readonly bool HasCurrent + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current.HasValue; + } + + /// + /// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop. + /// + public readonly 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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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..bac9e2bbcf --- /dev/null +++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeCoalescingEnumeratorTests.cs @@ -0,0 +1,169 @@ +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); + } + + [Test] + public void PagedMemoryRangeCoalescingEnumerator_ForPartiallyDiscontiguous4PagesWithPartialLast_HasCorrectResults() + { + // Arrange + const int StartAddress = 0; + const int Size = 14; + const int PageSize = 4; + var memoryMap = new Dictionary() + { + { 0ul, 0ul }, { 4ul, 4ul }, { 8ul, 20ul }, { 12ul, 24ul }, + }; + ulong MemoryMapLookup(ulong a) => memoryMap.TryGetValue(a, out var value) ? value : a; + var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, MemoryMapLookup); + + // Act + var results = new List(); + foreach (var memoryRange in enumerator) + { + results.Add(memoryRange); + } + + // Assert + Assert.AreEqual(2, results.Count); + + Assert.AreEqual(0, results[0].Address); + Assert.AreEqual(PageSize * 2, results[0].Size); + + Assert.AreEqual(20, results[1].Address); + Assert.AreEqual(PageSize + (Size % PageSize), results[1].Size); + } + + [Test] + public void PagedMemoryRangeCoalescingEnumerator_ForFullyDiscontiguous4PagesWithPartialLast_HasCorrectResults() + { + // Arrange + const int StartAddress = 0; + const int Size = 14; + const int PageSize = 4; + var memoryMap = new Dictionary() + { + { 0ul, 0ul }, { 4ul, 10ul }, { 8ul, 20ul }, { 12ul, 30ul }, + }; + ulong MemoryMapLookup(ulong a) => memoryMap.TryGetValue(a, out var value) ? value : a; + var enumerator = new PagedMemoryRangeCoalescingEnumerator(StartAddress, Size, PageSize, MemoryMapLookup); + + // 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(10, results[1].Address); + Assert.AreEqual(PageSize, results[1].Size); + + Assert.AreEqual(20, results[2].Address); + Assert.AreEqual(PageSize, results[2].Size); + + Assert.AreEqual(30, results[3].Address); + Assert.AreEqual(Size % PageSize, results[3].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..1da9b02ab9 --- /dev/null +++ b/src/Ryujinx.Tests.Memory/RangeTests/PagedMemoryRangeEnumeratorTests.cs @@ -0,0 +1,135 @@ +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); + } + } +}