diff options
Diffstat (limited to 'Ryujinx.Memory/Tracking')
| -rw-r--r-- | Ryujinx.Memory/Tracking/IMultiRegionHandle.cs | 7 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/IRegionHandle.cs | 1 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/MemoryTracking.cs | 20 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/MultiRegionHandle.cs | 16 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/RegionHandle.cs | 77 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs | 11 | ||||
| -rw-r--r-- | Ryujinx.Memory/Tracking/VirtualRegion.cs | 22 |
7 files changed, 141 insertions, 13 deletions
diff --git a/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs index 357b8c5c..71bd602f 100644 --- a/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs +++ b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs @@ -10,6 +10,13 @@ namespace Ryujinx.Memory.Tracking bool Dirty { get; } /// <summary> + /// Force the range of handles to be dirty, without reprotecting. + /// </summary> + /// <param name="address">Start address of the range</param> + /// <param name="size">Size of the range</param> + public void ForceDirty(ulong address, ulong size); + + /// <summary> /// Check if any part of the region has been modified, and perform an action for each. /// Contiguous modified regions are combined. /// </summary> diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs index cd33e5c8..ec802cb3 100644 --- a/Ryujinx.Memory/Tracking/IRegionHandle.cs +++ b/Ryujinx.Memory/Tracking/IRegionHandle.cs @@ -10,6 +10,7 @@ namespace Ryujinx.Memory.Tracking ulong Size { get; } ulong EndAddress { get; } + void ForceDirty(); void Reprotect(bool asDirty = false); void RegisterAction(RegionSignal action); } diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs index 425552f8..70951e8c 100644 --- a/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -9,7 +9,7 @@ namespace Ryujinx.Memory.Tracking public class MemoryTracking { private readonly IVirtualMemoryManager _memoryManager; - private readonly MemoryBlock _block; + private readonly InvalidAccessHandler _invalidAccessHandler; // Only use these from within the lock. private readonly NonOverlappingRangeList<VirtualRegion> _virtualRegions; @@ -25,8 +25,6 @@ namespace Ryujinx.Memory.Tracking /// </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. @@ -34,11 +32,11 @@ namespace Ryujinx.Memory.Tracking /// <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) + public MemoryTracking(IVirtualMemoryManager memoryManager, int pageSize, InvalidAccessHandler invalidAccessHandler = null) { _memoryManager = memoryManager; - _block = block; _pageSize = pageSize; + _invalidAccessHandler = invalidAccessHandler; _virtualRegions = new NonOverlappingRangeList<VirtualRegion>(); } @@ -56,9 +54,8 @@ namespace Ryujinx.Memory.Tracking /// 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) + public void Map(ulong va, 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 @@ -208,6 +205,15 @@ namespace Ryujinx.Memory.Tracking if (count == 0) { + if (!_memoryManager.IsMapped(address)) + { + _invalidAccessHandler?.Invoke(address); + + // We can't continue - it's impossible to remove protection from the page. + // Even if the access handler wants us to continue, we wouldn't be able to. + throw new InvalidMemoryRegionException(); + } + _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite); return false; // We can't handle this - it's probably a real invalid access. } diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs index df154bc2..1f09807a 100644 --- a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs +++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs @@ -34,6 +34,20 @@ namespace Ryujinx.Memory.Tracking Size = size; } + public void ForceDirty(ulong address, ulong size) + { + Dirty = true; + + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + for (int i = startHandle; i <= lastHandle; i++) + { + _handles[i].SequenceNumber--; + _handles[i].ForceDirty(); + } + } + public void SignalWrite() { Dirty = true; @@ -98,7 +112,7 @@ namespace Ryujinx.Memory.Tracking { RegionHandle handle = _handles[i]; - if (handle.Dirty && sequenceNumber != handle.SequenceNumber) + if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile()) { rgSize += handle.Size; handle.Reprotect(); diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs index 5c32fba4..69d77977 100644 --- a/Ryujinx.Memory/Tracking/RegionHandle.cs +++ b/Ryujinx.Memory/Tracking/RegionHandle.cs @@ -11,6 +11,17 @@ namespace Ryujinx.Memory.Tracking /// </summary> public class RegionHandle : IRegionHandle, IRange { + /// <summary> + /// If more than this number of checks have been performed on a dirty flag since its last reprotect, + /// then it is dirtied infrequently. + /// </summary> + private static int CheckCountForInfrequent = 3; + + /// <summary> + /// Number of frequent dirty/consume in a row to make this handle volatile. + /// </summary> + private static int VolatileThreshold = 5; + public bool Dirty { get; private set; } public bool Unmapped { get; private set; } @@ -28,6 +39,10 @@ namespace Ryujinx.Memory.Tracking private readonly MemoryTracking _tracking; private bool _disposed; + private int _checkCount = 0; + private int _volatileCount = 0; + private bool _volatile; + internal MemoryPermission RequiredPermission => _preAction != null ? MemoryPermission.None : (Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read); internal RegionSignal PreAction => _preAction; @@ -56,6 +71,25 @@ namespace Ryujinx.Memory.Tracking } /// <summary> + /// Clear the volatile state of this handle. + /// </summary> + private void ClearVolatile() + { + _volatileCount = 0; + _volatile = false; + } + + /// <summary> + /// Check if this handle is dirty, or if it is volatile. (changes very often) + /// </summary> + /// <returns>True if the handle is dirty or volatile, false otherwise</returns> + public bool DirtyOrVolatile() + { + _checkCount++; + return Dirty || _volatile; + } + + /// <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> @@ -77,18 +111,56 @@ namespace Ryujinx.Memory.Tracking } /// <summary> + /// Force this handle to be dirty, without reprotecting. + /// </summary> + public void ForceDirty() + { + Dirty = true; + } + + /// <summary> /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. /// </summary> public void Reprotect(bool asDirty = false) { + if (_volatile) return; + Dirty = asDirty; + + bool protectionChanged = false; + lock (_tracking.TrackingLock) { foreach (VirtualRegion region in _regions) { - region.UpdateProtection(); + protectionChanged |= region.UpdateProtection(); } } + + if (!protectionChanged) + { + // Counteract the check count being incremented when this handle was forced dirty. + // It doesn't count for protected write tracking. + + _checkCount--; + } + else if (!asDirty) + { + if (_checkCount > 0 && _checkCount < CheckCountForInfrequent) + { + if (++_volatileCount >= VolatileThreshold && _preAction == null) + { + _volatile = true; + return; + } + } + else + { + _volatileCount = 0; + } + + _checkCount = 0; + } } /// <summary> @@ -98,6 +170,8 @@ namespace Ryujinx.Memory.Tracking /// <param name="action">Action to call on read or write</param> public void RegisterAction(RegionSignal action) { + ClearVolatile(); + RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action); if (lastAction == null && action != lastAction) { @@ -142,6 +216,7 @@ namespace Ryujinx.Memory.Tracking if (Unmapped) { + ClearVolatile(); Dirty = false; } } diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs index 8bc10c41..eabbd723 100644 --- a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs +++ b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs @@ -41,6 +41,17 @@ namespace Ryujinx.Memory.Tracking Dirty = true; } + public void ForceDirty(ulong address, ulong size) + { + foreach (var handle in _handles) + { + if (handle != null && handle.OverlapsWith(address, size)) + { + handle.ForceDirty(); + } + } + } + public void RegisterAction(RegionSignal action) { foreach (var handle in _handles) diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs index 696d3560..e758f38e 100644 --- a/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -11,9 +11,11 @@ namespace Ryujinx.Memory.Tracking public List<RegionHandle> Handles = new List<RegionHandle>(); private readonly MemoryTracking _tracking; + private MemoryPermission _lastPermission; - public VirtualRegion(MemoryTracking tracking, ulong address, ulong size) : base(address, size) + public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) { + _lastPermission = lastPermission; _tracking = tracking; } @@ -33,6 +35,8 @@ namespace Ryujinx.Memory.Tracking /// <param name="mapped">True if the region has been mapped, false if unmapped</param> public void SignalMappingChanged(bool mapped) { + _lastPermission = MemoryPermission.Invalid; + foreach (RegionHandle handle in Handles) { handle.SignalMappingChanged(mapped); @@ -61,9 +65,19 @@ namespace Ryujinx.Memory.Tracking /// <summary> /// Updates the protection for this virtual region. /// </summary> - public void UpdateProtection() + public bool UpdateProtection() { - _tracking.ProtectVirtualRegion(this, GetRequiredPermission()); + MemoryPermission permission = GetRequiredPermission(); + + if (_lastPermission != permission) + { + _tracking.ProtectVirtualRegion(this, permission); + _lastPermission = permission; + + return true; + } + + return false; } /// <summary> @@ -85,7 +99,7 @@ namespace Ryujinx.Memory.Tracking public override INonOverlappingRange Split(ulong splitAddress) { - VirtualRegion newRegion = new VirtualRegion(_tracking, splitAddress, EndAddress - splitAddress); + VirtualRegion newRegion = new VirtualRegion(_tracking, splitAddress, EndAddress - splitAddress, _lastPermission); Size = splitAddress - Address; // The new region inherits all of our parents. |
