diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs index df3f8dc93e..05fb29ac71 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs @@ -4,7 +4,7 @@ using System.Threading; namespace Ryujinx.Common.Memory { - public sealed partial class ByteMemoryPool + public partial class ByteMemoryPool { /// /// Represents a that wraps an array rented from diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs index 071f56b136..6fd6a98aa7 100644 --- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs +++ b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs @@ -6,24 +6,8 @@ namespace Ryujinx.Common.Memory /// /// Provides a pool of re-usable byte array instances. /// - public sealed partial class ByteMemoryPool + public static partial class ByteMemoryPool { - private static readonly ByteMemoryPool _shared = new(); - - /// - /// Constructs a instance. Private to force access through - /// the instance. - /// - private ByteMemoryPool() - { - // No implementation - } - - /// - /// Retrieves a shared instance. - /// - public static ByteMemoryPool Shared => _shared; - /// /// Returns the maximum buffer size supported by this pool. /// @@ -95,6 +79,20 @@ namespace Ryujinx.Common.Memory return buffer; } + /// + /// 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.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 80f7c8a1f8..d930dbc3bf 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -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 } } + /// + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + return new ReadOnlySequence(_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(first, 0, last, (int)(size - last.RunningIndex)); + } + } + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { @@ -319,11 +391,11 @@ namespace Ryujinx.Cpu.AppleHv } else { - Memory memory = new byte[size]; + IMemoryOwner 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); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs index c87c8b8cc5..ffed754bf3 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -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 } } + /// + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + return new ReadOnlySequence(_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(first, 0, last, (int)(size - last.RunningIndex)); + } + } + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { @@ -357,11 +429,11 @@ namespace Ryujinx.Cpu.Jit } else { - Memory memory = new byte[size]; + IMemoryOwner 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); } } diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index f410d02e96..3b356e2f12 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -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 } } + /// + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + return new ReadOnlySequence(_addressSpace.Mirror.GetMemory(va, size)); + } + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs index f19b45b659..4f79fdbb57 100644 --- a/src/Ryujinx.Memory/AddressSpaceManager.cs +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -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; } + /// + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (IsContiguousAndMapped(va, size)) + { + return new ReadOnlySequence(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(first, 0, last, (int)(size - last.RunningIndex)); + } + } + /// public ReadOnlySpan 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*)GetHostAddress(va), size).Memory); + return new WritableRegion(null, va, GetHostMemoryContiguous(va, size)); } else { - Memory memory = new byte[size]; + IMemoryOwner 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 GetHostMemoryContiguous(ulong va, int size) + { + return new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory; + } + private unsafe Span GetHostSpanContiguous(ulong va, int size) { return new Span((void*)GetHostAddress(va), size); diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs index 557da2f261..96d3e85797 100644 --- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -124,6 +124,16 @@ namespace Ryujinx.Memory } } + /// + /// 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/WritableRegion.cs b/src/Ryujinx.Memory/WritableRegion.cs index 666c8a99b2..2c21ef4e80 100644 --- a/src/Ryujinx.Memory/WritableRegion.cs +++ b/src/Ryujinx.Memory/WritableRegion.cs @@ -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 _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 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(); } } } diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs index 85a1ac02bc..15e7d9b89a 100644 --- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -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 GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { throw new NotImplementedException();