MultiRange.cs 11 KB

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