MultiRange.cs 11 KB

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