aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Memory/Tracking
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Memory/Tracking')
-rw-r--r--Ryujinx.Memory/Tracking/AbstractRegion.cs63
-rw-r--r--Ryujinx.Memory/Tracking/IMultiRegionHandle.cs48
-rw-r--r--Ryujinx.Memory/Tracking/IRegionHandle.cs16
-rw-r--r--Ryujinx.Memory/Tracking/IVirtualMemoryManager.cs9
-rw-r--r--Ryujinx.Memory/Tracking/MemoryTracking.cs321
-rw-r--r--Ryujinx.Memory/Tracking/MultiRegionHandle.cs134
-rw-r--r--Ryujinx.Memory/Tracking/PhysicalRegion.cs97
-rw-r--r--Ryujinx.Memory/Tracking/RegionHandle.cs134
-rw-r--r--Ryujinx.Memory/Tracking/RegionSignal.cs4
-rw-r--r--Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs236
-rw-r--r--Ryujinx.Memory/Tracking/VirtualRegion.cs165
11 files changed, 1227 insertions, 0 deletions
diff --git a/Ryujinx.Memory/Tracking/AbstractRegion.cs b/Ryujinx.Memory/Tracking/AbstractRegion.cs
new file mode 100644
index 00000000..ffac439f
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/AbstractRegion.cs
@@ -0,0 +1,63 @@
+using Ryujinx.Memory.Range;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A region of memory.
+ /// </summary>
+ abstract class AbstractRegion : INonOverlappingRange
+ {
+ /// <summary>
+ /// Base address.
+ /// </summary>
+ public ulong Address { get; }
+
+ /// <summary>
+ /// Size of the range in bytes.
+ /// </summary>
+ public ulong Size { get; protected set; }
+
+ /// <summary>
+ /// End address.
+ /// </summary>
+ public ulong EndAddress => Address + Size;
+
+ /// <summary>
+ /// Create a new region.
+ /// </summary>
+ /// <param name="address">Base address</param>
+ /// <param name="size">Size of the range</param>
+ protected AbstractRegion(ulong address, ulong size)
+ {
+ Address = address;
+ Size = size;
+ }
+
+ /// <summary>
+ /// Check if this range overlaps with another.
+ /// </summary>
+ /// <param name="address">Base address</param>
+ /// <param name="size">Size of the range</param>
+ /// <returns>True if overlapping, false otherwise</returns>
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ /// <summary>
+ /// Signals to the handles that a memory event has occurred, and unprotects the region. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ /// <param name="address">Address accessed</param>
+ /// <param name="size">Size of the region affected in bytes</param>
+ /// <param name="write">Whether the region was written to or read</param>
+ public abstract void Signal(ulong address, ulong size, bool write);
+
+ /// <summary>
+ /// Split this region into two, around the specified address.
+ /// This region is updated to end at the split address, and a new region is created to represent past that point.
+ /// </summary>
+ /// <param name="splitAddress">Address to split the region around</param>
+ /// <returns>The second part of the split region, with start address at the given split.</returns>
+ public abstract INonOverlappingRange Split(ulong splitAddress);
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs
new file mode 100644
index 00000000..357b8c5c
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Ryujinx.Memory.Tracking
+{
+ public interface IMultiRegionHandle : IDisposable
+ {
+ /// <summary>
+ /// True if any write has occurred to the whole region since the last use of QueryModified (with no subregion specified).
+ /// </summary>
+ bool Dirty { get; }
+
+ /// <summary>
+ /// Check if any part of the region has been modified, and perform an action for each.
+ /// Contiguous modified regions are combined.
+ /// </summary>
+ /// <param name="modifiedAction">Action to perform for modified regions</param>
+ void QueryModified(Action<ulong, ulong> modifiedAction);
+
+
+ /// <summary>
+ /// Check if part of the region has been modified within a given range, and perform an action for each.
+ /// The range is aligned to the level of granularity of the contained handles.
+ /// Contiguous modified regions are combined.
+ /// </summary>
+ /// <param name="address">Start address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <param name="modifiedAction">Action to perform for modified regions</param>
+ void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction);
+
+ /// <summary>
+ /// Check if part of the region has been modified within a given range, and perform an action for each.
+ /// The sequence number provided is compared with each handle's saved sequence number.
+ /// If it is equal, then the handle's dirty flag is ignored. Otherwise, the sequence number is saved.
+ /// The range is aligned to the level of granularity of the contained handles.
+ /// Contiguous modified regions are combined.
+ /// </summary>
+ /// <param name="address">Start address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <param name="modifiedAction">Action to perform for modified regions</param>
+ /// <param name="sequenceNumber">Current sequence number</param>
+ void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber);
+
+ /// <summary>
+ /// Signal that one of the subregions of this multi-region has been modified. This sets the overall dirty flag.
+ /// </summary>
+ void SignalWrite();
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs
new file mode 100644
index 00000000..33628da6
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/IRegionHandle.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.Memory.Tracking
+{
+ public interface IRegionHandle : IDisposable
+ {
+ bool Dirty { get; }
+
+ ulong Address { get; }
+ ulong Size { get; }
+ ulong EndAddress { get; }
+
+ void Reprotect();
+ void RegisterAction(RegionSignal action);
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/IVirtualMemoryManager.cs b/Ryujinx.Memory/Tracking/IVirtualMemoryManager.cs
new file mode 100644
index 00000000..6b5474e1
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/IVirtualMemoryManager.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Memory.Tracking
+{
+ public interface IVirtualMemoryManager
+ {
+ (ulong address, ulong size)[] GetPhysicalRegions(ulong va, ulong size);
+
+ void TrackingReprotect(ulong va, ulong size, MemoryPermission protection);
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
new file mode 100644
index 00000000..779166c4
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -0,0 +1,321 @@
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// Manages memory tracking for a given virutal/physical memory block.
+ /// </summary>
+ public class MemoryTracking
+ {
+ private readonly IVirtualMemoryManager _memoryManager;
+ private readonly MemoryBlock _block;
+
+ // Only use these from within the lock.
+ private readonly NonOverlappingRangeList<VirtualRegion> _virtualRegions;
+ private readonly NonOverlappingRangeList<PhysicalRegion> _physicalRegions;
+
+ // Only use these from within the lock.
+ private readonly VirtualRegion[] _virtualResults = new VirtualRegion[10];
+ private readonly PhysicalRegion[] _physicalResults = new PhysicalRegion[10];
+
+ private readonly int _pageSize;
+
+ /// <summary>
+ /// This lock must be obtained when traversing or updating the region-handle hierarchy.
+ /// It is not required when reading dirty flags.
+ /// </summary>
+ internal object TrackingLock = new object();
+
+ public bool EnablePhysicalProtection { get; set; }
+
+ /// <summary>
+ /// Create a new tracking structure for the given "physical" memory block,
+ /// with a given "virtual" memory manager that will provide mappings and virtual memory protection.
+ /// </summary>
+ /// <param name="memoryManager">Virtual memory manager</param>
+ /// <param name="block">Physical memory block</param>
+ /// <param name="pageSize">Page size of the virtual memory space</param>
+ public MemoryTracking(IVirtualMemoryManager memoryManager, MemoryBlock block, int pageSize)
+ {
+ _memoryManager = memoryManager;
+ _block = block;
+ _pageSize = pageSize;
+
+ _virtualRegions = new NonOverlappingRangeList<VirtualRegion>();
+ _physicalRegions = new NonOverlappingRangeList<PhysicalRegion>();
+ }
+
+ private (ulong address, ulong size) PageAlign(ulong address, ulong size)
+ {
+ ulong pageMask = (ulong)_pageSize - 1;
+ ulong rA = address & ~pageMask;
+ ulong rS = ((address + size + pageMask) & ~pageMask) - rA;
+ return (rA, rS);
+ }
+
+ /// <summary>
+ /// Indicate that a virtual region has been mapped, and which physical region it has been mapped to.
+ /// Should be called after the mapping is complete.
+ /// </summary>
+ /// <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)
+ {
+ // A mapping may mean we need to re-evaluate each VirtualRegion's affected area.
+ // Find all handles that overlap with the range, we need to recalculate their physical regions
+
+ lock (TrackingLock)
+ {
+ var results = _virtualResults;
+ int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref results);
+
+ for (int i = 0; i < count; i++)
+ {
+ VirtualRegion region = results[i];
+ region.RecalculatePhysicalChildren();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Indicate that a virtual region has been unmapped.
+ /// Should be called after the unmapping is complete.
+ /// </summary>
+ /// <param name="va">Virtual memory address</param>
+ /// <param name="size">Size to be unmapped</param>
+ public void Unmap(ulong va, ulong size)
+ {
+ // An unmapping may mean we need to re-evaluate each VirtualRegion's affected area.
+ // Find all handles that overlap with the range, we need to recalculate their physical regions
+
+ lock (TrackingLock)
+ {
+ var results = _virtualResults;
+ int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref results);
+
+ for (int i = 0; i < count; i++)
+ {
+ VirtualRegion region = results[i];
+ region.RecalculatePhysicalChildren();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get a list of virtual regions that a handle covers.
+ /// </summary>
+ /// <param name="va">Starting virtual memory address of the handle</param>
+ /// <param name="size">Size of the handle's memory region</param>
+ /// <returns>A list of virtual regions within the given range</returns>
+ internal List<VirtualRegion> GetVirtualRegionsForHandle(ulong va, ulong size)
+ {
+ List<VirtualRegion> result = new List<VirtualRegion>();
+ _virtualRegions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size));
+
+ return result;
+ }
+
+ /// <summary>
+ /// Get a list of physical regions that a virtual region covers.
+ /// Note that this becomes outdated if the virtual or physical regions are unmapped or remapped.
+ /// </summary>
+ /// <param name="va">Virtual memory address</param>
+ /// <param name="size">Size of the virtual region</param>
+ /// <returns>A list of physical regions the virtual region covers</returns>
+ internal List<PhysicalRegion> GetPhysicalRegionsForVirtual(ulong va, ulong size)
+ {
+ List<PhysicalRegion> result = new List<PhysicalRegion>();
+
+ // Get a list of physical regions for this virtual region, from our injected virtual mapping function.
+ (ulong Address, ulong Size)[] physicalRegions = _memoryManager.GetPhysicalRegions(va, size);
+
+ if (physicalRegions != null)
+ {
+ foreach (var region in physicalRegions)
+ {
+ _physicalRegions.GetOrAddRegions(result, region.Address, region.Size, (pa, size) => new PhysicalRegion(this, pa, size));
+ }
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Remove a virtual region from the range list. This assumes that the lock has been acquired.
+ /// </summary>
+ /// <param name="region">Region to remove</param>
+ internal void RemoveVirtual(VirtualRegion region)
+ {
+ _virtualRegions.Remove(region);
+ }
+
+ /// <summary>
+ /// Remove a physical region from the range list. This assumes that the lock has been acquired.
+ /// </summary>
+ /// <param name="region">Region to remove</param>
+ internal void RemovePhysical(PhysicalRegion region)
+ {
+ _physicalRegions.Remove(region);
+ }
+
+ /// <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>
+ public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ulong granularity)
+ {
+ (address, size) = PageAlign(address, size);
+
+ return new MultiRegionHandle(this, 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>
+ public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+ {
+ (address, size) = PageAlign(address, size);
+
+ return new SmartMultiRegionHandle(this, address, size, granularity);
+ }
+
+ /// <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>
+ public RegionHandle BeginTracking(ulong address, ulong size)
+ {
+ (address, size) = PageAlign(address, size);
+
+ lock (TrackingLock)
+ {
+ RegionHandle handle = new RegionHandle(this, address, size);
+
+ return handle;
+ }
+ }
+
+ /// <summary>
+ /// Signal that a physical memory event happened at the given location.
+ /// </summary>
+ /// <param name="address">Physical address accessed</param>
+ /// <param name="write">Whether the region was written to or read</param>
+ /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
+ public bool PhysicalMemoryEvent(ulong address, bool write)
+ {
+ // Look up the physical region using the region list.
+ // Signal up the chain to relevant handles.
+
+ lock (TrackingLock)
+ {
+ var results = _physicalResults;
+ int count = _physicalRegions.FindOverlapsNonOverlapping(address, 1, ref results); // TODO: get/use the actual access size?
+
+ if (count == 0)
+ {
+ _block.Reprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
+ return false; // We can't handle this - unprotect and return.
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ PhysicalRegion region = results[i];
+ region.Signal(address, 1, write);
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Signal that a virtual memory event happened at the given location (one byte).
+ /// </summary>
+ /// <param name="address">Virtual address accessed</param>
+ /// <param name="write">Whether the address was written to or read</param>
+ /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
+ public bool VirtualMemoryEventTracking(ulong address, bool write)
+ {
+ return VirtualMemoryEvent(address, 1, write);
+ }
+
+ /// <summary>
+ /// Signal that a virtual memory event happened at the given location.
+ /// </summary>
+ /// <param name="address">Virtual address accessed</param>
+ /// <param name="size">Size of the region affected in bytes</param>
+ /// <param name="write">Whether the region was written to or read</param>
+ /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
+ public bool VirtualMemoryEvent(ulong address, ulong size, bool write)
+ {
+ // Look up the virtual region using the region list.
+ // Signal up the chain to relevant handles.
+
+ lock (TrackingLock)
+ {
+ var results = _virtualResults;
+ int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref results);
+
+ if (count == 0)
+ {
+ _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
+ return false; // We can't handle this - it's probably a real invalid access.
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ VirtualRegion region = results[i];
+ region.Signal(address, size, write);
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Reprotect a given physical region, if enabled. This is protected on the memory block provided during initialization.
+ /// </summary>
+ /// <param name="region">Region to reprotect</param>
+ /// <param name="permission">Memory permission to protect with</param>
+ internal void ProtectPhysicalRegion(PhysicalRegion region, MemoryPermission permission)
+ {
+ if (EnablePhysicalProtection)
+ {
+ _block.Reprotect(region.Address, region.Size, permission);
+ }
+ }
+
+ /// <summary>
+ /// Reprotect a given virtual region. The virtual memory manager will handle this.
+ /// </summary>
+ /// <param name="region">Region to reprotect</param>
+ /// <param name="permission">Memory permission to protect with</param>
+ internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission)
+ {
+ _memoryManager.TrackingReprotect(region.Address, region.Size, permission);
+ }
+
+ /// <summary>
+ /// Returns the number of virtual and physical regions currently being tracked.
+ /// Useful for tests and metrics.
+ /// </summary>
+ /// <returns>The number of virtual regions, and the number of physical regions</returns>
+ public (int VirtualCount, int PhysicalCount) GetRegionCounts()
+ {
+ lock (TrackingLock)
+ {
+ return (_virtualRegions.Count, _physicalRegions.Count);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
new file mode 100644
index 00000000..02ae3a8b
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
@@ -0,0 +1,134 @@
+using System;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A region handle that tracks a large region using many smaller handles, to provide
+ /// granular tracking that can be used to track partial updates.
+ /// </summary>
+ public class MultiRegionHandle : IMultiRegionHandle
+ {
+ /// <summary>
+ /// A list of region handles for each granularity sized chunk of the whole region.
+ /// </summary>
+ private readonly RegionHandle[] _handles;
+ private readonly ulong Address;
+ private readonly ulong Granularity;
+ private readonly ulong Size;
+
+ public bool Dirty { get; private set; } = true;
+
+ internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity)
+ {
+ _handles = new RegionHandle[size / granularity];
+ Granularity = granularity;
+
+ for (int i = 0; i < _handles.Length; i++)
+ {
+ RegionHandle handle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
+ handle.Parent = this;
+ _handles[i] = handle;
+ }
+
+ Address = address;
+ Size = size;
+ }
+
+ public void SignalWrite()
+ {
+ Dirty = true;
+ }
+
+ public void QueryModified(Action<ulong, ulong> modifiedAction)
+ {
+ if (!Dirty)
+ {
+ return;
+ }
+
+ Dirty = false;
+
+ QueryModified(Address, Size, modifiedAction);
+ }
+
+ public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
+ {
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ ulong rgStart = _handles[startHandle].Address;
+ ulong rgSize = 0;
+
+ for (int i = startHandle; i <= lastHandle; i++)
+ {
+ RegionHandle handle = _handles[i];
+
+ if (handle.Dirty)
+ {
+ rgSize += handle.Size;
+ handle.Reprotect();
+ }
+ else
+ {
+ // Submit the region scanned so far as dirty
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ rgSize = 0;
+ }
+ rgStart = handle.EndAddress;
+ }
+ }
+
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ }
+ }
+
+ public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
+ {
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ ulong rgStart = _handles[startHandle].Address;
+ ulong rgSize = 0;
+
+ for (int i = startHandle; i <= lastHandle; i++)
+ {
+ RegionHandle handle = _handles[i];
+
+ if (handle.Dirty && sequenceNumber != handle.SequenceNumber)
+ {
+ rgSize += handle.Size;
+ handle.Reprotect();
+ }
+ else
+ {
+ // Submit the region scanned so far as dirty
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ rgSize = 0;
+ }
+ rgStart = handle.EndAddress;
+ }
+
+ handle.SequenceNumber = sequenceNumber;
+ }
+
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (var handle in _handles)
+ {
+ handle.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/PhysicalRegion.cs b/Ryujinx.Memory/Tracking/PhysicalRegion.cs
new file mode 100644
index 00000000..683186b1
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/PhysicalRegion.cs
@@ -0,0 +1,97 @@
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A region of physical memory.
+ /// </summary>
+ class PhysicalRegion : AbstractRegion
+ {
+ public List<VirtualRegion> VirtualParents = new List<VirtualRegion>();
+ public MemoryPermission Protection { get; private set; }
+ public MemoryTracking Tracking;
+
+ public PhysicalRegion(MemoryTracking tracking, ulong address, ulong size) : base(address, size)
+ {
+ Tracking = tracking;
+ Protection = MemoryPermission.ReadAndWrite;
+ }
+
+ public override void Signal(ulong address, ulong size, bool write)
+ {
+ Protection = MemoryPermission.ReadAndWrite;
+ Tracking.ProtectPhysicalRegion(this, MemoryPermission.ReadAndWrite); // Remove our protection immedately.
+ foreach (var parent in VirtualParents)
+ {
+ parent.Signal(address, size, write);
+ }
+ }
+
+ /// <summary>
+ /// Update the protection of this region, based on our parent's requested protection.
+ /// </summary>
+ public void UpdateProtection()
+ {
+ // Re-evaluate protection, and commit to the block.
+
+ lock (Tracking.TrackingLock)
+ {
+ MemoryPermission result = MemoryPermission.ReadAndWrite;
+ foreach (var parent in VirtualParents)
+ {
+ result &= parent.GetRequiredPermission();
+ if (result == 0) break;
+ }
+
+ if (Protection != result)
+ {
+ Protection = result;
+ Tracking.ProtectPhysicalRegion(this, result);
+ }
+ }
+ }
+
+ public override INonOverlappingRange Split(ulong splitAddress)
+ {
+ PhysicalRegion newRegion = new PhysicalRegion(Tracking, splitAddress, EndAddress - splitAddress);
+ Size = splitAddress - Address;
+
+ // The new region inherits all of our parents.
+ newRegion.VirtualParents = new List<VirtualRegion>(VirtualParents);
+ foreach (var parent in VirtualParents)
+ {
+ parent.AddChild(newRegion);
+ }
+
+ return newRegion;
+ }
+
+ /// <summary>
+ /// Remove a parent virtual region from this physical region. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ /// <param name="region">Region to remove</param>
+ /// <returns>True if there are no more parents and we should be removed, false otherwise.</returns>
+ public bool RemoveParent(VirtualRegion region)
+ {
+ VirtualParents.Remove(region);
+ UpdateProtection();
+ if (VirtualParents.Count == 0)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Deletes this physical region if there are no more virtual parents.
+ /// </summary>
+ public void TryDelete()
+ {
+ if (VirtualParents.Count == 0)
+ {
+ Tracking.RemovePhysical(this);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
new file mode 100644
index 00000000..c00d039b
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -0,0 +1,134 @@
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
+ /// and an action can be performed when the region is read to or written from.
+ /// </summary>
+ public class RegionHandle : IRegionHandle, IRange
+ {
+ public bool Dirty { get; private set; } = true;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+ public ulong EndAddress { get; }
+
+ internal IMultiRegionHandle Parent { get; set; }
+ internal int SequenceNumber { get; set; }
+
+ private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
+ private readonly List<VirtualRegion> _regions;
+ private readonly MemoryTracking _tracking;
+
+ internal MemoryPermission RequiredPermission => _preAction != null ? MemoryPermission.None : (Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read);
+
+ /// <summary>
+ /// Create a new region handle. The handle is registered with the given tracking object,
+ /// and will be notified of any changes to the specified region.
+ /// </summary>
+ /// <param name="tracking">Tracking object for the target memory block</param>
+ /// <param name="address">Virtual address of the region to track</param>
+ /// <param name="size">Size of the region to track</param>
+ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size)
+ {
+ Address = address;
+ Size = size;
+ EndAddress = address + size;
+
+ _tracking = tracking;
+ _regions = tracking.GetVirtualRegionsForHandle(address, size);
+ foreach (var region in _regions)
+ {
+ region.Handles.Add(this);
+ }
+ }
+
+ /// <summary>
+ /// Signal that a memory action occurred within this handle's virtual regions.
+ /// </summary>
+ /// <param name="write">Whether the region was written to or read</param>
+ internal void Signal(ulong address, ulong size, bool write)
+ {
+ RegionSignal action = Interlocked.Exchange(ref _preAction, null);
+ action?.Invoke(address, size);
+
+ if (write)
+ {
+ Dirty = true;
+ Parent?.SignalWrite();
+ }
+ }
+
+ /// <summary>
+ /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
+ /// </summary>
+ public void Reprotect()
+ {
+ Dirty = false;
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ region.UpdateProtection();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Register an action to perform when the tracked region is read or written.
+ /// The action is automatically removed after it runs.
+ /// </summary>
+ /// <param name="action">Action to call on read or write</param>
+ public void RegisterAction(RegionSignal action)
+ {
+ RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action);
+ if (lastAction == null && action != lastAction)
+ {
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ region.UpdateProtection();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Add a child virtual region to this handle.
+ /// </summary>
+ /// <param name="region">Virtual region to add as a child</param>
+ internal void AddChild(VirtualRegion region)
+ {
+ _regions.Add(region);
+ }
+
+ /// <summary>
+ /// Check if this region overlaps with another.
+ /// </summary>
+ /// <param name="address">Base address</param>
+ /// <param name="size">Size of the region</param>
+ /// <returns>True if overlapping, false otherwise</returns>
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ /// <summary>
+ /// Dispose the handle. Within the tracking lock, this removes references from virtual and physical regions.
+ /// </summary>
+ public void Dispose()
+ {
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ region.RemoveHandle(this);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/RegionSignal.cs b/Ryujinx.Memory/Tracking/RegionSignal.cs
new file mode 100644
index 00000000..c8a28d7d
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/RegionSignal.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Memory.Tracking
+{
+ public delegate void RegionSignal(ulong address, ulong size);
+}
diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
new file mode 100644
index 00000000..60188400
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A MultiRegionHandle that attempts to segment a region's handles into the regions requested
+ /// to avoid iterating over granular chunks for canonically large regions.
+ /// If minimum granularity is to be expected, use MultiRegionHandle.
+ /// </summary>
+ public class SmartMultiRegionHandle : IMultiRegionHandle
+ {
+ /// <summary>
+ /// A list of region handles starting at each granularity size increment.
+ /// </summary>
+ private readonly RegionHandle[] _handles;
+ private readonly ulong _address;
+ private readonly ulong _granularity;
+ private readonly ulong _size;
+ private MemoryTracking _tracking;
+
+ public bool Dirty { get; private set; } = true;
+
+ internal SmartMultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity)
+ {
+ // For this multi-region handle, the handle list starts empty.
+ // As regions are queried, they are added to the _handles array at their start index.
+ // When a region being added overlaps another, the existing region is split.
+ // A query can therefore scan multiple regions, though with no overlaps they can cover a large area.
+
+ _tracking = tracking;
+ _handles = new RegionHandle[size / granularity];
+ _granularity = granularity;
+
+ _address = address;
+ _size = size;
+ }
+
+ public void SignalWrite()
+ {
+ Dirty = true;
+ }
+
+ public void QueryModified(Action<ulong, ulong> modifiedAction)
+ {
+ if (!Dirty)
+ {
+ return;
+ }
+
+ Dirty = false;
+
+ QueryModified(_address, _size, modifiedAction);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ulong HandlesToBytes(int handles)
+ {
+ return (ulong)handles * _granularity;
+ }
+
+ private void SplitHandle(int handleIndex, int splitIndex)
+ {
+ RegionHandle handle = _handles[handleIndex];
+ ulong address = _address + HandlesToBytes(handleIndex);
+ ulong size = HandlesToBytes(splitIndex - handleIndex);
+
+ // First, the target handle must be removed. Its data can still be used to determine the new handles.
+ handle.Dispose();
+
+ RegionHandle splitLow = _tracking.BeginTracking(address, size);
+ splitLow.Parent = this;
+ _handles[handleIndex] = splitLow;
+
+ RegionHandle splitHigh = _tracking.BeginTracking(address + size, handle.Size - size);
+ splitHigh.Parent = this;
+ _handles[splitIndex] = splitHigh;
+ }
+
+ private void CreateHandle(int startHandle, int lastHandle)
+ {
+ ulong startAddress = _address + HandlesToBytes(startHandle);
+
+ // Scan for the first handle before us. If it's overlapping us, it must be split.
+ for (int i = startHandle - 1; i >= 0; i--)
+ {
+ RegionHandle handle = _handles[i];
+ if (handle != null)
+ {
+ if (handle.EndAddress > startAddress)
+ {
+ SplitHandle(i, startHandle);
+ return; // The remainer of this handle should be filled in later on.
+ }
+ break;
+ }
+ }
+
+ // Scan for handles after us. We should create a handle that goes up to this handle's start point, if present.
+ for (int i = startHandle + 1; i <= lastHandle; i++)
+ {
+ RegionHandle handle = _handles[i];
+ if (handle != null)
+ {
+ // Fill up to the found handle.
+ handle = _tracking.BeginTracking(startAddress, HandlesToBytes(i - startHandle));
+ handle.Parent = this;
+ _handles[startHandle] = handle;
+ return;
+ }
+ }
+
+ // Can fill the whole range.
+ _handles[startHandle] = _tracking.BeginTracking(startAddress, HandlesToBytes(1 + lastHandle - startHandle));
+ _handles[startHandle].Parent = this;
+ }
+
+ public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
+ {
+ int startHandle = (int)((address - _address) / _granularity);
+ int lastHandle = (int)((address + (size - 1) - _address) / _granularity);
+
+ ulong rgStart = _address + (ulong)startHandle * _granularity;
+ ulong rgSize = 0;
+
+ ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity;
+
+ int i = startHandle;
+
+ while (i <= lastHandle)
+ {
+ RegionHandle handle = _handles[i];
+ if (handle == null)
+ {
+ // Missing handle. A new handle must be created.
+ CreateHandle(i, lastHandle);
+ handle = _handles[i];
+ }
+
+ if (handle.EndAddress > endAddress)
+ {
+ // End address of handle is beyond the end of the search. Force a split.
+ SplitHandle(i, lastHandle + 1);
+ handle = _handles[i];
+ }
+
+ if (handle.Dirty)
+ {
+ rgSize += handle.Size;
+ handle.Reprotect();
+ }
+ else
+ {
+ // Submit the region scanned so far as dirty
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ rgSize = 0;
+ }
+ rgStart = handle.EndAddress;
+ }
+
+ i += (int)(handle.Size / _granularity);
+ }
+
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ }
+ }
+
+ public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
+ {
+ int startHandle = (int)((address - _address) / _granularity);
+ int lastHandle = (int)((address + (size - 1) - _address) / _granularity);
+
+ ulong rgStart = _address + (ulong)startHandle * _granularity;
+ ulong rgSize = 0;
+
+ ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity;
+
+ int i = startHandle;
+
+ while (i <= lastHandle)
+ {
+ RegionHandle handle = _handles[i];
+ if (handle == null)
+ {
+ // Missing handle. A new handle must be created.
+ CreateHandle(i, lastHandle);
+ handle = _handles[i];
+ }
+
+ if (handle.EndAddress > endAddress)
+ {
+ // End address of handle is beyond the end of the search. Force a split.
+ SplitHandle(i, lastHandle + 1);
+ handle = _handles[i];
+ }
+
+ if (handle.Dirty && sequenceNumber != handle.SequenceNumber)
+ {
+ rgSize += handle.Size;
+ handle.Reprotect();
+ }
+ else
+ {
+ // Submit the region scanned so far as dirty
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ rgSize = 0;
+ }
+ rgStart = handle.EndAddress;
+ }
+
+ handle.SequenceNumber = sequenceNumber;
+
+ i += (int)(handle.Size / _granularity);
+ }
+
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (var handle in _handles)
+ {
+ handle?.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
new file mode 100644
index 00000000..90fb55d6
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -0,0 +1,165 @@
+using Ryujinx.Memory.Range;
+using System.Collections.Generic;
+
+namespace Ryujinx.Memory.Tracking
+{
+ /// <summary>
+ /// A region of virtual memory.
+ /// </summary>
+ class VirtualRegion : AbstractRegion
+ {
+ public List<RegionHandle> Handles = new List<RegionHandle>();
+ private List<PhysicalRegion> _physicalChildren;
+
+ private readonly MemoryTracking _tracking;
+
+ public VirtualRegion(MemoryTracking tracking, ulong address, ulong size) : base(address, size)
+ {
+ _tracking = tracking;
+
+ UpdatePhysicalChildren();
+ }
+
+ public override void Signal(ulong address, ulong size, bool write)
+ {
+ _tracking.ProtectVirtualRegion(this, MemoryPermission.ReadAndWrite); // Remove our protection immedately.
+
+ foreach (var handle in Handles)
+ {
+ handle.Signal(address, size, write);
+ }
+ }
+
+ /// <summary>
+ /// Clears all physical children of this region. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ private void ClearPhysicalChildren()
+ {
+ if (_physicalChildren != null)
+ {
+ foreach (PhysicalRegion child in _physicalChildren)
+ {
+ child.RemoveParent(this);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the physical children of this region, assuming that they are clear and that the tracking lock has been obtained.
+ /// </summary>
+ private void UpdatePhysicalChildren()
+ {
+ _physicalChildren = _tracking.GetPhysicalRegionsForVirtual(Address, Size);
+
+ foreach (PhysicalRegion child in _physicalChildren)
+ {
+ child.VirtualParents.Add(this);
+ }
+ }
+
+ /// <summary>
+ /// Recalculates the physical children for this virtual region. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ public void RecalculatePhysicalChildren()
+ {
+ ClearPhysicalChildren();
+ UpdatePhysicalChildren();
+ }
+
+ /// <summary>
+ /// Gets the strictest permission that the child handles demand. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ /// <returns>Protection level that this region demands</returns>
+ public MemoryPermission GetRequiredPermission()
+ {
+ // Start with Read/Write, each handle can strip off permissions as necessary.
+ // Assumes the tracking lock has already been obtained.
+
+ MemoryPermission result = MemoryPermission.ReadAndWrite;
+
+ foreach (var handle in Handles)
+ {
+ result &= handle.RequiredPermission;
+ if (result == 0) return result;
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Updates the protection for this virtual region, and all child physical regions.
+ /// </summary>
+ public void UpdateProtection()
+ {
+ // Re-evaluate protection for all physical children.
+
+ _tracking.ProtectVirtualRegion(this, GetRequiredPermission());
+ lock (_tracking.TrackingLock)
+ {
+ foreach (var child in _physicalChildren)
+ {
+ child.UpdateProtection();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes a handle from this virtual region. If there are no handles left, this virtual region is removed.
+ /// </summary>
+ /// <param name="handle">Handle to remove</param>
+ public void RemoveHandle(RegionHandle handle)
+ {
+ bool removedRegions = false;
+ lock (_tracking.TrackingLock)
+ {
+ Handles.Remove(handle);
+ UpdateProtection();
+ if (Handles.Count == 0)
+ {
+ _tracking.RemoveVirtual(this);
+ foreach (var child in _physicalChildren)
+ {
+ removedRegions |= child.RemoveParent(this);
+ }
+ }
+ }
+
+ if (removedRegions)
+ {
+ // The first lock will unprotect any regions that have been removed. This second lock will remove them.
+ lock (_tracking.TrackingLock)
+ {
+ foreach (var child in _physicalChildren)
+ {
+ child.TryDelete();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Add a child physical region to this virtual region. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ /// <param name="region">Physical region to add as a child</param>
+ public void AddChild(PhysicalRegion region)
+ {
+ _physicalChildren.Add(region);
+ }
+
+ public override INonOverlappingRange Split(ulong splitAddress)
+ {
+ ClearPhysicalChildren();
+ VirtualRegion newRegion = new VirtualRegion(_tracking, splitAddress, EndAddress - splitAddress);
+ Size = splitAddress - Address;
+ UpdatePhysicalChildren();
+
+ // The new region inherits all of our parents.
+ newRegion.Handles = new List<RegionHandle>(Handles);
+ foreach (var parent in Handles)
+ {
+ parent.AddChild(newRegion);
+ }
+
+ return newRegion;
+ }
+ }
+}