aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Cpu
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2021-05-24 21:52:44 +0100
committerGitHub <noreply@github.com>2021-05-24 22:52:44 +0200
commit54ea2285f05ef6f59a6f1c63df4a7bdd77d7b883 (patch)
tree244f601856dc96f2bf8ce15aecd0b5623ae7b234 /Ryujinx.Cpu
parentfb65f392d1c4b0e01f22b6ddebcc8317ba9769c3 (diff)
POWER - Performance Optimizations With Extensive Ramifications (#2286)
* Refactoring of KMemoryManager class * Replace some trivial uses of DRAM address with VA * Get rid of GetDramAddressFromVa * Abstracting more operations on derived page table class * Run auto-format on KPageTableBase * Managed to make TryConvertVaToPa private, few uses remains now * Implement guest physical pages ref counting, remove manual freeing * Make DoMmuOperation private and call new abstract methods only from the base class * Pass pages count rather than size on Map/UnmapMemory * Change memory managers to take host pointers * Fix a guest memory leak and simplify KPageTable * Expose new methods for host range query and mapping * Some refactoring of MapPagesFromClientProcess to allow proper page ref counting and mapping without KPageLists * Remove more uses of AddVaRangeToPageList, now only one remains (shared memory page checking) * Add a SharedMemoryStorage class, will be useful for host mapping * Sayonara AddVaRangeToPageList, you served us well * Start to implement host memory mapping (WIP) * Support memory tracking through host exception handling * Fix some access violations from HLE service guest memory access and CPU * Fix memory tracking * Fix mapping list bugs, including a race and a error adding mapping ranges * Simple page table for memory tracking * Simple "volatile" region handle mode * Update UBOs directly (experimental, rough) * Fix the overlap check * Only set non-modified buffers as volatile * Fix some memory tracking issues * Fix possible race in MapBufferFromClientProcess (block list updates were not locked) * Write uniform update to memory immediately, only defer the buffer set. * Fix some memory tracking issues * Pass correct pages count on shared memory unmap * Armeilleure Signal Handler v1 + Unix changes Unix currently behaves like windows, rather than remapping physical * Actually check if the host platform is unix * Fix decommit on linux. * Implement windows 10 placeholder shared memory, fix a buffer issue. * Make PTC version something that will never match with master * Remove testing variable for block count * Add reference count for memory manager, fix dispose Can still deadlock with OpenAL * Add address validation, use page table for mapped check, add docs Might clean up the page table traversing routines. * Implement batched mapping/tracking. * Move documentation, fix tests. * Cleanup uniform buffer update stuff. * Remove unnecessary assignment. * Add unsafe host mapped memory switch On by default. Would be good to turn this off for untrusted code (homebrew, exefs mods) and give the user the option to turn it on manually, though that requires some UI work. * Remove C# exception handlers They have issues due to current .NET limitations, so the meilleure one fully replaces them for now. * Fix MapPhysicalMemory on the software MemoryManager. * Null check for GetHostAddress, docs * Add configuration for setting memory manager mode (not in UI yet) * Add config to UI * Fix type mismatch on Unix signal handler code emit * Fix 6GB DRAM mode. The size can be greater than `uint.MaxValue` when the DRAM is >4GB. * Address some feedback. * More detailed error if backing memory cannot be mapped. * SetLastError on all OS functions for consistency * Force pages dirty with UBO update instead of setting them directly. Seems to be much faster across a few games. Need retesting. * Rebase, configuration rework, fix mem tracking regression * Fix race in FreePages * Set memory managers null after decrementing ref count * Remove readonly keyword, as this is now modified. * Use a local variable for the signal handler rather than a register. * Fix bug with buffer resize, and index/uniform buffer binding. Should fix flickering in games. * Add InvalidAccessHandler to MemoryTracking Doesn't do anything yet * Call invalid access handler on unmapped read/write. Same rules as the regular memory manager. * Make unsafe mapped memory its own MemoryManagerType * Move FlushUboDirty into UpdateState. * Buffer dirty cache, rather than ubo cache Much cleaner, may be reusable for Inline2Memory updates. * This doesn't return anything anymore. * Add sigaction remove methods, correct a few function signatures. * Return empty list of physical regions for size 0. * Also on AddressSpaceManager Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Diffstat (limited to 'Ryujinx.Cpu')
-rw-r--r--Ryujinx.Cpu/CpuContext.cs5
-rw-r--r--Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs42
-rw-r--r--Ryujinx.Cpu/InvalidAccessHandler.cs9
-rw-r--r--Ryujinx.Cpu/MemoryEhMeilleure.cs41
-rw-r--r--Ryujinx.Cpu/MemoryManager.cs291
-rw-r--r--Ryujinx.Cpu/MemoryManagerBase.cs32
-rw-r--r--Ryujinx.Cpu/MemoryManagerHostMapped.cs692
-rw-r--r--Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs1
-rw-r--r--Ryujinx.Cpu/Tracking/CpuRegionHandle.cs2
-rw-r--r--Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs1
10 files changed, 900 insertions, 216 deletions
diff --git a/Ryujinx.Cpu/CpuContext.cs b/Ryujinx.Cpu/CpuContext.cs
index 45040586..407353fd 100644
--- a/Ryujinx.Cpu/CpuContext.cs
+++ b/Ryujinx.Cpu/CpuContext.cs
@@ -1,4 +1,5 @@
-using ARMeilleure.State;
+using ARMeilleure.Memory;
+using ARMeilleure.State;
using ARMeilleure.Translation;
namespace Ryujinx.Cpu
@@ -7,7 +8,7 @@ namespace Ryujinx.Cpu
{
private readonly Translator _translator;
- public CpuContext(MemoryManager memory)
+ public CpuContext(IMemoryManager memory)
{
_translator = new Translator(new JitMemoryAllocator(), memory);
memory.UnmapEvent += UnmapHandler;
diff --git a/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs b/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs
new file mode 100644
index 00000000..81874339
--- /dev/null
+++ b/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs
@@ -0,0 +1,42 @@
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.Cpu
+{
+ public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager
+ {
+ /// <summary>
+ /// Writes data to CPU mapped memory, without write tracking.
+ /// </summary>
+ /// <param name="va">Virtual address to write the data into</param>
+ /// <param name="data">Data to be written</param>
+ void WriteUntracked(ulong va, ReadOnlySpan<byte> data);
+
+ /// <summary>
+ /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
+ /// </summary>
+ /// <param name="address">CPU virtual address of the region</param>
+ /// <param name="size">Size of the region</param>
+ /// <returns>The memory tracking handle</returns>
+ CpuRegionHandle BeginTracking(ulong address, ulong size);
+
+ /// <summary>
+ /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
+ /// </summary>
+ /// <param name="address">CPU virtual address of the region</param>
+ /// <param name="size">Size of the region</param>
+ /// <param name="granularity">Desired granularity of write tracking</param>
+ /// <returns>The memory tracking handle</returns>
+ CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ulong granularity);
+
+ /// <summary>
+ /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
+ /// </summary>
+ /// <param name="address">CPU virtual address of the region</param>
+ /// <param name="size">Size of the region</param>
+ /// <param name="granularity">Desired granularity of write tracking</param>
+ /// <returns>The memory tracking handle</returns>
+ CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity);
+ }
+}
diff --git a/Ryujinx.Cpu/InvalidAccessHandler.cs b/Ryujinx.Cpu/InvalidAccessHandler.cs
deleted file mode 100644
index 0d3d387d..00000000
--- a/Ryujinx.Cpu/InvalidAccessHandler.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Ryujinx.Cpu
-{
- /// <summary>
- /// Function that handles a invalid memory access from the emulated CPU.
- /// </summary>
- /// <param name="va">Virtual address of the invalid region that is being accessed</param>
- /// <returns>True if the invalid access should be ignored, false otherwise</returns>
- public delegate bool InvalidAccessHandler(ulong va);
-}
diff --git a/Ryujinx.Cpu/MemoryEhMeilleure.cs b/Ryujinx.Cpu/MemoryEhMeilleure.cs
new file mode 100644
index 00000000..ac7791b4
--- /dev/null
+++ b/Ryujinx.Cpu/MemoryEhMeilleure.cs
@@ -0,0 +1,41 @@
+using ARMeilleure.Signal;
+using Ryujinx.Memory;
+using Ryujinx.Memory.Tracking;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Cpu
+{
+ class MemoryEhMeilleure : IDisposable
+ {
+ private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write);
+
+ private readonly MemoryBlock _addressSpace;
+ private readonly MemoryTracking _tracking;
+ private readonly TrackingEventDelegate _trackingEvent;
+
+ private readonly ulong _baseAddress;
+
+ public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryTracking tracking)
+ {
+ _addressSpace = addressSpace;
+ _tracking = tracking;
+
+ _baseAddress = (ulong)_addressSpace.Pointer;
+ ulong endAddress = _baseAddress + addressSpace.Size;
+
+ _trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
+ bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
+
+ if (!added)
+ {
+ throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
+ }
+ }
+
+ public void Dispose()
+ {
+ NativeSignalHandler.RemoveTrackedRegion((nuint)_baseAddress);
+ }
+ }
+}
diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 591299ca..dbc2f736 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -1,9 +1,11 @@
using ARMeilleure.Memory;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
@@ -13,7 +15,7 @@ namespace Ryujinx.Cpu
/// <summary>
/// Represents a CPU memory manager.
/// </summary>
- public sealed class MemoryManager : IMemoryManager, IVirtualMemoryManager, IWritableBlock, IDisposable
+ public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@@ -32,7 +34,6 @@ namespace Ryujinx.Cpu
private readonly ulong _addressSpaceSize;
- private readonly MemoryBlock _backingMemory;
private readonly MemoryBlock _pageTable;
/// <summary>
@@ -40,17 +41,18 @@ namespace Ryujinx.Cpu
/// </summary>
public IntPtr PageTablePointer => _pageTable.Pointer;
+ public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable;
+
public MemoryTracking Tracking { get; }
- internal event Action<ulong, ulong> UnmapEvent;
+ public event Action<ulong, ulong> UnmapEvent;
/// <summary>
/// Creates a new instance of the memory manager.
/// </summary>
- /// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
/// <param name="addressSpaceSize">Size of the address space</param>
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
- public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
+ public MemoryManager(ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
{
_invalidAccessHandler = invalidAccessHandler;
@@ -65,45 +67,30 @@ namespace Ryujinx.Cpu
AddressSpaceBits = asBits;
_addressSpaceSize = asSize;
- _backingMemory = backingMemory;
_pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
- Tracking = new MemoryTracking(this, backingMemory, PageSize);
- Tracking.EnablePhysicalProtection = false; // Disabled for now, as protection is done in software.
+ Tracking = new MemoryTracking(this, PageSize);
}
- /// <summary>
- /// Maps a virtual memory range into a physical memory range.
- /// </summary>
- /// <remarks>
- /// Addresses and size must be page aligned.
- /// </remarks>
- /// <param name="va">Virtual memory address</param>
- /// <param name="pa">Physical memory address</param>
- /// <param name="size">Size to be mapped</param>
- public void Map(ulong va, ulong pa, ulong size)
+ /// <inheritdoc/>
+ public void Map(ulong va, nuint hostAddress, ulong size)
{
AssertValidAddressAndSize(va, size);
ulong remainingSize = size;
ulong oVa = va;
- ulong oPa = pa;
while (remainingSize != 0)
{
- _pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
+ _pageTable.Write((va / PageSize) * PteSize, hostAddress);
va += PageSize;
- pa += PageSize;
+ hostAddress += PageSize;
remainingSize -= PageSize;
}
- Tracking.Map(oVa, oPa, size);
+ Tracking.Map(oVa, size);
}
- /// <summary>
- /// Unmaps a previously mapped range of virtual memory.
- /// </summary>
- /// <param name="va">Virtual address of the range to be unmapped</param>
- /// <param name="size">Size of the range to be unmapped</param>
+ /// <inheritdoc/>
public void Unmap(ulong va, ulong size)
{
// If size is 0, there's nothing to unmap, just exit early.
@@ -120,66 +107,39 @@ namespace Ryujinx.Cpu
ulong remainingSize = size;
while (remainingSize != 0)
{
- _pageTable.Write((va / PageSize) * PteSize, 0UL);
+ _pageTable.Write((va / PageSize) * PteSize, (nuint)0);
va += PageSize;
remainingSize -= PageSize;
}
}
- /// <summary>
- /// Reads data from CPU mapped memory.
- /// </summary>
- /// <typeparam name="T">Type of the data being read</typeparam>
- /// <param name="va">Virtual address of the data in memory</param>
- /// <returns>The data</returns>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ /// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>(), true))[0];
}
- /// <summary>
- /// Reads data from CPU mapped memory, with read tracking
- /// </summary>
- /// <typeparam name="T">Type of the data being read</typeparam>
- /// <param name="va">Virtual address of the data in memory</param>
- /// <returns>The data</returns>
+ /// <inheritdoc/>
public T ReadTracked<T>(ulong va) where T : unmanaged
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
}
- /// <summary>
- /// Reads data from CPU mapped memory.
- /// </summary>
- /// <param name="va">Virtual address of the data in memory</param>
- /// <param name="data">Span to store the data being read into</param>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ /// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
{
ReadImpl(va, data);
}
- /// <summary>
- /// Writes data to CPU mapped memory.
- /// </summary>
- /// <typeparam name="T">Type of the data being written</typeparam>
- /// <param name="va">Virtual address to write the data into</param>
- /// <param name="value">Data to be written</param>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ /// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged
{
Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
}
- /// <summary>
- /// Writes data to CPU mapped memory, with write tracking.
- /// </summary>
- /// <param name="va">Virtual address to write the data into</param>
- /// <param name="data">Data to be written</param>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ /// <inheritdoc/>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
@@ -192,11 +152,7 @@ namespace Ryujinx.Cpu
WriteImpl(va, data);
}
- /// <summary>
- /// Writes data to CPU mapped memory, without write tracking.
- /// </summary>
- /// <param name="va">Virtual address to write the data into</param>
- /// <param name="data">Data to be written</param>
+ /// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
@@ -221,7 +177,7 @@ namespace Ryujinx.Cpu
if (IsContiguousAndMapped(va, data.Length))
{
- data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
+ data.CopyTo(GetHostSpanContiguous(va, data.Length));
}
else
{
@@ -229,22 +185,18 @@ namespace Ryujinx.Cpu
if ((va & PageMask) != 0)
{
- ulong pa = GetPhysicalAddressInternal(va);
-
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
- data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
+ data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
- ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
-
size = Math.Min(data.Length - offset, PageSize);
- data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
+ data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
}
}
}
@@ -257,18 +209,7 @@ namespace Ryujinx.Cpu
}
}
- /// <summary>
- /// Gets a read-only span of data from CPU mapped memory.
- /// </summary>
- /// <remarks>
- /// This may perform a allocation if the data is not contiguous in memory.
- /// For this reason, the span is read-only, you can't modify the data.
- /// </remarks>
- /// <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 span</param>
- /// <returns>A read-only span of the data</returns>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
if (size == 0)
@@ -283,7 +224,7 @@ namespace Ryujinx.Cpu
if (IsContiguousAndMapped(va, size))
{
- return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
+ return GetHostSpanContiguous(va, size);
}
else
{
@@ -295,19 +236,8 @@ namespace Ryujinx.Cpu
}
}
- /// <summary>
- /// Gets a region of memory that can be written to.
- /// </summary>
- /// <remarks>
- /// If the requested region is not contiguous in physical memory,
- /// this will perform an allocation, and flush the data (writing it
- /// back to guest memory) on disposal.
- /// </remarks>
- /// <param name="va">Virtual address of the data</param>
- /// <param name="size">Size of the data</param>
- /// <returns>A writable region of memory containing the data</returns>
- /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
- public WritableRegion GetWritableRegion(ulong va, int size)
+ /// <inheritdoc/>
+ public unsafe WritableRegion GetWritableRegion(ulong va, int size)
{
if (size == 0)
{
@@ -316,7 +246,7 @@ namespace Ryujinx.Cpu
if (IsContiguousAndMapped(va, size))
{
- return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
+ return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
}
else
{
@@ -328,17 +258,8 @@ namespace Ryujinx.Cpu
}
}
- /// <summary>
- /// Gets a reference for the given type at the specified virtual memory address.
- /// </summary>
- /// <remarks>
- /// The data must be located at a contiguous memory region.
- /// </remarks>
- /// <typeparam name="T">Type of the data to get the reference</typeparam>
- /// <param name="va">Virtual address of the data</param>
- /// <returns>A reference to the data in memory</returns>
- /// <exception cref="MemoryNotContiguousException">Throw if the specified memory region is not contiguous in physical memory</exception>
- public ref T GetRef<T>(ulong va) where T : unmanaged
+ /// <inheritdoc/>
+ public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
{
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
{
@@ -347,7 +268,7 @@ namespace Ryujinx.Cpu
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
- return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
+ return ref *(T*)GetHostAddress(va);
}
/// <summary>
@@ -389,7 +310,7 @@ namespace Ryujinx.Cpu
return false;
}
- if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
+ if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
{
return false;
}
@@ -400,15 +321,14 @@ namespace Ryujinx.Cpu
return true;
}
- /// <summary>
- /// Gets the physical regions that make up the given virtual address region.
- /// If any part of the virtual region is unmapped, null is returned.
- /// </summary>
- /// <param name="va">Virtual address of the range</param>
- /// <param name="size">Size of the range</param>
- /// <returns>Array of physical regions</returns>
- public (ulong address, ulong size)[] GetPhysicalRegions(ulong va, ulong size)
+ /// <inheritdoc/>
+ public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
+ if (size == 0)
+ {
+ return Enumerable.Empty<HostMemoryRange>();
+ }
+
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
{
return null;
@@ -416,9 +336,9 @@ namespace Ryujinx.Cpu
int pages = GetPagesCount(va, (uint)size, out va);
- List<(ulong, ulong)> regions = new List<(ulong, ulong)>();
+ var regions = new List<HostMemoryRange>();
- ulong regionStart = GetPhysicalAddressInternal(va);
+ nuint regionStart = GetHostAddress(va);
ulong regionSize = PageSize;
for (int page = 0; page < pages - 1; page++)
@@ -428,12 +348,12 @@ namespace Ryujinx.Cpu
return null;
}
- ulong newPa = GetPhysicalAddressInternal(va + PageSize);
+ nuint newHostAddress = GetHostAddress(va + PageSize);
- if (GetPhysicalAddressInternal(va) + PageSize != newPa)
+ if (GetHostAddress(va) + PageSize != newHostAddress)
{
- regions.Add((regionStart, regionSize));
- regionStart = newPa;
+ regions.Add(new HostMemoryRange(regionStart, regionSize));
+ regionStart = newHostAddress;
regionSize = 0;
}
@@ -441,9 +361,9 @@ namespace Ryujinx.Cpu
regionSize += PageSize;
}
- regions.Add((regionStart, regionSize));
+ regions.Add(new HostMemoryRange(regionStart, regionSize));
- return regions.ToArray();
+ return regions;
}
private void ReadImpl(ulong va, Span<byte> data)
@@ -461,22 +381,18 @@ namespace Ryujinx.Cpu
if ((va & PageMask) != 0)
{
- ulong pa = GetPhysicalAddressInternal(va);
-
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
- _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
+ GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
offset += size;
}
for (; offset < data.Length; offset += size)
{
- ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
-
size = Math.Min(data.Length - offset, PageSize);
- _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
+ GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
@@ -488,12 +404,7 @@ namespace Ryujinx.Cpu
}
}
- /// <summary>
- /// Checks if a memory range is mapped.
- /// </summary>
- /// <param name="va">Virtual address of the range</param>
- /// <param name="size">Size of the range in bytes</param>
- /// <returns>True if the entire range is mapped, false otherwise</returns>
+ /// <inheritdoc/>
public bool IsRangeMapped(ulong va, ulong size)
{
if (size == 0UL)
@@ -521,11 +432,7 @@ namespace Ryujinx.Cpu
return true;
}
- /// <summary>
- /// Checks if the page at a given CPU virtual address is mapped.
- /// </summary>
- /// <param name="va">Virtual address to check</param>
- /// <returns>True if the address is mapped, false otherwise</returns>
+ /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
@@ -534,7 +441,7 @@ namespace Ryujinx.Cpu
return false;
}
- return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
+ return _pageTable.Read<nuint>((va / PageSize) * PteSize) != 0;
}
private bool ValidateAddress(ulong va)
@@ -569,35 +476,39 @@ namespace Ryujinx.Cpu
}
/// <summary>
- /// Performs address translation of the address inside a CPU mapped memory range.
+ /// Get a span representing the given virtual address and size range in host memory.
+ /// This function assumes that the requested virtual memory region is contiguous.
/// </summary>
- /// <remarks>
- /// If the address is invalid or unmapped, -1 will be returned.
- /// </remarks>
- /// <param name="va">Virtual address to be translated</param>
- /// <returns>The physical address</returns>
- public ulong GetPhysicalAddress(ulong va)
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ /// <returns>A span representing the given virtual range in host memory</returns>
+ /// <exception cref="InvalidMemoryRegionException">Throw when the base virtual address is not mapped</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
{
- // We return -1L if the virtual address is invalid or unmapped.
- if (!ValidateAddress(va) || !IsMapped(va))
- {
- return ulong.MaxValue;
- }
-
- return GetPhysicalAddressInternal(va);
+ return new Span<byte>((void*)GetHostAddress(va), size);
}
- private ulong GetPhysicalAddressInternal(ulong va)
+ /// <summary>
+ /// Get the host address for a given virtual address, using the page table.
+ /// </summary>
+ /// <param name="va">Virtual address</param>
+ /// <returns>The corresponding host address for the given virtual address</returns>
+ /// <exception cref="InvalidMemoryRegionException">Throw when the virtual address is not mapped</exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private nuint GetHostAddress(ulong va)
{
- return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
+ nuint pageBase = _pageTable.Read<nuint>((va / PageSize) * PteSize) & unchecked((nuint)0xffff_ffff_ffffUL);
+
+ if (pageBase == 0)
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
+ }
+
+ return pageBase + (nuint)(va & PageMask);
}
- /// <summary>
- /// Reprotect a region of virtual memory for tracking. Sets software protection bits.
- /// </summary>
- /// <param name="va">Virtual address base</param>
- /// <param name="size">Size of the region to protect</param>
- /// <param name="protection">Memory protection to set</param>
+ /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
AssertValidAddressAndSize(va, size);
@@ -632,47 +543,25 @@ namespace Ryujinx.Cpu
}
}
- /// <summary>
- /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
- /// </summary>
- /// <param name="address">CPU virtual address of the region</param>
- /// <param name="size">Size of the region</param>
- /// <returns>The memory tracking handle</returns>
+ /// <inheritdoc/>
public CpuRegionHandle BeginTracking(ulong address, ulong size)
{
return new CpuRegionHandle(Tracking.BeginTracking(address, size));
}
- /// <summary>
- /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
- /// </summary>
- /// <param name="address">CPU virtual address of the region</param>
- /// <param name="size">Size of the region</param>
- /// <param name="granularity">Desired granularity of write tracking</param>
- /// <returns>The memory tracking handle</returns>
+ /// <inheritdoc/>
public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ulong granularity)
{
return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, granularity));
}
- /// <summary>
- /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
- /// </summary>
- /// <param name="address">CPU virtual address of the region</param>
- /// <param name="size">Size of the region</param>
- /// <param name="granularity">Desired granularity of write tracking</param>
- /// <returns>The memory tracking handle</returns>
+ /// <inheritdoc/>
public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
{
return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
}
- /// <summary>
- /// Alerts the memory tracking that a given region has been read from or written to.
- /// This should be called before read/write is performed.
- /// </summary>
- /// <param name="va">Virtual address of the region</param>
- /// <param name="size">Size of the region</param>
+ /// <inheritdoc/>
public void SignalMemoryTracking(ulong va, ulong size, bool write)
{
AssertValidAddressAndSize(va, size);
@@ -704,19 +593,11 @@ namespace Ryujinx.Cpu
}
}
- private ulong PaToPte(ulong pa)
- {
- return (ulong)_backingMemory.GetPointer(pa, PageSize).ToInt64();
- }
-
- private ulong PteToPa(ulong pte)
- {
- return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
- }
-
/// <summary>
/// Disposes of resources used by the memory manager.
/// </summary>
- public void Dispose() => _pageTable.Dispose();
+ protected override void Destroy() => _pageTable.Dispose();
+
+ private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
}
}
diff --git a/Ryujinx.Cpu/MemoryManagerBase.cs b/Ryujinx.Cpu/MemoryManagerBase.cs
new file mode 100644
index 00000000..d2fc7a19
--- /dev/null
+++ b/Ryujinx.Cpu/MemoryManagerBase.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Memory;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Ryujinx.Cpu
+{
+ public abstract class MemoryManagerBase : IRefCounted
+ {
+ private int _referenceCount;
+
+ public void IncrementReferenceCount()
+ {
+ int newRefCount = Interlocked.Increment(ref _referenceCount);
+
+ Debug.Assert(newRefCount >= 1);
+ }
+
+ public void DecrementReferenceCount()
+ {
+ int newRefCount = Interlocked.Decrement(ref _referenceCount);
+
+ Debug.Assert(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ Destroy();
+ }
+ }
+
+ protected abstract void Destroy();
+ }
+}
diff --git a/Ryujinx.Cpu/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/MemoryManagerHostMapped.cs
new file mode 100644
index 00000000..da81d04f
--- /dev/null
+++ b/Ryujinx.Cpu/MemoryManagerHostMapped.cs
@@ -0,0 +1,692 @@
+using ARMeilleure.Memory;
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
+using Ryujinx.Memory.Tracking;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Ryujinx.Cpu
+{
+ /// <summary>
+ /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
+ /// </summary>
+ public class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked
+ {
+ public const int PageBits = 12;
+ public const int PageSize = 1 << PageBits;
+ public const int PageMask = PageSize - 1;
+
+ public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
+ public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
+
+ private enum HostMappedPtBits : ulong
+ {
+ Unmapped = 0,
+ Mapped,
+ WriteTracked,
+ ReadWriteTracked,
+
+ MappedReplicated = 0x5555555555555555,
+ WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
+ ReadWriteTrackedReplicated = ulong.MaxValue
+ }
+
+ private readonly InvalidAccessHandler _invalidAccessHandler;
+ private readonly bool _unsafeMode;
+
+ private readonly MemoryBlock _addressSpace;
+ private readonly MemoryBlock _addressSpaceMirror;
+ private readonly ulong _addressSpaceSize;
+
+ private readonly MemoryEhMeilleure _memoryEh;
+
+ private ulong[] _pageTable;
+
+ public int AddressSpaceBits { get; }
+
+ public IntPtr PageTablePointer => _addressSpace.Pointer;
+
+ public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
+
+ public MemoryTracking Tracking { get; }
+
+ public event Action<ulong, ulong> UnmapEvent;
+
+ /// <summary>
+ /// Creates a new instance of the host mapped memory manager.
+ /// </summary>
+ /// <param name="addressSpaceSize">Size of the address space</param>
+ /// <param name="unsafeMode">True if unmanaged access should not be masked (unsafe), false otherwise.</param>
+ /// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
+ public MemoryManagerHostMapped(ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
+ {
+ _invalidAccessHandler = invalidAccessHandler;
+ _unsafeMode = unsafeMode;
+ _addressSpaceSize = addressSpaceSize;
+
+ ulong asSize = PageSize;
+ int asBits = PageBits;
+
+ while (asSize < addressSpaceSize)
+ {
+ asSize <<= 1;
+ asBits++;
+ }
+
+ AddressSpaceBits = asBits;
+
+ _pageTable = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
+ _addressSpace = new MemoryBlock(asSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable);
+ _addressSpaceMirror = _addressSpace.CreateMirror();
+ Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
+ _memoryEh = new MemoryEhMeilleure(_addressSpace, Tracking);
+ }
+
+ /// <summary>
+ /// Checks if the virtual address is part of the addressable space.
+ /// </summary>
+ /// <param name="va">Virtual address</param>
+ /// <returns>True if the virtual address is part of the addressable space</returns>
+ private bool ValidateAddress(ulong va)
+ {
+ return va < _addressSpaceSize;
+ }
+
+ /// <summary>
+ /// Checks if the combination of virtual address and size is part of the addressable space.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ /// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
+ private bool ValidateAddressAndSize(ulong va, ulong size)
+ {
+ ulong endVa = va + size;
+ return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
+ }
+
+ /// <summary>
+ /// Ensures the combination of virtual address and size is part of the addressable space.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
+ private void AssertValidAddressAndSize(ulong va, ulong size)
+ {
+ if (!ValidateAddressAndSize(va, size))
+ {
+ throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+
+ /// <summary>
+ /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ private void AssertMapped(ulong va, ulong size)
+ {
+ if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
+ {
+ throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+
+ /// <inheritdoc/>
+ public void Map(ulong va, nuint hostAddress, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ _addressSpace.Commit(va, size);
+ AddMapping(va, size);
+
+ Tracking.Map(va, size);
+ }
+
+ /// <inheritdoc/>
+ public void Unmap(ulong va, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ UnmapEvent?.Invoke(va, size);
+ Tracking.Unmap(va, size);
+
+ RemoveMapping(va, size);
+ _addressSpace.Decommit(va, size);
+ }
+
+ /// <inheritdoc/>
+ public T Read<T>(ulong va) where T : unmanaged
+ {
+ try
+ {
+ AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
+
+ return _addressSpaceMirror.Read<T>(va);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+
+ return default;
+ }
+ }
+
+ /// <inheritdoc/>
+ public T ReadTracked<T>(ulong va) where T : unmanaged
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
+
+ return Read<T>(va);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+
+ return default;
+ }
+ }
+
+ /// <inheritdoc/>
+ public void Read(ulong va, Span<byte> data)
+ {
+ try
+ {
+ AssertMapped(va, (ulong)data.Length);
+
+ _addressSpaceMirror.Read(va, data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public void Write<T>(ulong va, T value) where T : unmanaged
+ {
+ try
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
+
+ _addressSpaceMirror.Write(va, value);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public void Write(ulong va, ReadOnlySpan<byte> data)
+ {
+ try {
+ SignalMemoryTracking(va, (ulong)data.Length, write: true);
+
+ _addressSpaceMirror.Write(va, data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
+ {
+ try
+ {
+ AssertMapped(va, (ulong)data.Length);
+
+ _addressSpaceMirror.Write(va, data);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+ {
+ throw;
+ }
+ }
+}
+
+ /// <inheritdoc/>
+ public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
+ {
+ if (tracked)
+ {
+ SignalMemoryTracking(va, (ulong)size, write: false);
+ }
+ else
+ {
+ AssertMapped(va, (ulong)size);
+ }
+
+ return _addressSpaceMirror.GetSpan(va, size);
+ }
+
+ /// <inheritdoc/>
+ public WritableRegion GetWritableRegion(ulong va, int size)
+ {
+ AssertMapped(va, (ulong)size);
+
+ return _addressSpaceMirror.GetWritableRegion(va, size);
+ }
+
+ /// <inheritdoc/>
+ public ref T GetRef<T>(ulong va) where T : unmanaged
+ {
+ SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
+
+ return ref _addressSpaceMirror.GetRef<T>(va);
+ }
+
+ /// <inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsMapped(ulong va)
+ {
+ return ValidateAddress(va) && IsMappedImpl(va);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsMappedImpl(ulong va)
+ {
+ ulong page = va >> PageBits;
+
+ int bit = (int)((page & 31) << 1);
+
+ int pageIndex = (int)(page >> PageToPteShift);
+ ref ulong pageRef = ref _pageTable[pageIndex];
+
+ ulong pte = Volatile.Read(ref pageRef);
+
+ return ((pte >> bit) & 3) != 0;
+ }
+
+ /// <inheritdoc/>
+ public bool IsRangeMapped(ulong va, ulong size)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ return IsRangeMappedImpl(va, size);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
+ {
+ startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
+ endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
+
+ pageIndex = (int)(pageStart >> PageToPteShift);
+ pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
+ }
+
+ private bool IsRangeMappedImpl(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+
+ if (pages == 1)
+ {
+ return IsMappedImpl(va);
+ }
+
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ // Check if either bit in each 2 bit page entry is set.
+ // OR the block with itself shifted down by 1, and check the first bit of each entry.
+
+ ulong mask = BlockMappedMask & startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageTable[pageIndex++];
+ ulong pte = Volatile.Read(ref pageRef);
+
+ pte |= pte >> 1;
+ if ((pte & mask) != mask)
+ {
+ return false;
+ }
+
+ mask = BlockMappedMask;
+ }
+
+ return true;
+ }
+
+ /// <inheritdoc/>
+ public IEnumerable<HostMemoryRange> GetPhysicalRegions(ulong va, ulong size)
+ {
+ if (size == 0)
+ {
+ return Enumerable.Empty<HostMemoryRange>();
+ }
+
+ AssertMapped(va, size);
+
+ return new HostMemoryRange[] { new HostMemoryRange(_addressSpaceMirror.GetPointer(va, size), size) };
+ }
+
+ /// <inheritdoc/>
+ /// <remarks>
+ /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
+ /// </remarks>
+ public void SignalMemoryTracking(ulong va, ulong size, bool write)
+ {
+ AssertValidAddressAndSize(va, size);
+
+ // Software table, used for managed memory tracking.
+
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+
+ if (pages == 1)
+ {
+ ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
+
+ int bit = (int)((pageStart & 31) << 1);
+
+ int pageIndex = (int)(pageStart >> PageToPteShift);
+ ref ulong pageRef = ref _pageTable[pageIndex];
+
+ ulong pte = Volatile.Read(ref pageRef);
+ ulong state = ((pte >> bit) & 3);
+
+ if (state >= tag)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write);
+ return;
+ }
+ else if (state == 0)
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+ }
+ else
+ {
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageTable[pageIndex++];
+
+ ulong pte = Volatile.Read(ref pageRef);
+ ulong mappedMask = mask & BlockMappedMask;
+
+ ulong mappedPte = pte | (pte >> 1);
+ if ((mappedPte & mappedMask) != mappedMask)
+ {
+ ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
+ }
+
+ pte &= mask;
+ if ((pte & anyTrackingTag) != 0) // Search for any tracking.
+ {
+ // Writes trigger any tracking.
+ // Only trigger tracking from reads if both bits are set on any page.
+ if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write);
+ break;
+ }
+ }
+
+ mask = ulong.MaxValue;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Computes the number of pages in a virtual address range.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <param name="startVa">The virtual address of the beginning of the first page</param>
+ /// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetPagesCount(ulong va, ulong size, out ulong startVa)
+ {
+ // WARNING: Always check if ulong does not overflow during the operations.
+ startVa = va & ~(ulong)PageMask;
+ ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
+
+ return (int)(vaSpan / PageSize);
+ }
+
+ /// <inheritdoc/>
+ public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ // Protection is inverted on software pages, since the default value is 0.
+ protection = (~protection) & MemoryPermission.ReadAndWrite;
+
+ int pages = GetPagesCount(va, size, out va);
+ ulong pageStart = va >> PageBits;
+
+ if (pages == 1)
+ {
+ ulong protTag = protection switch
+ {
+ MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
+ MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
+ _ => (ulong)HostMappedPtBits.ReadWriteTracked,
+ };
+
+ int bit = (int)((pageStart & 31) << 1);
+
+ ulong tagMask = 3UL << bit;
+ ulong invTagMask = ~tagMask;
+
+ ulong tag = protTag << bit;
+
+ int pageIndex = (int)(pageStart >> PageToPteShift);
+ ref ulong pageRef = ref _pageTable[pageIndex];
+
+ ulong pte;
+
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+ }
+ while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
+ }
+ else
+ {
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ ulong protTag = protection switch
+ {
+ MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
+ MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
+ _ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
+ };
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageTable[pageIndex++];
+
+ ulong pte;
+ ulong mappedMask;
+
+ // Change the protection of all 2 bit entries that are mapped.
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+
+ mappedMask = pte | (pte >> 1);
+ mappedMask |= (mappedMask & BlockMappedMask) << 1;
+ mappedMask &= mask; // Only update mapped pages within the given range.
+ }
+ while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
+
+ mask = ulong.MaxValue;
+ }
+ }
+
+ protection = protection switch
+ {
+ MemoryPermission.None => MemoryPermission.ReadAndWrite,
+ MemoryPermission.Write => MemoryPermission.Read,
+ _ => MemoryPermission.None
+ };
+
+ _addressSpace.Reprotect(va, size, protection, false);
+ }
+
+ /// <inheritdoc/>
+ public CpuRegionHandle BeginTracking(ulong address, ulong size)
+ {
+ return new CpuRegionHandle(Tracking.BeginTracking(address, size));
+ }
+
+ /// <inheritdoc/>
+ public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ulong granularity)
+ {
+ return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, granularity));
+ }
+
+ /// <inheritdoc/>
+ public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+ {
+ return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
+ }
+
+ /// <summary>
+ /// Adds the given address mapping to the page table.
+ /// </summary>
+ /// <param name="va">Virtual memory address</param>
+ /// <param name="size">Size to be mapped</param>
+ private void AddMapping(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ ulong mask = startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask &= endMask;
+ }
+
+ ref ulong pageRef = ref _pageTable[pageIndex++];
+
+ ulong pte;
+ ulong mappedMask;
+
+ // Map all 2-bit entries that are unmapped.
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+
+ mappedMask = pte | (pte >> 1);
+ mappedMask |= (mappedMask & BlockMappedMask) << 1;
+ mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
+ }
+ while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
+
+ mask = ulong.MaxValue;
+ }
+ }
+
+ /// <summary>
+ /// Removes the given address mapping from the page table.
+ /// </summary>
+ /// <param name="va">Virtual memory address</param>
+ /// <param name="size">Size to be unmapped</param>
+ private void RemoveMapping(ulong va, ulong size)
+ {
+ int pages = GetPagesCount(va, size, out _);
+ ulong pageStart = va >> PageBits;
+ ulong pageEnd = pageStart + (ulong)pages;
+
+ GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
+
+ startMask = ~startMask;
+ endMask = ~endMask;
+
+ ulong mask = startMask;
+
+ while (pageIndex <= pageEndIndex)
+ {
+ if (pageIndex == pageEndIndex)
+ {
+ mask |= endMask;
+ }
+
+ ref ulong pageRef = ref _pageTable[pageIndex++];
+ ulong pte;
+
+ do
+ {
+ pte = Volatile.Read(ref pageRef);
+ }
+ while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
+
+ mask = 0;
+ }
+ }
+
+ /// <summary>
+ /// Disposes of resources used by the memory manager.
+ /// </summary>
+ protected override void Destroy()
+ {
+ _addressSpaceMirror.Dispose();
+ _addressSpace.Dispose();
+ _memoryEh.Dispose();
+ }
+
+ private void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
+ }
+}
diff --git a/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
index 8204a13e..344b1a78 100644
--- a/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.Tracking
}
public void Dispose() => _impl.Dispose();
+ public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
index 6a530b0e..acb27b40 100644
--- a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
@@ -19,6 +19,8 @@ namespace Ryujinx.Cpu.Tracking
}
public void Dispose() => _impl.Dispose();
+ public bool DirtyOrVolatile() => _impl.DirtyOrVolatile();
+ public void ForceDirty() => _impl.ForceDirty();
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
public void RegisterDirtyEvent(Action action) => _impl.RegisterDirtyEvent(action);
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
diff --git a/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
index e38babfc..944e4c02 100644
--- a/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.Tracking
}
public void Dispose() => _impl.Dispose();
+ public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);