RegionHandle.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection.Metadata;
  5. using System.Threading;
  6. namespace Ryujinx.Memory.Tracking
  7. {
  8. /// <summary>
  9. /// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
  10. /// and an action can be performed when the region is read to or written from.
  11. /// </summary>
  12. public class RegionHandle : IRegionHandle
  13. {
  14. /// <summary>
  15. /// If more than this number of checks have been performed on a dirty flag since its last reprotect,
  16. /// then it is dirtied infrequently.
  17. /// </summary>
  18. private const int CheckCountForInfrequent = 3;
  19. /// <summary>
  20. /// Number of frequent dirty/consume in a row to make this handle volatile.
  21. /// </summary>
  22. private const int VolatileThreshold = 5;
  23. public bool Dirty
  24. {
  25. get
  26. {
  27. return Bitmap.IsSet(DirtyBit);
  28. }
  29. protected set
  30. {
  31. Bitmap.Set(DirtyBit, value);
  32. }
  33. }
  34. internal int SequenceNumber { get; set; }
  35. internal int Id { get; }
  36. public bool Unmapped { get; private set; }
  37. public ulong Address { get; }
  38. public ulong Size { get; }
  39. public ulong EndAddress { get; }
  40. public ulong RealAddress { get; }
  41. public ulong RealSize { get; }
  42. public ulong RealEndAddress { get; }
  43. internal IMultiRegionHandle Parent { get; set; }
  44. private event Action _onDirty;
  45. private object _preActionLock = new object();
  46. private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
  47. private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write.
  48. private readonly List<VirtualRegion> _regions;
  49. private readonly MemoryTracking _tracking;
  50. private bool _disposed;
  51. private int _checkCount = 0;
  52. private int _volatileCount = 0;
  53. private bool _volatile;
  54. internal MemoryPermission RequiredPermission
  55. {
  56. get
  57. {
  58. // If this is unmapped, allow reprotecting as RW as it can't be dirtied.
  59. // This is required for the partial unmap cases where part of the data are still being accessed.
  60. if (Unmapped)
  61. {
  62. return MemoryPermission.ReadAndWrite;
  63. }
  64. if (_preAction != null)
  65. {
  66. return MemoryPermission.None;
  67. }
  68. return Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read;
  69. }
  70. }
  71. internal RegionSignal PreAction => _preAction;
  72. internal ConcurrentBitmap Bitmap;
  73. internal int DirtyBit;
  74. /// <summary>
  75. /// Create a new bitmap backed region handle. The handle is registered with the given tracking object,
  76. /// and will be notified of any changes to the specified region.
  77. /// </summary>
  78. /// <param name="tracking">Tracking object for the target memory block</param>
  79. /// <param name="address">Virtual address of the region to track</param>
  80. /// <param name="size">Size of the region to track</param>
  81. /// <param name="realAddress">The real, unaligned address of the handle</param>
  82. /// <param name="realSize">The real, unaligned size of the handle</param>
  83. /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
  84. /// <param name="bit">The bit index representing the dirty flag for this handle</param>
  85. /// <param name="id">Handle ID</param>
  86. /// <param name="mapped">True if the region handle starts mapped</param>
  87. internal RegionHandle(
  88. MemoryTracking tracking,
  89. ulong address,
  90. ulong size,
  91. ulong realAddress,
  92. ulong realSize,
  93. ConcurrentBitmap bitmap,
  94. int bit,
  95. int id,
  96. bool mapped = true)
  97. {
  98. Bitmap = bitmap;
  99. DirtyBit = bit;
  100. Dirty = mapped;
  101. Id = id;
  102. Unmapped = !mapped;
  103. Address = address;
  104. Size = size;
  105. EndAddress = address + size;
  106. RealAddress = realAddress;
  107. RealSize = realSize;
  108. RealEndAddress = realAddress + realSize;
  109. _tracking = tracking;
  110. _regions = tracking.GetVirtualRegionsForHandle(address, size);
  111. foreach (var region in _regions)
  112. {
  113. region.Handles.Add(this);
  114. }
  115. }
  116. /// <summary>
  117. /// Create a new region handle. The handle is registered with the given tracking object,
  118. /// and will be notified of any changes to the specified region.
  119. /// </summary>
  120. /// <param name="tracking">Tracking object for the target memory block</param>
  121. /// <param name="address">Virtual address of the region to track</param>
  122. /// <param name="size">Size of the region to track</param>
  123. /// <param name="realAddress">The real, unaligned address of the handle</param>
  124. /// <param name="realSize">The real, unaligned size of the handle</param>
  125. /// <param name="id">Handle ID</param>
  126. /// <param name="mapped">True if the region handle starts mapped</param>
  127. internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true)
  128. {
  129. Bitmap = new ConcurrentBitmap(1, mapped);
  130. Id = id;
  131. Unmapped = !mapped;
  132. Address = address;
  133. Size = size;
  134. EndAddress = address + size;
  135. RealAddress = realAddress;
  136. RealSize = realSize;
  137. RealEndAddress = realAddress + realSize;
  138. _tracking = tracking;
  139. _regions = tracking.GetVirtualRegionsForHandle(address, size);
  140. foreach (var region in _regions)
  141. {
  142. region.Handles.Add(this);
  143. }
  144. }
  145. /// <summary>
  146. /// Replace the bitmap and bit index used to track dirty state.
  147. /// </summary>
  148. /// <remarks>
  149. /// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
  150. /// </remarks>
  151. /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
  152. /// <param name="bit">The bit index representing the dirty flag for this handle</param>
  153. internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
  154. {
  155. // Assumes the tracking lock is held, so nothing else can signal right now.
  156. var oldBitmap = Bitmap;
  157. var oldBit = DirtyBit;
  158. bitmap.Set(bit, Dirty);
  159. Bitmap = bitmap;
  160. DirtyBit = bit;
  161. Dirty |= oldBitmap.IsSet(oldBit);
  162. }
  163. /// <summary>
  164. /// Clear the volatile state of this handle.
  165. /// </summary>
  166. private void ClearVolatile()
  167. {
  168. _volatileCount = 0;
  169. _volatile = false;
  170. }
  171. /// <summary>
  172. /// Check if this handle is dirty, or if it is volatile. (changes very often)
  173. /// </summary>
  174. /// <returns>True if the handle is dirty or volatile, false otherwise</returns>
  175. public bool DirtyOrVolatile()
  176. {
  177. _checkCount++;
  178. return _volatile || Dirty;
  179. }
  180. /// <summary>
  181. /// Signal that a memory action occurred within this handle's virtual regions.
  182. /// </summary>
  183. /// <param name="address">Address accessed</param>
  184. /// <param name="size">Size of the region affected in bytes</param>
  185. /// <param name="write">Whether the region was written to or read</param>
  186. /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
  187. internal void Signal(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
  188. {
  189. // If this handle was already unmapped (even if just partially),
  190. // then we have nothing to do until it is mapped again.
  191. // The pre-action should be still consumed to avoid flushing on remap.
  192. if (Unmapped)
  193. {
  194. Interlocked.Exchange(ref _preAction, null);
  195. return;
  196. }
  197. if (_preAction != null)
  198. {
  199. // Limit the range to within this handle.
  200. ulong maxAddress = Math.Max(address, RealAddress);
  201. ulong minEndAddress = Math.Min(address + size, RealAddress + RealSize);
  202. // Copy the handles list in case it changes when we're out of the lock.
  203. if (handleIterable is List<RegionHandle>)
  204. {
  205. handleIterable = handleIterable.ToArray();
  206. }
  207. // Temporarily release the tracking lock while we're running the action.
  208. Monitor.Exit(_tracking.TrackingLock);
  209. try
  210. {
  211. lock (_preActionLock)
  212. {
  213. _preAction?.Invoke(maxAddress, minEndAddress - maxAddress);
  214. // The action is removed after it returns, to ensure that the null check above succeeds when
  215. // it's still in progress rather than continuing and possibly missing a required data flush.
  216. Interlocked.Exchange(ref _preAction, null);
  217. }
  218. }
  219. finally
  220. {
  221. Monitor.Enter(_tracking.TrackingLock);
  222. }
  223. }
  224. if (write)
  225. {
  226. bool oldDirty = Dirty;
  227. Dirty = true;
  228. if (!oldDirty)
  229. {
  230. _onDirty?.Invoke();
  231. }
  232. Parent?.SignalWrite();
  233. }
  234. }
  235. /// <summary>
  236. /// Signal that a precise memory action occurred within this handle's virtual regions.
  237. /// If there is no precise action, or the action returns false, the normal signal handler will be called.
  238. /// </summary>
  239. /// <param name="address">Address accessed</param>
  240. /// <param name="size">Size of the region affected in bytes</param>
  241. /// <param name="write">Whether the region was written to or read</param>
  242. /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
  243. /// <returns>True if a precise action was performed and returned true, false otherwise</returns>
  244. internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
  245. {
  246. if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write))
  247. {
  248. return true;
  249. }
  250. Signal(address, size, write, ref handleIterable);
  251. return false;
  252. }
  253. /// <summary>
  254. /// Force this handle to be dirty, without reprotecting.
  255. /// </summary>
  256. public void ForceDirty()
  257. {
  258. Dirty = true;
  259. }
  260. /// <summary>
  261. /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
  262. /// </summary>
  263. /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
  264. /// <param name="consecutiveCheck">True if this reprotect is the result of consecutive dirty checks</param>
  265. public void Reprotect(bool asDirty, bool consecutiveCheck = false)
  266. {
  267. if (_volatile) return;
  268. Dirty = asDirty;
  269. bool protectionChanged = false;
  270. lock (_tracking.TrackingLock)
  271. {
  272. foreach (VirtualRegion region in _regions)
  273. {
  274. protectionChanged |= region.UpdateProtection();
  275. }
  276. }
  277. if (!protectionChanged)
  278. {
  279. // Counteract the check count being incremented when this handle was forced dirty.
  280. // It doesn't count for protected write tracking.
  281. _checkCount--;
  282. }
  283. else if (!asDirty)
  284. {
  285. if (consecutiveCheck || (_checkCount > 0 && _checkCount < CheckCountForInfrequent))
  286. {
  287. if (++_volatileCount >= VolatileThreshold && _preAction == null)
  288. {
  289. _volatile = true;
  290. return;
  291. }
  292. }
  293. else
  294. {
  295. _volatileCount = 0;
  296. }
  297. _checkCount = 0;
  298. }
  299. }
  300. /// <summary>
  301. /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
  302. /// </summary>
  303. /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param>
  304. public void Reprotect(bool asDirty = false)
  305. {
  306. Reprotect(asDirty, false);
  307. }
  308. /// <summary>
  309. /// Register an action to perform when the tracked region is read or written.
  310. /// The action is automatically removed after it runs.
  311. /// </summary>
  312. /// <param name="action">Action to call on read or write</param>
  313. public void RegisterAction(RegionSignal action)
  314. {
  315. ClearVolatile();
  316. lock (_preActionLock)
  317. {
  318. RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action);
  319. if (lastAction == null && action != lastAction)
  320. {
  321. lock (_tracking.TrackingLock)
  322. {
  323. foreach (VirtualRegion region in _regions)
  324. {
  325. region.UpdateProtection();
  326. }
  327. }
  328. }
  329. }
  330. }
  331. /// <summary>
  332. /// Register an action to perform when a precise access occurs (one with exact address and size).
  333. /// If the action returns true, read/write tracking are skipped.
  334. /// </summary>
  335. /// <param name="action">Action to call on read or write</param>
  336. public void RegisterPreciseAction(PreciseRegionSignal action)
  337. {
  338. _preciseAction = action;
  339. }
  340. /// <summary>
  341. /// Register an action to perform when the region is written to.
  342. /// This action will not be removed when it is called - it is called each time the dirty flag is set.
  343. /// </summary>
  344. /// <param name="action">Action to call on dirty</param>
  345. public void RegisterDirtyEvent(Action action)
  346. {
  347. _onDirty += action;
  348. }
  349. /// <summary>
  350. /// Add a child virtual region to this handle.
  351. /// </summary>
  352. /// <param name="region">Virtual region to add as a child</param>
  353. internal void AddChild(VirtualRegion region)
  354. {
  355. _regions.Add(region);
  356. }
  357. /// <summary>
  358. /// Signal that this handle has been mapped or unmapped.
  359. /// </summary>
  360. /// <param name="mapped">True if the handle has been mapped, false if unmapped</param>
  361. internal void SignalMappingChanged(bool mapped)
  362. {
  363. if (Unmapped == mapped)
  364. {
  365. Unmapped = !mapped;
  366. if (Unmapped)
  367. {
  368. ClearVolatile();
  369. Dirty = false;
  370. }
  371. }
  372. }
  373. /// <summary>
  374. /// Check if this region overlaps with another.
  375. /// </summary>
  376. /// <param name="address">Base address</param>
  377. /// <param name="size">Size of the region</param>
  378. /// <returns>True if overlapping, false otherwise</returns>
  379. public bool OverlapsWith(ulong address, ulong size)
  380. {
  381. return Address < address + size && address < EndAddress;
  382. }
  383. /// <summary>
  384. /// Determines if this handle's memory range matches another exactly.
  385. /// </summary>
  386. /// <param name="other">The other handle</param>
  387. /// <returns>True on a match, false otherwise</returns>
  388. public bool RangeEquals(RegionHandle other)
  389. {
  390. return RealAddress == other.RealAddress && RealSize == other.RealSize;
  391. }
  392. /// <summary>
  393. /// Dispose the handle. Within the tracking lock, this removes references from virtual regions.
  394. /// </summary>
  395. public void Dispose()
  396. {
  397. ObjectDisposedException.ThrowIf(_disposed, this);
  398. _disposed = true;
  399. lock (_tracking.TrackingLock)
  400. {
  401. foreach (VirtualRegion region in _regions)
  402. {
  403. region.RemoveHandle(this);
  404. }
  405. }
  406. }
  407. }
  408. }