MemoryManager.cs 20 KB

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