- WritableRegion: enable wrapping IMemoryOwner<byte>

- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl
This commit is contained in:
Jim Horvath 2024-02-04 01:09:19 -05:00
parent 6208c3e6f0
commit f05b70ce36
10 changed files with 296 additions and 28 deletions

View file

@ -4,7 +4,7 @@ using System.Threading;
namespace Ryujinx.Common.Memory
{
public sealed partial class ByteMemoryPool
public partial class ByteMemoryPool
{
/// <summary>
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from

View file

@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory
/// <summary>
/// Provides a pool of re-usable byte array instances.
/// </summary>
public sealed partial class ByteMemoryPool
public static partial class ByteMemoryPool
{
private static readonly ByteMemoryPool _shared = new();
/// <summary>
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
/// the <see cref="ByteMemoryPool.Shared"/> instance.
/// </summary>
private ByteMemoryPool()
{
// No implementation
}
/// <summary>
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
/// </summary>
public static ByteMemoryPool Shared => _shared;
/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory
return buffer;
}
/// <summary>
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
/// </summary>
/// <param name="buffer">The byte buffer to copy</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> buffer)
{
var copy = RentImpl(buffer.Length);
buffer.CopyTo(copy.Memory.Span);
return copy;
}
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)

View file

@ -0,0 +1,26 @@
using System;
using System.Buffers;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A concrete implementation of <seealso cref="ReadOnlySequence{T}"/>,
/// with methods to help build a full sequence.
/// </summary>
public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment<byte>
{
public BytesReadOnlySequenceSegment(Memory<byte> memory) => Memory = memory;
public BytesReadOnlySequenceSegment Append(Memory<byte> memory)
{
var nextSegment = new BytesReadOnlySequenceSegment(memory)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = nextSegment;
return nextSegment;
}
}
}

View file

@ -1,8 +1,10 @@
using ARMeilleure.Memory;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -273,6 +275,76 @@ namespace Ryujinx.Cpu.AppleHv
}
}
/// <inheritdoc/>
public ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
if (size == 0)
{
return ReadOnlySequence<byte>.Empty;
}
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, false);
}
if (IsContiguousAndMapped(va, size))
{
return new ReadOnlySequence<byte>(_backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
else
{
BytesReadOnlySequenceSegment first = null, last = null;
try
{
AssertValidAddressAndSize(va, (ulong)size);
int offset = 0, segmentSize;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressChecked(va);
segmentSize = Math.Min(size, PageSize - (int)(va & PageMask));
var memory = _backingMemory.GetMemory(pa, segmentSize);
first = last = new BytesReadOnlySequenceSegment(memory);
offset += segmentSize;
}
for (; offset < size; offset += segmentSize)
{
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
segmentSize = Math.Min(size - offset, PageSize);
var memory = _backingMemory.GetMemory(pa, segmentSize);
if (first == null)
{
first = last = new BytesReadOnlySequenceSegment(memory);
}
else
{
last = last.Append(memory);
}
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
@ -319,11 +391,11 @@ namespace Ryujinx.Cpu.AppleHv
}
else
{
Memory<byte> memory = new byte[size];
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
base.Read(va, memory.Span);
base.Read(va, memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory);
return new WritableRegion(this, va, memoryOwner);
}
}

View file

@ -1,8 +1,10 @@
using ARMeilleure.Memory;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -311,6 +313,76 @@ namespace Ryujinx.Cpu.Jit
}
}
/// <inheritdoc/>
public ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
if (size == 0)
{
return ReadOnlySequence<byte>.Empty;
}
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, false);
}
if (IsContiguousAndMapped(va, size))
{
return new ReadOnlySequence<byte>(_backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
}
else
{
BytesReadOnlySequenceSegment first = null, last = null;
try
{
AssertValidAddressAndSize(va, (ulong)size);
int offset = 0, segmentSize;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
segmentSize = Math.Min(size, PageSize - (int)(va & PageMask));
var memory = _backingMemory.GetMemory(pa, segmentSize);
first = last = new BytesReadOnlySequenceSegment(memory);
offset += segmentSize;
}
for (; offset < size; offset += segmentSize)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
segmentSize = Math.Min(size - offset, PageSize);
var memory = _backingMemory.GetMemory(pa, segmentSize);
if (first == null)
{
first = last = new BytesReadOnlySequenceSegment(memory);
}
else
{
last = last.Append(memory);
}
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
@ -357,11 +429,11 @@ namespace Ryujinx.Cpu.Jit
}
else
{
Memory<byte> memory = new byte[size];
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span);
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory, tracked);
return new WritableRegion(this, va, memoryOwner, tracked);
}
}

View file

@ -3,6 +3,7 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -279,6 +280,21 @@ namespace Ryujinx.Cpu.Jit
}
}
/// <inheritdoc/>
public ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, write: false);
}
else
{
AssertMapped(va, (ulong)size);
}
return new ReadOnlySequence<byte>(_addressSpace.Mirror.GetMemory(va, size));
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{

View file

@ -1,5 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -148,6 +150,57 @@ namespace Ryujinx.Memory
return true;
}
/// <inheritdoc/>
public ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
if (size == 0)
{
return ReadOnlySequence<byte>.Empty;
}
if (IsContiguousAndMapped(va, size))
{
return new ReadOnlySequence<byte>(GetHostMemoryContiguous(va, size));
}
else
{
AssertValidAddressAndSize(va, (ulong)size);
int offset = 0, segmentSize;
BytesReadOnlySequenceSegment first = null, last = null;
if ((va & PageMask) != 0)
{
segmentSize = Math.Min(size, PageSize - (int)(va & PageMask));
var memory = GetHostMemoryContiguous(va, segmentSize);
first = last = new BytesReadOnlySequenceSegment(memory);
offset += segmentSize;
}
for (; offset < size; offset += segmentSize)
{
segmentSize = Math.Min(size - offset, PageSize);
var memory = GetHostMemoryContiguous(va + (ulong)offset, segmentSize);
if (first == null)
{
first = last = new BytesReadOnlySequenceSegment(memory);
}
else
{
last = last.Append(memory);
}
}
return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
@ -180,15 +233,15 @@ namespace Ryujinx.Memory
if (IsContiguousAndMapped(va, size))
{
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
return new WritableRegion(null, va, GetHostMemoryContiguous(va, size));
}
else
{
Memory<byte> memory = new byte[size];
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
GetSpan(va, size).CopyTo(memory.Span);
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
return new WritableRegion(this, va, memory);
return new WritableRegion(this, va, memoryOwner);
}
}
@ -376,6 +429,11 @@ namespace Ryujinx.Memory
return true;
}
private unsafe Memory<byte> GetHostMemoryContiguous(ulong va, int size)
{
return new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory;
}
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
{
return new Span<byte>((void*)GetHostAddress(va), size);

View file

@ -124,6 +124,16 @@ namespace Ryujinx.Memory
}
}
/// <summary>
/// Gets a read-only sequence of read-only memory blocks from CPU mapped memory.
/// </summary>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <param name="tracked">True if read tracking is triggered on the memory</param>
/// <returns>A read-only sequence of read-only memory of the data</returns>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false);
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
/// </summary>

View file

@ -1,4 +1,5 @@
using System;
using System.Buffers;
namespace Ryujinx.Memory
{
@ -6,6 +7,7 @@ namespace Ryujinx.Memory
{
private readonly IWritableBlock _block;
private readonly ulong _va;
private readonly IMemoryOwner<byte> _memoryOwner;
private readonly bool _tracked;
private bool NeedsWriteback => _block != null;
@ -20,6 +22,12 @@ namespace Ryujinx.Memory
Memory = memory;
}
public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner<byte> memoryOwner, bool tracked = false)
: this(block, va, memoryOwner.Memory, tracked)
{
_memoryOwner = memoryOwner;
}
public void Dispose()
{
if (NeedsWriteback)
@ -33,6 +41,8 @@ namespace Ryujinx.Memory
_block.WriteUntracked(_va, Memory.Span);
}
}
_memoryOwner?.Dispose();
}
}
}

View file

@ -1,6 +1,7 @@
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Buffers;
using System.Collections.Generic;
namespace Ryujinx.Tests.Memory
@ -57,6 +58,11 @@ namespace Ryujinx.Tests.Memory
throw new NotImplementedException();
}
public ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
throw new NotImplementedException();
}
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
throw new NotImplementedException();