MemoryManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. using ARMeilleure.Memory;
  2. using Ryujinx.Memory;
  3. using Ryujinx.Memory.Range;
  4. using Ryujinx.Memory.Tracking;
  5. using System;
  6. using System.Buffers;
  7. using System.Collections.Generic;
  8. using System.Runtime.CompilerServices;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. namespace Ryujinx.Cpu.Jit
  12. {
  13. /// <summary>
  14. /// Represents a CPU memory manager.
  15. /// </summary>
  16. public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked
  17. {
  18. private const int PteSize = 8;
  19. private const int PointerTagBit = 62;
  20. private readonly MemoryBlock _backingMemory;
  21. private readonly InvalidAccessHandler _invalidAccessHandler;
  22. /// <inheritdoc/>
  23. public bool UsesPrivateAllocations => false;
  24. /// <summary>
  25. /// Address space width in bits.
  26. /// </summary>
  27. public int AddressSpaceBits { get; }
  28. private readonly MemoryBlock _pageTable;
  29. private readonly ManagedPageFlags _pages;
  30. /// <summary>
  31. /// Page table base pointer.
  32. /// </summary>
  33. public nint PageTablePointer => _pageTable.Pointer;
  34. public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable;
  35. public MemoryTracking Tracking { get; }
  36. public event Action<ulong, ulong> UnmapEvent;
  37. protected override ulong AddressSpaceSize { get; }
  38. /// <summary>
  39. /// Creates a new instance of the memory manager.
  40. /// </summary>
  41. /// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
  42. /// <param name="addressSpaceSize">Size of the address space</param>
  43. /// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
  44. public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
  45. {
  46. _backingMemory = backingMemory;
  47. _invalidAccessHandler = invalidAccessHandler;
  48. ulong asSize = PageSize;
  49. int asBits = PageBits;
  50. while (asSize < addressSpaceSize)
  51. {
  52. asSize <<= 1;
  53. asBits++;
  54. }
  55. AddressSpaceBits = asBits;
  56. AddressSpaceSize = asSize;
  57. _pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
  58. _pages = new ManagedPageFlags(AddressSpaceBits);
  59. Tracking = new MemoryTracking(this, PageSize);
  60. }
  61. /// <inheritdoc/>
  62. public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
  63. {
  64. AssertValidAddressAndSize(va, size);
  65. ulong remainingSize = size;
  66. ulong oVa = va;
  67. while (remainingSize != 0)
  68. {
  69. _pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
  70. va += PageSize;
  71. pa += PageSize;
  72. remainingSize -= PageSize;
  73. }
  74. _pages.AddMapping(oVa, size);
  75. Tracking.Map(oVa, size);
  76. }
  77. /// <inheritdoc/>
  78. public void Unmap(ulong va, ulong size)
  79. {
  80. // If size is 0, there's nothing to unmap, just exit early.
  81. if (size == 0)
  82. {
  83. return;
  84. }
  85. AssertValidAddressAndSize(va, size);
  86. UnmapEvent?.Invoke(va, size);
  87. Tracking.Unmap(va, size);
  88. _pages.RemoveMapping(va, size);
  89. ulong remainingSize = size;
  90. while (remainingSize != 0)
  91. {
  92. _pageTable.Write((va / PageSize) * PteSize, 0UL);
  93. va += PageSize;
  94. remainingSize -= PageSize;
  95. }
  96. }
  97. public override T ReadTracked<T>(ulong va)
  98. {
  99. try
  100. {
  101. return base.ReadTracked<T>(va);
  102. }
  103. catch (InvalidMemoryRegionException)
  104. {
  105. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  106. {
  107. throw;
  108. }
  109. return default;
  110. }
  111. }
  112. /// <inheritdoc/>
  113. public T ReadGuest<T>(ulong va) where T : unmanaged
  114. {
  115. try
  116. {
  117. SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf<T>(), false, true);
  118. return Read<T>(va);
  119. }
  120. catch (InvalidMemoryRegionException)
  121. {
  122. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  123. {
  124. throw;
  125. }
  126. return default;
  127. }
  128. }
  129. /// <inheritdoc/>
  130. public override void Read(ulong va, Span<byte> data)
  131. {
  132. try
  133. {
  134. base.Read(va, data);
  135. }
  136. catch (InvalidMemoryRegionException)
  137. {
  138. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  139. {
  140. throw;
  141. }
  142. }
  143. }
  144. public override void Write(ulong va, ReadOnlySpan<byte> data)
  145. {
  146. try
  147. {
  148. base.Write(va, data);
  149. }
  150. catch (InvalidMemoryRegionException)
  151. {
  152. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  153. {
  154. throw;
  155. }
  156. }
  157. }
  158. /// <inheritdoc/>
  159. public void WriteGuest<T>(ulong va, T value) where T : unmanaged
  160. {
  161. Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
  162. SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
  163. Write(va, data);
  164. }
  165. public override void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
  166. {
  167. try
  168. {
  169. base.WriteUntracked(va, data);
  170. }
  171. catch (InvalidMemoryRegionException)
  172. {
  173. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  174. {
  175. throw;
  176. }
  177. }
  178. }
  179. public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
  180. {
  181. try
  182. {
  183. return base.GetReadOnlySequence(va, size, tracked);
  184. }
  185. catch (InvalidMemoryRegionException)
  186. {
  187. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  188. {
  189. throw;
  190. }
  191. return ReadOnlySequence<byte>.Empty;
  192. }
  193. }
  194. public ref T GetRef<T>(ulong va) where T : unmanaged
  195. {
  196. if (!IsContiguous(va, Unsafe.SizeOf<T>()))
  197. {
  198. ThrowMemoryNotContiguous();
  199. }
  200. SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
  201. return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
  202. }
  203. /// <inheritdoc/>
  204. public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
  205. {
  206. if (size == 0)
  207. {
  208. yield break;
  209. }
  210. IEnumerable<MemoryRange> guestRegions = GetPhysicalRegionsImpl(va, size);
  211. if (guestRegions == null)
  212. {
  213. yield break;
  214. }
  215. foreach (MemoryRange guestRegion in guestRegions)
  216. {
  217. nint pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
  218. yield return new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
  219. }
  220. }
  221. /// <inheritdoc/>
  222. public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
  223. {
  224. if (size == 0)
  225. {
  226. yield break;
  227. }
  228. foreach (MemoryRange physicalRegion in GetPhysicalRegionsImpl(va, size))
  229. {
  230. yield return physicalRegion;
  231. }
  232. }
  233. private IEnumerable<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
  234. {
  235. if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
  236. {
  237. yield break;
  238. }
  239. int pages = GetPagesCount(va, (uint)size, out va);
  240. ulong regionStart = GetPhysicalAddressInternal(va);
  241. ulong regionSize = PageSize;
  242. for (int page = 0; page < pages - 1; page++)
  243. {
  244. if (!ValidateAddress(va + PageSize))
  245. {
  246. yield break;
  247. }
  248. ulong newPa = GetPhysicalAddressInternal(va + PageSize);
  249. if (GetPhysicalAddressInternal(va) + PageSize != newPa)
  250. {
  251. yield return new MemoryRange(regionStart, regionSize);
  252. regionStart = newPa;
  253. regionSize = 0;
  254. }
  255. va += PageSize;
  256. regionSize += PageSize;
  257. }
  258. yield return new MemoryRange(regionStart, regionSize);
  259. }
  260. /// <inheritdoc/>
  261. public bool IsRangeMapped(ulong va, ulong size)
  262. {
  263. if (size == 0UL)
  264. {
  265. return true;
  266. }
  267. if (!ValidateAddressAndSize(va, size))
  268. {
  269. return false;
  270. }
  271. int pages = GetPagesCount(va, (uint)size, out va);
  272. for (int page = 0; page < pages; page++)
  273. {
  274. if (!IsMapped(va))
  275. {
  276. return false;
  277. }
  278. va += PageSize;
  279. }
  280. return true;
  281. }
  282. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  283. public override bool IsMapped(ulong va)
  284. {
  285. if (!ValidateAddress(va))
  286. {
  287. return false;
  288. }
  289. return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
  290. }
  291. private nuint GetPhysicalAddressInternal(ulong va)
  292. {
  293. return (nuint)(PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask));
  294. }
  295. /// <inheritdoc/>
  296. public void Reprotect(ulong va, ulong size, MemoryPermission protection)
  297. {
  298. // TODO
  299. }
  300. /// <inheritdoc/>
  301. public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
  302. {
  303. AssertValidAddressAndSize(va, size);
  304. if (guest)
  305. {
  306. // Protection is inverted on software pages, since the default value is 0.
  307. protection = (~protection) & MemoryPermission.ReadAndWrite;
  308. long tag = protection switch
  309. {
  310. MemoryPermission.None => 0L,
  311. MemoryPermission.Write => 2L << PointerTagBit,
  312. _ => 3L << PointerTagBit,
  313. };
  314. int pages = GetPagesCount(va, (uint)size, out va);
  315. ulong pageStart = va >> PageBits;
  316. long invTagMask = ~(0xffffL << 48);
  317. for (int page = 0; page < pages; page++)
  318. {
  319. ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
  320. long pte;
  321. do
  322. {
  323. pte = Volatile.Read(ref pageRef);
  324. }
  325. while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
  326. pageStart++;
  327. }
  328. }
  329. else
  330. {
  331. _pages.TrackingReprotect(va, size, protection);
  332. }
  333. }
  334. /// <inheritdoc/>
  335. public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
  336. {
  337. return Tracking.BeginTracking(address, size, id, flags);
  338. }
  339. /// <inheritdoc/>
  340. public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
  341. {
  342. return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
  343. }
  344. /// <inheritdoc/>
  345. public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
  346. {
  347. return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
  348. }
  349. private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null)
  350. {
  351. AssertValidAddressAndSize(va, size);
  352. if (precise)
  353. {
  354. Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
  355. return;
  356. }
  357. // If the memory tracking is coming from the guest, use the tag bits in the page table entry.
  358. // Otherwise, use the managed page flags.
  359. if (guest)
  360. {
  361. // We emulate guard pages for software memory access. This makes for an easy transition to
  362. // tracking using host guard pages in future, but also supporting platforms where this is not possible.
  363. // Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
  364. long tag = (write ? 3L : 1L) << PointerTagBit;
  365. int pages = GetPagesCount(va, (uint)size, out _);
  366. ulong pageStart = va >> PageBits;
  367. for (int page = 0; page < pages; page++)
  368. {
  369. ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
  370. long pte = Volatile.Read(ref pageRef);
  371. if ((pte & tag) != 0)
  372. {
  373. Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true);
  374. break;
  375. }
  376. pageStart++;
  377. }
  378. }
  379. else
  380. {
  381. _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
  382. }
  383. }
  384. /// <inheritdoc/>
  385. public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
  386. {
  387. SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId);
  388. }
  389. private ulong PaToPte(ulong pa)
  390. {
  391. return (ulong)_backingMemory.GetPointer(pa, PageSize);
  392. }
  393. private ulong PteToPa(ulong pte)
  394. {
  395. return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
  396. }
  397. /// <summary>
  398. /// Disposes of resources used by the memory manager.
  399. /// </summary>
  400. protected override void Destroy() => _pageTable.Dispose();
  401. protected override Memory<byte> GetPhysicalAddressMemory(nuint pa, int size)
  402. => _backingMemory.GetMemory(pa, size);
  403. protected override Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
  404. => _backingMemory.GetSpan(pa, size);
  405. protected override nuint TranslateVirtualAddressChecked(ulong va)
  406. => GetPhysicalAddressInternal(va);
  407. protected override nuint TranslateVirtualAddressUnchecked(ulong va)
  408. => GetPhysicalAddressInternal(va);
  409. }
  410. }