MemoryManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. using ARMeilleure.Memory;
  2. using Ryujinx.Memory;
  3. using Ryujinx.Memory.Range;
  4. using Ryujinx.Memory.Tracking;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  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 : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
  17. {
  18. public const int PageBits = 12;
  19. public const int PageSize = 1 << PageBits;
  20. public const int PageMask = PageSize - 1;
  21. private const int PteSize = 8;
  22. private const int PointerTagBit = 62;
  23. private readonly MemoryBlock _backingMemory;
  24. private readonly InvalidAccessHandler _invalidAccessHandler;
  25. /// <inheritdoc/>
  26. public bool Supports4KBPages => true;
  27. /// <summary>
  28. /// Address space width in bits.
  29. /// </summary>
  30. public int AddressSpaceBits { get; }
  31. private readonly ulong _addressSpaceSize;
  32. private readonly MemoryBlock _pageTable;
  33. /// <summary>
  34. /// Page table base pointer.
  35. /// </summary>
  36. public IntPtr PageTablePointer => _pageTable.Pointer;
  37. public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable;
  38. public MemoryTracking Tracking { get; }
  39. public event Action<ulong, ulong> UnmapEvent;
  40. /// <summary>
  41. /// Creates a new instance of the memory manager.
  42. /// </summary>
  43. /// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
  44. /// <param name="addressSpaceSize">Size of the address space</param>
  45. /// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
  46. public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null)
  47. {
  48. _backingMemory = backingMemory;
  49. _invalidAccessHandler = invalidAccessHandler;
  50. ulong asSize = PageSize;
  51. int asBits = PageBits;
  52. while (asSize < addressSpaceSize)
  53. {
  54. asSize <<= 1;
  55. asBits++;
  56. }
  57. AddressSpaceBits = asBits;
  58. _addressSpaceSize = asSize;
  59. _pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
  60. Tracking = new MemoryTracking(this, PageSize);
  61. }
  62. /// <inheritdoc/>
  63. public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
  64. {
  65. AssertValidAddressAndSize(va, size);
  66. ulong remainingSize = size;
  67. ulong oVa = va;
  68. while (remainingSize != 0)
  69. {
  70. _pageTable.Write((va / PageSize) * PteSize, PaToPte(pa));
  71. va += PageSize;
  72. pa += PageSize;
  73. remainingSize -= PageSize;
  74. }
  75. Tracking.Map(oVa, size);
  76. }
  77. /// <inheritdoc/>
  78. public void MapForeign(ulong va, nuint hostPointer, ulong size)
  79. {
  80. throw new NotSupportedException();
  81. }
  82. /// <inheritdoc/>
  83. public void Unmap(ulong va, ulong size)
  84. {
  85. // If size is 0, there's nothing to unmap, just exit early.
  86. if (size == 0)
  87. {
  88. return;
  89. }
  90. AssertValidAddressAndSize(va, size);
  91. UnmapEvent?.Invoke(va, size);
  92. Tracking.Unmap(va, size);
  93. ulong remainingSize = size;
  94. while (remainingSize != 0)
  95. {
  96. _pageTable.Write((va / PageSize) * PteSize, 0UL);
  97. va += PageSize;
  98. remainingSize -= PageSize;
  99. }
  100. }
  101. /// <inheritdoc/>
  102. public T Read<T>(ulong va) where T : unmanaged
  103. {
  104. return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
  105. }
  106. /// <inheritdoc/>
  107. public T ReadTracked<T>(ulong va) where T : unmanaged
  108. {
  109. try
  110. {
  111. SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
  112. return Read<T>(va);
  113. }
  114. catch (InvalidMemoryRegionException)
  115. {
  116. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  117. {
  118. throw;
  119. }
  120. return default;
  121. }
  122. }
  123. /// <inheritdoc/>
  124. public void Read(ulong va, Span<byte> data)
  125. {
  126. ReadImpl(va, data);
  127. }
  128. /// <inheritdoc/>
  129. public void Write<T>(ulong va, T value) where T : unmanaged
  130. {
  131. Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1)));
  132. }
  133. /// <inheritdoc/>
  134. public void Write(ulong va, ReadOnlySpan<byte> data)
  135. {
  136. if (data.Length == 0)
  137. {
  138. return;
  139. }
  140. SignalMemoryTracking(va, (ulong)data.Length, true);
  141. WriteImpl(va, data);
  142. }
  143. /// <inheritdoc/>
  144. public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
  145. {
  146. if (data.Length == 0)
  147. {
  148. return;
  149. }
  150. WriteImpl(va, data);
  151. }
  152. /// <inheritdoc/>
  153. public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
  154. {
  155. if (data.Length == 0)
  156. {
  157. return false;
  158. }
  159. SignalMemoryTracking(va, (ulong)data.Length, false);
  160. if (IsContiguousAndMapped(va, data.Length))
  161. {
  162. var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
  163. bool changed = !data.SequenceEqual(target);
  164. if (changed)
  165. {
  166. data.CopyTo(target);
  167. }
  168. return changed;
  169. }
  170. else
  171. {
  172. WriteImpl(va, data);
  173. return true;
  174. }
  175. }
  176. /// <summary>
  177. /// Writes data to CPU mapped memory.
  178. /// </summary>
  179. /// <param name="va">Virtual address to write the data into</param>
  180. /// <param name="data">Data to be written</param>
  181. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  182. private void WriteImpl(ulong va, ReadOnlySpan<byte> data)
  183. {
  184. try
  185. {
  186. AssertValidAddressAndSize(va, (ulong)data.Length);
  187. if (IsContiguousAndMapped(va, data.Length))
  188. {
  189. data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
  190. }
  191. else
  192. {
  193. int offset = 0, size;
  194. if ((va & PageMask) != 0)
  195. {
  196. ulong pa = GetPhysicalAddressInternal(va);
  197. size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
  198. data[..size].CopyTo(_backingMemory.GetSpan(pa, size));
  199. offset += size;
  200. }
  201. for (; offset < data.Length; offset += size)
  202. {
  203. ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
  204. size = Math.Min(data.Length - offset, PageSize);
  205. data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
  206. }
  207. }
  208. }
  209. catch (InvalidMemoryRegionException)
  210. {
  211. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  212. {
  213. throw;
  214. }
  215. }
  216. }
  217. /// <inheritdoc/>
  218. public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
  219. {
  220. if (size == 0)
  221. {
  222. return ReadOnlySpan<byte>.Empty;
  223. }
  224. if (tracked)
  225. {
  226. SignalMemoryTracking(va, (ulong)size, false);
  227. }
  228. if (IsContiguousAndMapped(va, size))
  229. {
  230. return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
  231. }
  232. else
  233. {
  234. Span<byte> data = new byte[size];
  235. ReadImpl(va, data);
  236. return data;
  237. }
  238. }
  239. /// <inheritdoc/>
  240. public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
  241. {
  242. if (size == 0)
  243. {
  244. return new WritableRegion(null, va, Memory<byte>.Empty);
  245. }
  246. if (IsContiguousAndMapped(va, size))
  247. {
  248. if (tracked)
  249. {
  250. SignalMemoryTracking(va, (ulong)size, true);
  251. }
  252. return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
  253. }
  254. else
  255. {
  256. Memory<byte> memory = new byte[size];
  257. GetSpan(va, size).CopyTo(memory.Span);
  258. return new WritableRegion(this, va, memory, tracked);
  259. }
  260. }
  261. /// <inheritdoc/>
  262. public ref T GetRef<T>(ulong va) where T : unmanaged
  263. {
  264. if (!IsContiguous(va, Unsafe.SizeOf<T>()))
  265. {
  266. ThrowMemoryNotContiguous();
  267. }
  268. SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
  269. return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
  270. }
  271. /// <summary>
  272. /// Computes the number of pages in a virtual address range.
  273. /// </summary>
  274. /// <param name="va">Virtual address of the range</param>
  275. /// <param name="size">Size of the range</param>
  276. /// <param name="startVa">The virtual address of the beginning of the first page</param>
  277. /// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
  278. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  279. private static int GetPagesCount(ulong va, uint size, out ulong startVa)
  280. {
  281. // WARNING: Always check if ulong does not overflow during the operations.
  282. startVa = va & ~(ulong)PageMask;
  283. ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
  284. return (int)(vaSpan / PageSize);
  285. }
  286. private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
  287. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  288. private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
  289. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  290. private bool IsContiguous(ulong va, int size)
  291. {
  292. if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size))
  293. {
  294. return false;
  295. }
  296. int pages = GetPagesCount(va, (uint)size, out va);
  297. for (int page = 0; page < pages - 1; page++)
  298. {
  299. if (!ValidateAddress(va + PageSize))
  300. {
  301. return false;
  302. }
  303. if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
  304. {
  305. return false;
  306. }
  307. va += PageSize;
  308. }
  309. return true;
  310. }
  311. /// <inheritdoc/>
  312. public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
  313. {
  314. if (size == 0)
  315. {
  316. return Enumerable.Empty<HostMemoryRange>();
  317. }
  318. var guestRegions = GetPhysicalRegionsImpl(va, size);
  319. if (guestRegions == null)
  320. {
  321. return null;
  322. }
  323. var regions = new HostMemoryRange[guestRegions.Count];
  324. for (int i = 0; i < regions.Length; i++)
  325. {
  326. var guestRegion = guestRegions[i];
  327. IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
  328. regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
  329. }
  330. return regions;
  331. }
  332. /// <inheritdoc/>
  333. public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
  334. {
  335. if (size == 0)
  336. {
  337. return Enumerable.Empty<MemoryRange>();
  338. }
  339. return GetPhysicalRegionsImpl(va, size);
  340. }
  341. private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
  342. {
  343. if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
  344. {
  345. return null;
  346. }
  347. int pages = GetPagesCount(va, (uint)size, out va);
  348. var regions = new List<MemoryRange>();
  349. ulong regionStart = GetPhysicalAddressInternal(va);
  350. ulong regionSize = PageSize;
  351. for (int page = 0; page < pages - 1; page++)
  352. {
  353. if (!ValidateAddress(va + PageSize))
  354. {
  355. return null;
  356. }
  357. ulong newPa = GetPhysicalAddressInternal(va + PageSize);
  358. if (GetPhysicalAddressInternal(va) + PageSize != newPa)
  359. {
  360. regions.Add(new MemoryRange(regionStart, regionSize));
  361. regionStart = newPa;
  362. regionSize = 0;
  363. }
  364. va += PageSize;
  365. regionSize += PageSize;
  366. }
  367. regions.Add(new MemoryRange(regionStart, regionSize));
  368. return regions;
  369. }
  370. private void ReadImpl(ulong va, Span<byte> data)
  371. {
  372. if (data.Length == 0)
  373. {
  374. return;
  375. }
  376. try
  377. {
  378. AssertValidAddressAndSize(va, (ulong)data.Length);
  379. int offset = 0, size;
  380. if ((va & PageMask) != 0)
  381. {
  382. ulong pa = GetPhysicalAddressInternal(va);
  383. size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
  384. _backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
  385. offset += size;
  386. }
  387. for (; offset < data.Length; offset += size)
  388. {
  389. ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
  390. size = Math.Min(data.Length - offset, PageSize);
  391. _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
  392. }
  393. }
  394. catch (InvalidMemoryRegionException)
  395. {
  396. if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
  397. {
  398. throw;
  399. }
  400. }
  401. }
  402. /// <inheritdoc/>
  403. public bool IsRangeMapped(ulong va, ulong size)
  404. {
  405. if (size == 0UL)
  406. {
  407. return true;
  408. }
  409. if (!ValidateAddressAndSize(va, size))
  410. {
  411. return false;
  412. }
  413. int pages = GetPagesCount(va, (uint)size, out va);
  414. for (int page = 0; page < pages; page++)
  415. {
  416. if (!IsMapped(va))
  417. {
  418. return false;
  419. }
  420. va += PageSize;
  421. }
  422. return true;
  423. }
  424. /// <inheritdoc/>
  425. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  426. public bool IsMapped(ulong va)
  427. {
  428. if (!ValidateAddress(va))
  429. {
  430. return false;
  431. }
  432. return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
  433. }
  434. private bool ValidateAddress(ulong va)
  435. {
  436. return va < _addressSpaceSize;
  437. }
  438. /// <summary>
  439. /// Checks if the combination of virtual address and size is part of the addressable space.
  440. /// </summary>
  441. /// <param name="va">Virtual address of the range</param>
  442. /// <param name="size">Size of the range in bytes</param>
  443. /// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
  444. private bool ValidateAddressAndSize(ulong va, ulong size)
  445. {
  446. ulong endVa = va + size;
  447. return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
  448. }
  449. /// <summary>
  450. /// Ensures the combination of virtual address and size is part of the addressable space.
  451. /// </summary>
  452. /// <param name="va">Virtual address of the range</param>
  453. /// <param name="size">Size of the range in bytes</param>
  454. /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
  455. private void AssertValidAddressAndSize(ulong va, ulong size)
  456. {
  457. if (!ValidateAddressAndSize(va, size))
  458. {
  459. throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
  460. }
  461. }
  462. private ulong GetPhysicalAddressInternal(ulong va)
  463. {
  464. return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
  465. }
  466. /// <inheritdoc/>
  467. public void Reprotect(ulong va, ulong size, MemoryPermission protection)
  468. {
  469. // TODO
  470. }
  471. /// <inheritdoc/>
  472. public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
  473. {
  474. AssertValidAddressAndSize(va, size);
  475. // Protection is inverted on software pages, since the default value is 0.
  476. protection = (~protection) & MemoryPermission.ReadAndWrite;
  477. long tag = protection switch
  478. {
  479. MemoryPermission.None => 0L,
  480. MemoryPermission.Write => 2L << PointerTagBit,
  481. _ => 3L << PointerTagBit,
  482. };
  483. int pages = GetPagesCount(va, (uint)size, out va);
  484. ulong pageStart = va >> PageBits;
  485. long invTagMask = ~(0xffffL << 48);
  486. for (int page = 0; page < pages; page++)
  487. {
  488. ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
  489. long pte;
  490. do
  491. {
  492. pte = Volatile.Read(ref pageRef);
  493. }
  494. while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
  495. pageStart++;
  496. }
  497. }
  498. /// <inheritdoc/>
  499. public RegionHandle BeginTracking(ulong address, ulong size, int id)
  500. {
  501. return Tracking.BeginTracking(address, size, id);
  502. }
  503. /// <inheritdoc/>
  504. public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
  505. {
  506. return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
  507. }
  508. /// <inheritdoc/>
  509. public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
  510. {
  511. return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
  512. }
  513. /// <inheritdoc/>
  514. public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
  515. {
  516. AssertValidAddressAndSize(va, size);
  517. if (precise)
  518. {
  519. Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
  520. return;
  521. }
  522. // We emulate guard pages for software memory access. This makes for an easy transition to
  523. // tracking using host guard pages in future, but also supporting platforms where this is not possible.
  524. // Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
  525. long tag = (write ? 3L : 1L) << PointerTagBit;
  526. int pages = GetPagesCount(va, (uint)size, out _);
  527. ulong pageStart = va >> PageBits;
  528. for (int page = 0; page < pages; page++)
  529. {
  530. ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
  531. long pte;
  532. pte = Volatile.Read(ref pageRef);
  533. if ((pte & tag) != 0)
  534. {
  535. Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
  536. break;
  537. }
  538. pageStart++;
  539. }
  540. }
  541. private ulong PaToPte(ulong pa)
  542. {
  543. return (ulong)_backingMemory.GetPointer(pa, PageSize);
  544. }
  545. private ulong PteToPa(ulong pte)
  546. {
  547. return (ulong)((long)pte - _backingMemory.Pointer.ToInt64());
  548. }
  549. /// <summary>
  550. /// Disposes of resources used by the memory manager.
  551. /// </summary>
  552. protected override void Destroy() => _pageTable.Dispose();
  553. }
  554. }