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.
This commit is contained in:
parent
29ad198003
commit
d8c384b073
6 changed files with 349 additions and 2 deletions
|
@ -78,7 +78,7 @@ namespace Ryujinx.Common.Memory
|
|||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
|
||||
/// </summary>
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace Ryujinx.Memory
|
|||
writableRegion.Memory.Span.Fill(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only sequence of read-only memory blocks from CPU mapped memory.
|
||||
/// </summary>
|
||||
|
|
|
@ -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<ulong, ulong> mapAddress)
|
||||
{
|
||||
_enumerator = new PagedMemoryRangeEnumerator(startAddress, size, pageSize, mapAddress);
|
||||
}
|
||||
|
||||
public MemoryRange Current => _current!.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
68
src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
Normal file
68
src/Ryujinx.Memory/Range/PagedMemoryRangeEnumerator.cs
Normal file
|
@ -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<ulong, ulong> _mapAddress;
|
||||
private int _offset;
|
||||
private MemoryRange? _current;
|
||||
|
||||
public PagedMemoryRangeEnumerator(ulong startAddress, int size, int pageSize, Func<ulong, ulong> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Returning this through a GetEnumerator() call allows it to be used directly in a foreach loop.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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<MemoryRange>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue