/ src / Ryujinx.Memory / Range / MultiRange.cs
MultiRange.cs
  1  using System;
  2  using System.Collections.Generic;
  3  
  4  namespace Ryujinx.Memory.Range
  5  {
  6      /// <summary>
  7      /// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
  8      /// </summary>
  9      public readonly struct MultiRange : IEquatable<MultiRange>
 10      {
 11          private const ulong InvalidAddress = ulong.MaxValue;
 12  
 13          private readonly MemoryRange _singleRange;
 14          private readonly MemoryRange[] _ranges;
 15  
 16          private bool HasSingleRange => _ranges == null;
 17  
 18          /// <summary>
 19          /// Indicates that the range is fully unmapped.
 20          /// </summary>
 21          public bool IsUnmapped => HasSingleRange && _singleRange.Address == InvalidAddress;
 22  
 23          /// <summary>
 24          /// Total of physical sub-ranges on the virtual memory region.
 25          /// </summary>
 26          public int Count => HasSingleRange ? 1 : _ranges.Length;
 27  
 28          /// <summary>
 29          /// Creates a new multi-range with a single physical region.
 30          /// </summary>
 31          /// <param name="address">Start address of the region</param>
 32          /// <param name="size">Size of the region in bytes</param>
 33          public MultiRange(ulong address, ulong size)
 34          {
 35              _singleRange = new MemoryRange(address, size);
 36              _ranges = null;
 37          }
 38  
 39          /// <summary>
 40          /// Creates a new multi-range with multiple physical regions.
 41          /// </summary>
 42          /// <param name="ranges">Array of physical regions</param>
 43          /// <exception cref="ArgumentNullException"><paramref name="ranges"/> is null</exception>
 44          public MultiRange(MemoryRange[] ranges)
 45          {
 46              ArgumentNullException.ThrowIfNull(ranges);
 47  
 48              if (ranges.Length == 1)
 49              {
 50                  _singleRange = ranges[0];
 51                  _ranges = null;
 52              }
 53              else
 54              {
 55                  _singleRange = MemoryRange.Empty;
 56                  _ranges = ranges;
 57              }
 58          }
 59  
 60          /// <summary>
 61          /// Gets a slice of the multi-range.
 62          /// </summary>
 63          /// <param name="offset">Offset of the slice into the multi-range in bytes</param>
 64          /// <param name="size">Size of the slice in bytes</param>
 65          /// <returns>A new multi-range representing the given slice of this one</returns>
 66          public MultiRange Slice(ulong offset, ulong size)
 67          {
 68              if (HasSingleRange)
 69              {
 70                  ArgumentOutOfRangeException.ThrowIfGreaterThan(size, _singleRange.Size - offset);
 71  
 72                  return new MultiRange(_singleRange.Address + offset, size);
 73              }
 74              else
 75              {
 76                  var ranges = new List<MemoryRange>();
 77  
 78                  foreach (MemoryRange range in _ranges)
 79                  {
 80                      if ((long)offset <= 0)
 81                      {
 82                          ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size)));
 83                          size -= range.Size;
 84                      }
 85                      else if (offset < range.Size)
 86                      {
 87                          ulong sliceSize = Math.Min(size, range.Size - offset);
 88  
 89                          if (range.Address == InvalidAddress)
 90                          {
 91                              ranges.Add(new MemoryRange(range.Address, sliceSize));
 92                          }
 93                          else
 94                          {
 95                              ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
 96                          }
 97  
 98                          size -= sliceSize;
 99                      }
100  
101                      if ((long)size <= 0)
102                      {
103                          break;
104                      }
105  
106                      offset -= range.Size;
107                  }
108  
109                  return ranges.Count == 1 ? new MultiRange(ranges[0].Address, ranges[0].Size) : new MultiRange(ranges.ToArray());
110              }
111          }
112  
113          /// <summary>
114          /// Gets the physical region at the specified index.
115          /// </summary>
116          /// <param name="index">Index of the physical region</param>
117          /// <returns>Region at the index specified</returns>
118          /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid</exception>
119          public MemoryRange GetSubRange(int index)
120          {
121              if (HasSingleRange)
122              {
123                  ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
124  
125                  return _singleRange;
126              }
127              else
128              {
129                  if ((uint)index >= _ranges.Length)
130                  {
131                      throw new ArgumentOutOfRangeException(nameof(index));
132                  }
133  
134                  return _ranges[index];
135              }
136          }
137  
138          /// <summary>
139          /// Gets the physical region at the specified index, without explicit bounds checking.
140          /// </summary>
141          /// <param name="index">Index of the physical region</param>
142          /// <returns>Region at the index specified</returns>
143          private MemoryRange GetSubRangeUnchecked(int index)
144          {
145              return HasSingleRange ? _singleRange : _ranges[index];
146          }
147  
148          /// <summary>
149          /// Check if two multi-ranges overlap with each other.
150          /// </summary>
151          /// <param name="other">Other multi-range to check for overlap</param>
152          /// <returns>True if any sub-range overlaps, false otherwise</returns>
153          public bool OverlapsWith(MultiRange other)
154          {
155              if (HasSingleRange && other.HasSingleRange)
156              {
157                  return _singleRange.OverlapsWith(other._singleRange);
158              }
159              else
160              {
161                  for (int i = 0; i < Count; i++)
162                  {
163                      MemoryRange currentRange = GetSubRangeUnchecked(i);
164  
165                      for (int j = 0; j < other.Count; j++)
166                      {
167                          if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j)))
168                          {
169                              return true;
170                          }
171                      }
172                  }
173              }
174  
175              return false;
176          }
177  
178          /// <summary>
179          /// Checks if a given multi-range is fully contained inside another.
180          /// </summary>
181          /// <param name="other">Multi-range to be checked</param>
182          /// <returns>True if all the sub-ranges on <paramref name="other"/> are contained inside the multi-range, with the same order, false otherwise</returns>
183          public bool Contains(MultiRange other)
184          {
185              return FindOffset(other) >= 0;
186          }
187  
188          /// <summary>
189          /// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained
190          /// inside the other multi-range, otherwise returns -1.
191          /// </summary>
192          /// <param name="other">Multi-range that should be fully contained inside this one</param>
193          /// <returns>Offset in bytes if fully contained, otherwise -1</returns>
194          public int FindOffset(MultiRange other)
195          {
196              int thisCount = Count;
197              int otherCount = other.Count;
198  
199              if (thisCount == 1 && otherCount == 1)
200              {
201                  MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
202                  MemoryRange currentFirstRange = GetSubRangeUnchecked(0);
203  
204                  if (otherFirstRange.Address >= currentFirstRange.Address &&
205                      otherFirstRange.EndAddress <= currentFirstRange.EndAddress)
206                  {
207                      return (int)(otherFirstRange.Address - currentFirstRange.Address);
208                  }
209              }
210              else if (thisCount >= otherCount)
211              {
212                  ulong baseOffset = 0;
213  
214                  MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
215                  MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1);
216  
217                  for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++)
218                  {
219                      MemoryRange currentFirstRange = GetSubRangeUnchecked(i);
220                      MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1);
221  
222                      if (otherCount > 1)
223                      {
224                          if (otherFirstRange.Address < currentFirstRange.Address ||
225                              otherFirstRange.EndAddress != currentFirstRange.EndAddress)
226                          {
227                              continue;
228                          }
229  
230                          if (otherLastRange.Address != currentLastRange.Address ||
231                              otherLastRange.EndAddress > currentLastRange.EndAddress)
232                          {
233                              continue;
234                          }
235  
236                          bool fullMatch = true;
237  
238                          for (int j = 1; j < otherCount - 1; j++)
239                          {
240                              if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j)))
241                              {
242                                  fullMatch = false;
243                                  break;
244                              }
245                          }
246  
247                          if (!fullMatch)
248                          {
249                              continue;
250                          }
251                      }
252                      else if (currentFirstRange.Address > otherFirstRange.Address ||
253                               currentFirstRange.EndAddress < otherFirstRange.EndAddress)
254                      {
255                          continue;
256                      }
257  
258                      return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address));
259                  }
260              }
261  
262              return -1;
263          }
264  
265          /// <summary>
266          /// Gets the total size of all sub-ranges in bytes.
267          /// </summary>
268          /// <returns>Total size in bytes</returns>
269          public ulong GetSize()
270          {
271              if (HasSingleRange)
272              {
273                  return _singleRange.Size;
274              }
275  
276              ulong sum = 0;
277  
278              foreach (MemoryRange range in _ranges)
279              {
280                  sum += range.Size;
281              }
282  
283              return sum;
284          }
285  
286          public override bool Equals(object obj)
287          {
288              return obj is MultiRange other && Equals(other);
289          }
290  
291          public bool Equals(MultiRange other)
292          {
293              if (HasSingleRange && other.HasSingleRange)
294              {
295                  return _singleRange.Equals(other._singleRange);
296              }
297  
298              int thisCount = Count;
299              if (thisCount != other.Count)
300              {
301                  return false;
302              }
303  
304              for (int i = 0; i < thisCount; i++)
305              {
306                  if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i)))
307                  {
308                      return false;
309                  }
310              }
311  
312              return true;
313          }
314  
315          public override int GetHashCode()
316          {
317              if (HasSingleRange)
318              {
319                  return _singleRange.GetHashCode();
320              }
321  
322              HashCode hash = new();
323  
324              foreach (MemoryRange range in _ranges)
325              {
326                  hash.Add(range);
327              }
328  
329              return hash.ToHashCode();
330          }
331  
332          /// <summary>
333          /// Returns a string summary of the ranges contained in the MultiRange.
334          /// </summary>
335          /// <returns>A string summary of the ranges contained within</returns>
336          public override string ToString()
337          {
338              return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges);
339          }
340  
341          public static bool operator ==(MultiRange left, MultiRange right)
342          {
343              return left.Equals(right);
344          }
345  
346          public static bool operator !=(MultiRange left, MultiRange right)
347          {
348              return !(left == right);
349          }
350      }
351  }