MultiRange.cs 11 KB

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