MultiRegionHandle.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Numerics; 5 using System.Runtime.CompilerServices; 6 using System.Threading; 7 8 namespace Ryujinx.Memory.Tracking 9 { 10 /// <summary> 11 /// A region handle that tracks a large region using many smaller handles, to provide 12 /// granular tracking that can be used to track partial updates. Backed by a bitmap 13 /// to improve performance when scanning large regions. 14 /// </summary> 15 public class MultiRegionHandle : IMultiRegionHandle 16 { 17 /// <summary> 18 /// A list of region handles for each granularity sized chunk of the whole region. 19 /// </summary> 20 private readonly RegionHandle[] _handles; 21 private readonly ulong Address; 22 private readonly ulong Granularity; 23 private readonly ulong Size; 24 25 private readonly ConcurrentBitmap _dirtyBitmap; 26 27 private int _sequenceNumber; 28 private readonly BitMap _sequenceNumberBitmap; 29 private readonly BitMap _dirtyCheckedBitmap; 30 private int _uncheckedHandles; 31 32 public bool Dirty { get; private set; } = true; 33 34 internal MultiRegionHandle( 35 MemoryTracking tracking, 36 ulong address, 37 ulong size, 38 IEnumerable<IRegionHandle> handles, 39 ulong granularity, 40 int id, 41 RegionFlags flags) 42 { 43 _handles = new RegionHandle[(size + granularity - 1) / granularity]; 44 Granularity = granularity; 45 46 _dirtyBitmap = new ConcurrentBitmap(_handles.Length, true); 47 _sequenceNumberBitmap = new BitMap(_handles.Length); 48 _dirtyCheckedBitmap = new BitMap(_handles.Length); 49 50 int i = 0; 51 52 if (handles != null) 53 { 54 // Inherit from the handles we were given. Any gaps must be filled with new handles, 55 // and old handles larger than our granularity must copy their state onto new granular handles and dispose. 56 // It is assumed that the provided handles do not overlap, in order, are on page boundaries, 57 // and don't extend past the requested range. 58 59 foreach (RegionHandle handle in handles.Cast<RegionHandle>()) 60 { 61 int startIndex = (int)((handle.RealAddress - address) / granularity); 62 63 // Fill any gap left before this handle. 64 while (i < startIndex) 65 { 66 RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); 67 fillHandle.Parent = this; 68 _handles[i++] = fillHandle; 69 } 70 71 lock (tracking.TrackingLock) 72 { 73 if (handle is RegionHandle bitHandle && handle.Size == granularity) 74 { 75 handle.Parent = this; 76 77 bitHandle.ReplaceBitmap(_dirtyBitmap, i); 78 79 _handles[i++] = bitHandle; 80 } 81 else 82 { 83 int endIndex = (int)((handle.RealEndAddress - address) / granularity); 84 85 while (i < endIndex) 86 { 87 RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); 88 splitHandle.Parent = this; 89 90 splitHandle.Reprotect(handle.Dirty); 91 92 RegionSignal signal = handle.PreAction; 93 if (signal != null) 94 { 95 splitHandle.RegisterAction(signal); 96 } 97 98 _handles[i++] = splitHandle; 99 } 100 101 handle.Dispose(); 102 } 103 } 104 } 105 } 106 107 // Fill any remaining space with new handles. 108 while (i < _handles.Length) 109 { 110 RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); 111 handle.Parent = this; 112 _handles[i++] = handle; 113 } 114 115 _uncheckedHandles = _handles.Length; 116 117 Address = address; 118 Size = size; 119 } 120 121 public void SignalWrite() 122 { 123 Dirty = true; 124 } 125 126 public IEnumerable<RegionHandle> GetHandles() 127 { 128 return _handles; 129 } 130 131 public void ForceDirty(ulong address, ulong size) 132 { 133 Dirty = true; 134 135 int startHandle = (int)((address - Address) / Granularity); 136 int lastHandle = (int)((address + (size - 1) - Address) / Granularity); 137 138 for (int i = startHandle; i <= lastHandle; i++) 139 { 140 if (_sequenceNumberBitmap.Clear(i)) 141 { 142 _uncheckedHandles++; 143 } 144 145 _handles[i].ForceDirty(); 146 } 147 } 148 149 public void QueryModified(Action<ulong, ulong> modifiedAction) 150 { 151 if (!Dirty) 152 { 153 return; 154 } 155 156 Dirty = false; 157 158 QueryModified(Address, Size, modifiedAction); 159 } 160 161 [MethodImpl(MethodImplOptions.AggressiveInlining)] 162 private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction) 163 { 164 while (dirtyBits != 0) 165 { 166 int bit = BitOperations.TrailingZeroCount(dirtyBits); 167 168 dirtyBits &= ~(1L << bit); 169 170 int handleIndex = baseBit + bit; 171 172 RegionHandle handle = _handles[handleIndex]; 173 174 if (handleIndex != prevHandle + 1) 175 { 176 // Submit handles scanned until the gap as dirty 177 if (rgSize != 0) 178 { 179 modifiedAction(rgStart, rgSize); 180 rgSize = 0; 181 } 182 183 rgStart = handle.RealAddress; 184 } 185 186 if (handle.Dirty) 187 { 188 rgSize += handle.RealSize; 189 handle.Reprotect(); 190 } 191 192 prevHandle = handleIndex; 193 } 194 195 baseBit += ConcurrentBitmap.IntSize; 196 } 197 198 public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) 199 { 200 int startHandle = (int)((address - Address) / Granularity); 201 int lastHandle = (int)((address + (size - 1) - Address) / Granularity); 202 203 ulong rgStart = Address + (ulong)startHandle * Granularity; 204 205 if (startHandle == lastHandle) 206 { 207 RegionHandle handle = _handles[startHandle]; 208 209 if (handle.Dirty) 210 { 211 handle.Reprotect(); 212 modifiedAction(rgStart, handle.RealSize); 213 } 214 215 return; 216 } 217 218 ulong rgSize = 0; 219 220 long[] masks = _dirtyBitmap.Masks; 221 222 int startIndex = startHandle >> ConcurrentBitmap.IntShift; 223 int startBit = startHandle & ConcurrentBitmap.IntMask; 224 long startMask = -1L << startBit; 225 226 int endIndex = lastHandle >> ConcurrentBitmap.IntShift; 227 int endBit = lastHandle & ConcurrentBitmap.IntMask; 228 long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit)); 229 230 long startValue = Volatile.Read(ref masks[startIndex]); 231 232 int baseBit = startIndex << ConcurrentBitmap.IntShift; 233 int prevHandle = startHandle - 1; 234 235 if (startIndex == endIndex) 236 { 237 ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 238 } 239 else 240 { 241 ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 242 243 for (int i = startIndex + 1; i < endIndex; i++) 244 { 245 ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 246 } 247 248 long endValue = Volatile.Read(ref masks[endIndex]); 249 250 ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 251 } 252 253 if (rgSize != 0) 254 { 255 modifiedAction(rgStart, rgSize); 256 } 257 } 258 259 [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, long[] checkMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action<ulong, ulong> modifiedAction) 261 { 262 long seqMask = mask & ~seqMasks[index]; 263 long checkMask = (~dirtyBits) & seqMask; 264 dirtyBits &= seqMask; 265 266 while (dirtyBits != 0) 267 { 268 int bit = BitOperations.TrailingZeroCount(dirtyBits); 269 long bitValue = 1L << bit; 270 271 dirtyBits &= ~bitValue; 272 273 int handleIndex = baseBit + bit; 274 275 RegionHandle handle = _handles[handleIndex]; 276 277 if (handleIndex != prevHandle + 1) 278 { 279 // Submit handles scanned until the gap as dirty 280 if (rgSize != 0) 281 { 282 modifiedAction(rgStart, rgSize); 283 rgSize = 0; 284 } 285 rgStart = handle.RealAddress; 286 } 287 288 rgSize += handle.RealSize; 289 handle.Reprotect(false, (checkMasks[index] & bitValue) == 0); 290 291 checkMasks[index] &= ~bitValue; 292 293 prevHandle = handleIndex; 294 } 295 296 checkMasks[index] |= checkMask; 297 seqMasks[index] |= mask; 298 _uncheckedHandles -= BitOperations.PopCount((ulong)seqMask); 299 300 baseBit += ConcurrentBitmap.IntSize; 301 } 302 303 public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) 304 { 305 int startHandle = (int)((address - Address) / Granularity); 306 int lastHandle = (int)((address + (size - 1) - Address) / Granularity); 307 308 ulong rgStart = Address + (ulong)startHandle * Granularity; 309 310 if (sequenceNumber != _sequenceNumber) 311 { 312 if (_uncheckedHandles != _handles.Length) 313 { 314 _sequenceNumberBitmap.Clear(); 315 _uncheckedHandles = _handles.Length; 316 } 317 318 _sequenceNumber = sequenceNumber; 319 } 320 321 if (startHandle == lastHandle) 322 { 323 var handle = _handles[startHandle]; 324 if (_sequenceNumberBitmap.Set(startHandle)) 325 { 326 _uncheckedHandles--; 327 328 if (handle.DirtyOrVolatile()) 329 { 330 handle.Reprotect(); 331 332 modifiedAction(rgStart, handle.RealSize); 333 } 334 } 335 336 return; 337 } 338 339 if (_uncheckedHandles == 0) 340 { 341 return; 342 } 343 344 ulong rgSize = 0; 345 346 long[] seqMasks = _sequenceNumberBitmap.Masks; 347 long[] checkedMasks = _dirtyCheckedBitmap.Masks; 348 long[] masks = _dirtyBitmap.Masks; 349 350 int startIndex = startHandle >> ConcurrentBitmap.IntShift; 351 int startBit = startHandle & ConcurrentBitmap.IntMask; 352 long startMask = -1L << startBit; 353 354 int endIndex = lastHandle >> ConcurrentBitmap.IntShift; 355 int endBit = lastHandle & ConcurrentBitmap.IntMask; 356 long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit)); 357 358 long startValue = Volatile.Read(ref masks[startIndex]); 359 360 int baseBit = startIndex << ConcurrentBitmap.IntShift; 361 int prevHandle = startHandle - 1; 362 363 if (startIndex == endIndex) 364 { 365 ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 366 } 367 else 368 { 369 ParseDirtyBits(startValue, startMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 370 371 for (int i = startIndex + 1; i < endIndex; i++) 372 { 373 ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 374 } 375 376 long endValue = Volatile.Read(ref masks[endIndex]); 377 378 ParseDirtyBits(endValue, endMask, endIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); 379 } 380 381 if (rgSize != 0) 382 { 383 modifiedAction(rgStart, rgSize); 384 } 385 } 386 387 public void RegisterAction(ulong address, ulong size, RegionSignal action) 388 { 389 int startHandle = (int)((address - Address) / Granularity); 390 int lastHandle = (int)((address + (size - 1) - Address) / Granularity); 391 392 for (int i = startHandle; i <= lastHandle; i++) 393 { 394 _handles[i].RegisterAction(action); 395 } 396 } 397 398 public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action) 399 { 400 int startHandle = (int)((address - Address) / Granularity); 401 int lastHandle = (int)((address + (size - 1) - Address) / Granularity); 402 403 for (int i = startHandle; i <= lastHandle; i++) 404 { 405 _handles[i].RegisterPreciseAction(action); 406 } 407 } 408 409 public void Dispose() 410 { 411 GC.SuppressFinalize(this); 412 413 foreach (var handle in _handles) 414 { 415 handle.Dispose(); 416 } 417 } 418 } 419 }