JitCache.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using ARMeilleure.Memory;
  2. using Humanizer;
  3. using Ryujinx.Common.Logging;
  4. using Ryujinx.Memory;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Runtime.InteropServices;
  9. using System.Runtime.Versioning;
  10. using System.Threading;
  11. namespace Ryujinx.Cpu.LightningJit.Cache
  12. {
  13. static partial class JitCache
  14. {
  15. private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
  16. private static readonly int _pageMask = _pageSize - 1;
  17. private const int CodeAlignment = 4; // Bytes.
  18. private const int CacheSize = 256 * 1024 * 1024;
  19. private static JitCacheInvalidation _jitCacheInvalidator;
  20. private static CacheMemoryAllocator _cacheAllocator;
  21. private static readonly List<CacheEntry> _cacheEntries = [];
  22. private static readonly Lock _lock = new();
  23. private static bool _initialized;
  24. private static readonly List<ReservedRegion> _jitRegions = new();
  25. private static int _activeRegionIndex = 0;
  26. [SupportedOSPlatform("windows")]
  27. [LibraryImport("kernel32.dll", SetLastError = true)]
  28. public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
  29. public static void Initialize(IJitMemoryAllocator allocator)
  30. {
  31. if (_initialized)
  32. {
  33. return;
  34. }
  35. lock (_lock)
  36. {
  37. if (_initialized)
  38. {
  39. return;
  40. }
  41. var firstRegion = new ReservedRegion(allocator, CacheSize);
  42. _jitRegions.Add(firstRegion);
  43. _activeRegionIndex = 0;
  44. if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
  45. {
  46. _jitCacheInvalidator = new JitCacheInvalidation(allocator);
  47. }
  48. _cacheAllocator = new CacheMemoryAllocator(CacheSize);
  49. _initialized = true;
  50. }
  51. }
  52. public unsafe static nint Map(ReadOnlySpan<byte> code)
  53. {
  54. lock (_lock)
  55. {
  56. Debug.Assert(_initialized);
  57. int funcOffset = Allocate(code.Length);
  58. ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
  59. nint funcPtr = targetRegion.Pointer + funcOffset;
  60. if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
  61. {
  62. unsafe
  63. {
  64. fixed (byte* codePtr = code)
  65. {
  66. JitSupportDarwin.Copy(funcPtr, (nint)codePtr, (ulong)code.Length);
  67. }
  68. }
  69. }
  70. else
  71. {
  72. ReprotectAsWritable(targetRegion, funcOffset, code.Length);
  73. Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
  74. ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
  75. _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
  76. }
  77. Add(funcOffset, code.Length);
  78. return funcPtr;
  79. }
  80. }
  81. public static void Unmap(nint pointer)
  82. {
  83. lock (_lock)
  84. {
  85. Debug.Assert(_initialized);
  86. foreach (var region in _jitRegions)
  87. {
  88. if (pointer.ToInt64() < region.Pointer.ToInt64() ||
  89. pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
  90. {
  91. continue;
  92. }
  93. int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
  94. if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
  95. {
  96. _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
  97. _cacheEntries.RemoveAt(entryIndex);
  98. }
  99. return;
  100. }
  101. }
  102. }
  103. private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
  104. {
  105. int endOffs = offset + size;
  106. int regionStart = offset & ~_pageMask;
  107. int regionEnd = (endOffs + _pageMask) & ~_pageMask;
  108. region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
  109. }
  110. private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
  111. {
  112. int endOffs = offset + size;
  113. int regionStart = offset & ~_pageMask;
  114. int regionEnd = (endOffs + _pageMask) & ~_pageMask;
  115. region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
  116. }
  117. private static int Allocate(int codeSize)
  118. {
  119. codeSize = AlignCodeSize(codeSize);
  120. for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
  121. {
  122. int allocOffset = _cacheAllocator.Allocate(codeSize);
  123. if (allocOffset >= 0)
  124. {
  125. _jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
  126. _activeRegionIndex = i;
  127. return allocOffset;
  128. }
  129. }
  130. int exhaustedRegion = _activeRegionIndex;
  131. var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
  132. _jitRegions.Add(newRegion);
  133. _activeRegionIndex = _jitRegions.Count - 1;
  134. int newRegionNumber = _activeRegionIndex;
  135. Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
  136. _cacheAllocator = new CacheMemoryAllocator(CacheSize);
  137. int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
  138. if (allocOffsetNew < 0)
  139. {
  140. throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
  141. }
  142. newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
  143. return allocOffsetNew;
  144. }
  145. private static int AlignCodeSize(int codeSize)
  146. {
  147. return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
  148. }
  149. private static void Add(int offset, int size)
  150. {
  151. CacheEntry entry = new(offset, size);
  152. int index = _cacheEntries.BinarySearch(entry);
  153. if (index < 0)
  154. {
  155. index = ~index;
  156. }
  157. _cacheEntries.Insert(index, entry);
  158. }
  159. public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
  160. {
  161. lock (_lock)
  162. {
  163. int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0));
  164. if (index < 0)
  165. {
  166. index = ~index - 1;
  167. }
  168. if (index >= 0)
  169. {
  170. entry = _cacheEntries[index];
  171. entryIndex = index;
  172. return true;
  173. }
  174. }
  175. entry = default;
  176. entryIndex = 0;
  177. return false;
  178. }
  179. }
  180. }