/ src / Ryujinx.Memory / Tracking / SmartMultiRegionHandle.cs
SmartMultiRegionHandle.cs
  1  using System;
  2  using System.Runtime.CompilerServices;
  3  
  4  namespace Ryujinx.Memory.Tracking
  5  {
  6      /// <summary>
  7      /// A MultiRegionHandle that attempts to segment a region's handles into the regions requested
  8      /// to avoid iterating over granular chunks for canonically large regions.
  9      /// If minimum granularity is to be expected, use MultiRegionHandle.
 10      /// </summary>
 11      public class SmartMultiRegionHandle : IMultiRegionHandle
 12      {
 13          /// <summary>
 14          /// A list of region handles starting at each granularity size increment.
 15          /// </summary>
 16          private readonly RegionHandle[] _handles;
 17          private readonly ulong _address;
 18          private readonly ulong _granularity;
 19          private readonly ulong _size;
 20          private readonly MemoryTracking _tracking;
 21          private readonly int _id;
 22  
 23          public bool Dirty { get; private set; } = true;
 24  
 25          internal SmartMultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity, int id)
 26          {
 27              // For this multi-region handle, the handle list starts empty.
 28              // As regions are queried, they are added to the _handles array at their start index.
 29              // When a region being added overlaps another, the existing region is split.
 30              // A query can therefore scan multiple regions, though with no overlaps they can cover a large area.
 31  
 32              _tracking = tracking;
 33              _handles = new RegionHandle[size / granularity];
 34              _granularity = granularity;
 35  
 36              _address = address;
 37              _size = size;
 38              _id = id;
 39          }
 40  
 41          public void SignalWrite()
 42          {
 43              Dirty = true;
 44          }
 45  
 46          public void ForceDirty(ulong address, ulong size)
 47          {
 48              foreach (var handle in _handles)
 49              {
 50                  if (handle != null && handle.OverlapsWith(address, size))
 51                  {
 52                      handle.ForceDirty();
 53                  }
 54              }
 55          }
 56  
 57          public void RegisterAction(RegionSignal action)
 58          {
 59              foreach (var handle in _handles)
 60              {
 61                  if (handle != null)
 62                  {
 63                      handle?.RegisterAction((address, size) => action(handle.Address, handle.Size));
 64                  }
 65              }
 66          }
 67  
 68          public void RegisterPreciseAction(PreciseRegionSignal action)
 69          {
 70              foreach (var handle in _handles)
 71              {
 72                  if (handle != null)
 73                  {
 74                      handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
 75                  }
 76              }
 77          }
 78  
 79          public void QueryModified(Action<ulong, ulong> modifiedAction)
 80          {
 81              if (!Dirty)
 82              {
 83                  return;
 84              }
 85  
 86              Dirty = false;
 87  
 88              QueryModified(_address, _size, modifiedAction);
 89          }
 90  
 91          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 92          private ulong HandlesToBytes(int handles)
 93          {
 94              return (ulong)handles * _granularity;
 95          }
 96  
 97          private void SplitHandle(int handleIndex, int splitIndex)
 98          {
 99              RegionHandle handle = _handles[handleIndex];
100              ulong address = _address + HandlesToBytes(handleIndex);
101              ulong size = HandlesToBytes(splitIndex - handleIndex);
102  
103              // First, the target handle must be removed. Its data can still be used to determine the new handles.
104              RegionSignal signal = handle.PreAction;
105              handle.Dispose();
106  
107              RegionHandle splitLow = _tracking.BeginTracking(address, size, _id);
108              splitLow.Parent = this;
109              if (signal != null)
110              {
111                  splitLow.RegisterAction(signal);
112              }
113              _handles[handleIndex] = splitLow;
114  
115              RegionHandle splitHigh = _tracking.BeginTracking(address + size, handle.Size - size, _id);
116              splitHigh.Parent = this;
117              if (signal != null)
118              {
119                  splitHigh.RegisterAction(signal);
120              }
121              _handles[splitIndex] = splitHigh;
122          }
123  
124          private void CreateHandle(int startHandle, int lastHandle)
125          {
126              ulong startAddress = _address + HandlesToBytes(startHandle);
127  
128              // Scan for the first handle before us. If it's overlapping us, it must be split.
129              for (int i = startHandle - 1; i >= 0; i--)
130              {
131                  RegionHandle handle = _handles[i];
132                  if (handle != null)
133                  {
134                      if (handle.EndAddress > startAddress)
135                      {
136                          SplitHandle(i, startHandle);
137                          return; // The remainer of this handle should be filled in later on.
138                      }
139                      break;
140                  }
141              }
142  
143              // Scan for handles after us. We should create a handle that goes up to this handle's start point, if present.
144              for (int i = startHandle + 1; i <= lastHandle; i++)
145              {
146                  RegionHandle handle = _handles[i];
147                  if (handle != null)
148                  {
149                      // Fill up to the found handle.
150                      handle = _tracking.BeginTracking(startAddress, HandlesToBytes(i - startHandle), _id);
151                      handle.Parent = this;
152                      _handles[startHandle] = handle;
153                      return;
154                  }
155              }
156  
157              // Can fill the whole range.
158              _handles[startHandle] = _tracking.BeginTracking(startAddress, HandlesToBytes(1 + lastHandle - startHandle), _id);
159              _handles[startHandle].Parent = this;
160          }
161  
162          public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction)
163          {
164              int startHandle = (int)((address - _address) / _granularity);
165              int lastHandle = (int)((address + (size - 1) - _address) / _granularity);
166  
167              ulong rgStart = _address + (ulong)startHandle * _granularity;
168              ulong rgSize = 0;
169  
170              ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity;
171  
172              int i = startHandle;
173  
174              while (i <= lastHandle)
175              {
176                  RegionHandle handle = _handles[i];
177                  if (handle == null)
178                  {
179                      // Missing handle. A new handle must be created.
180                      CreateHandle(i, lastHandle);
181                      handle = _handles[i];
182                  }
183  
184                  if (handle.EndAddress > endAddress)
185                  {
186                      // End address of handle is beyond the end of the search. Force a split.
187                      SplitHandle(i, lastHandle + 1);
188                      handle = _handles[i];
189                  }
190  
191                  if (handle.Dirty)
192                  {
193                      rgSize += handle.Size;
194                      handle.Reprotect();
195                  }
196                  else
197                  {
198                      // Submit the region scanned so far as dirty
199                      if (rgSize != 0)
200                      {
201                          modifiedAction(rgStart, rgSize);
202                          rgSize = 0;
203                      }
204                      rgStart = handle.EndAddress;
205                  }
206  
207                  i += (int)(handle.Size / _granularity);
208              }
209  
210              if (rgSize != 0)
211              {
212                  modifiedAction(rgStart, rgSize);
213              }
214          }
215  
216          public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber)
217          {
218              int startHandle = (int)((address - _address) / _granularity);
219              int lastHandle = (int)((address + (size - 1) - _address) / _granularity);
220  
221              ulong rgStart = _address + (ulong)startHandle * _granularity;
222              ulong rgSize = 0;
223  
224              ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity;
225  
226              int i = startHandle;
227  
228              while (i <= lastHandle)
229              {
230                  RegionHandle handle = _handles[i];
231                  if (handle == null)
232                  {
233                      // Missing handle. A new handle must be created.
234                      CreateHandle(i, lastHandle);
235                      handle = _handles[i];
236                  }
237  
238                  if (handle.EndAddress > endAddress)
239                  {
240                      // End address of handle is beyond the end of the search. Force a split.
241                      SplitHandle(i, lastHandle + 1);
242                      handle = _handles[i];
243                  }
244  
245                  if (handle.Dirty && sequenceNumber != handle.SequenceNumber)
246                  {
247                      rgSize += handle.Size;
248                      handle.Reprotect();
249                  }
250                  else
251                  {
252                      // Submit the region scanned so far as dirty
253                      if (rgSize != 0)
254                      {
255                          modifiedAction(rgStart, rgSize);
256                          rgSize = 0;
257                      }
258                      rgStart = handle.EndAddress;
259                  }
260  
261                  handle.SequenceNumber = sequenceNumber;
262  
263                  i += (int)(handle.Size / _granularity);
264              }
265  
266              if (rgSize != 0)
267              {
268                  modifiedAction(rgStart, rgSize);
269              }
270          }
271  
272          public void Dispose()
273          {
274              GC.SuppressFinalize(this);
275  
276              foreach (var handle in _handles)
277              {
278                  handle?.Dispose();
279              }
280          }
281      }
282  }