aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Memory/Tracking/RegionHandle.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Memory/Tracking/RegionHandle.cs')
-rw-r--r--src/Ryujinx.Memory/Tracking/RegionHandle.cs464
1 files changed, 464 insertions, 0 deletions
diff --git a/src/Ryujinx.Memory/Tracking/RegionHandle.cs b/src/Ryujinx.Memory/Tracking/RegionHandle.cs
new file mode 100644
index 00000000..7a59f9f2
--- /dev/null
+++ b/src/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -0,0 +1,464 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+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
+ {
+ /// <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 const int CheckCountForInfrequent = 3;
+
+ /// <summary>
+ /// Number of frequent dirty/consume in a row to make this handle volatile.
+ /// </summary>
+ private const int VolatileThreshold = 5;
+
+ public bool Dirty
+ {
+ get
+ {
+ return Bitmap.IsSet(DirtyBit);
+ }
+ protected set
+ {
+ Bitmap.Set(DirtyBit, value);
+ }
+ }
+
+ internal int SequenceNumber { get; set; }
+ internal int Id { get; }
+
+ public bool Unmapped { get; private set; }
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+ public ulong EndAddress { get; }
+
+ public ulong RealAddress { get; }
+ public ulong RealSize { get; }
+ public ulong RealEndAddress { get; }
+
+ internal IMultiRegionHandle Parent { get; set; }
+
+ private event Action _onDirty;
+
+ private object _preActionLock = new object();
+ private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
+ private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write.
+ private readonly List<VirtualRegion> _regions;
+ private readonly MemoryTracking _tracking;
+ private bool _disposed;
+
+ private int _checkCount = 0;
+ private int _volatileCount = 0;
+ private bool _volatile;
+
+ internal MemoryPermission RequiredPermission
+ {
+ get
+ {
+ // If this is unmapped, allow reprotecting as RW as it can't be dirtied.
+ // This is required for the partial unmap cases where part of the data are still being accessed.
+ if (Unmapped)
+ {
+ return MemoryPermission.ReadAndWrite;
+ }
+
+ if (_preAction != null)
+ {
+ return MemoryPermission.None;
+ }
+
+ return Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read;
+ }
+ }
+
+ internal RegionSignal PreAction => _preAction;
+
+ internal ConcurrentBitmap Bitmap;
+ internal int DirtyBit;
+
+ /// <summary>
+ /// Create a new bitmap backed 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>
+ /// <param name="realAddress">The real, unaligned address of the handle</param>
+ /// <param name="realSize">The real, unaligned size of the handle</param>
+ /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
+ /// <param name="bit">The bit index representing the dirty flag for this handle</param>
+ /// <param name="id">Handle ID</param>
+ /// <param name="mapped">True if the region handle starts mapped</param>
+ internal RegionHandle(
+ MemoryTracking tracking,
+ ulong address,
+ ulong size,
+ ulong realAddress,
+ ulong realSize,
+ ConcurrentBitmap bitmap,
+ int bit,
+ int id,
+ bool mapped = true)
+ {
+ Bitmap = bitmap;
+ DirtyBit = bit;
+
+ Dirty = mapped;
+
+ Id = id;
+
+ Unmapped = !mapped;
+ Address = address;
+ Size = size;
+ EndAddress = address + size;
+
+ RealAddress = realAddress;
+ RealSize = realSize;
+ RealEndAddress = realAddress + realSize;
+
+ _tracking = tracking;
+ _regions = tracking.GetVirtualRegionsForHandle(address, size);
+ foreach (var region in _regions)
+ {
+ region.Handles.Add(this);
+ }
+ }
+
+ /// <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>
+ /// <param name="realAddress">The real, unaligned address of the handle</param>
+ /// <param name="realSize">The real, unaligned size of the handle</param>
+ /// <param name="id">Handle ID</param>
+ /// <param name="mapped">True if the region handle starts mapped</param>
+ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true)
+ {
+ Bitmap = new ConcurrentBitmap(1, mapped);
+
+ Id = id;
+
+ Unmapped = !mapped;
+
+ Address = address;
+ Size = size;
+ EndAddress = address + size;
+
+ RealAddress = realAddress;
+ RealSize = realSize;
+ RealEndAddress = realAddress + realSize;
+
+ _tracking = tracking;
+ _regions = tracking.GetVirtualRegionsForHandle(address, size);
+ foreach (var region in _regions)
+ {
+ region.Handles.Add(this);
+ }
+ }
+
+ /// <summary>
+ /// Replace the bitmap and bit index used to track dirty state.
+ /// </summary>
+ /// <remarks>
+ /// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
+ /// </remarks>
+ /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
+ /// <param name="bit">The bit index representing the dirty flag for this handle</param>
+ internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
+ {
+ // Assumes the tracking lock is held, so nothing else can signal right now.
+
+ var oldBitmap = Bitmap;
+ var oldBit = DirtyBit;
+
+ bitmap.Set(bit, Dirty);
+
+ Bitmap = bitmap;
+ DirtyBit = bit;
+
+ Dirty |= oldBitmap.IsSet(oldBit);
+ }
+
+ /// <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 _volatile || Dirty;
+ }
+
+ /// <summary>
+ /// Signal that a memory action occurred within this handle's virtual regions.
+ /// </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>
+ /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
+ internal void Signal(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
+ {
+ // If this handle was already unmapped (even if just partially),
+ // then we have nothing to do until it is mapped again.
+ // The pre-action should be still consumed to avoid flushing on remap.
+ if (Unmapped)
+ {
+ Interlocked.Exchange(ref _preAction, null);
+ return;
+ }
+
+ if (_preAction != null)
+ {
+ // Limit the range to within this handle.
+ ulong maxAddress = Math.Max(address, RealAddress);
+ ulong minEndAddress = Math.Min(address + size, RealAddress + RealSize);
+
+ // Copy the handles list in case it changes when we're out of the lock.
+ if (handleIterable is List<RegionHandle>)
+ {
+ handleIterable = handleIterable.ToArray();
+ }
+
+ // Temporarily release the tracking lock while we're running the action.
+ Monitor.Exit(_tracking.TrackingLock);
+
+ try
+ {
+ lock (_preActionLock)
+ {
+ _preAction?.Invoke(maxAddress, minEndAddress - maxAddress);
+
+ // The action is removed after it returns, to ensure that the null check above succeeds when
+ // it's still in progress rather than continuing and possibly missing a required data flush.
+ Interlocked.Exchange(ref _preAction, null);
+ }
+ }
+ finally
+ {
+ Monitor.Enter(_tracking.TrackingLock);
+ }
+ }
+
+ if (write)
+ {
+ bool oldDirty = Dirty;
+ Dirty = true;
+ if (!oldDirty)
+ {
+ _onDirty?.Invoke();
+ }
+ Parent?.SignalWrite();
+ }
+ }
+
+ /// <summary>
+ /// Signal that a precise memory action occurred within this handle's virtual regions.
+ /// If there is no precise action, or the action returns false, the normal signal handler will be called.
+ /// </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>
+ /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
+ /// <returns>True if a precise action was performed and returned true, false otherwise</returns>
+ internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
+ {
+ if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write))
+ {
+ return true;
+ }
+
+ Signal(address, size, write, ref handleIterable);
+
+ return false;
+ }
+
+ /// <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>
+ /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
+ /// <param name="consecutiveCheck">True if this reprotect is the result of consecutive dirty checks</param>
+ public void Reprotect(bool asDirty, bool consecutiveCheck = false)
+ {
+ if (_volatile) return;
+
+ Dirty = asDirty;
+
+ bool protectionChanged = false;
+
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ 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 (consecutiveCheck || (_checkCount > 0 && _checkCount < CheckCountForInfrequent))
+ {
+ if (++_volatileCount >= VolatileThreshold && _preAction == null)
+ {
+ _volatile = true;
+ return;
+ }
+ }
+ else
+ {
+ _volatileCount = 0;
+ }
+
+ _checkCount = 0;
+ }
+ }
+
+ /// <summary>
+ /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
+ /// </summary>
+ /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
+ public void Reprotect(bool asDirty = false)
+ {
+ Reprotect(asDirty, false);
+ }
+
+ /// <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)
+ {
+ ClearVolatile();
+
+ lock (_preActionLock)
+ {
+ RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action);
+
+ if (lastAction == null && action != lastAction)
+ {
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ region.UpdateProtection();
+ }
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Register an action to perform when a precise access occurs (one with exact address and size).
+ /// If the action returns true, read/write tracking are skipped.
+ /// </summary>
+ /// <param name="action">Action to call on read or write</param>
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ _preciseAction = action;
+ }
+
+ /// <summary>
+ /// Register an action to perform when the region is written to.
+ /// This action will not be removed when it is called - it is called each time the dirty flag is set.
+ /// </summary>
+ /// <param name="action">Action to call on dirty</param>
+ public void RegisterDirtyEvent(Action action)
+ {
+ _onDirty += action;
+ }
+
+ /// <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>
+ /// Signal that this handle has been mapped or unmapped.
+ /// </summary>
+ /// <param name="mapped">True if the handle has been mapped, false if unmapped</param>
+ internal void SignalMappingChanged(bool mapped)
+ {
+ if (Unmapped == mapped)
+ {
+ Unmapped = !mapped;
+
+ if (Unmapped)
+ {
+ ClearVolatile();
+ Dirty = false;
+ }
+ }
+ }
+
+ /// <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 regions.
+ /// </summary>
+ public void Dispose()
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
+ _disposed = true;
+
+ lock (_tracking.TrackingLock)
+ {
+ foreach (VirtualRegion region in _regions)
+ {
+ region.RemoveHandle(this);
+ }
+ }
+ }
+ }
+}