JitCache.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. using ARMeilleure.CodeGen;
  2. using ARMeilleure.CodeGen.Unwinding;
  3. using ARMeilleure.Memory;
  4. using ARMeilleure.Native;
  5. using Ryujinx.Memory;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Runtime.InteropServices;
  10. using System.Runtime.Versioning;
  11. namespace ARMeilleure.Translation.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 = 2047 * 1024 * 1024;
  19. private static ReservedRegion _jitRegion;
  20. private static JitCacheInvalidation _jitCacheInvalidator;
  21. private static CacheMemoryAllocator _cacheAllocator;
  22. private static readonly List<CacheEntry> _cacheEntries = new();
  23. private static readonly object _lock = new();
  24. private static bool _initialized;
  25. [SupportedOSPlatform("windows")]
  26. [LibraryImport("kernel32.dll", SetLastError = true)]
  27. public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
  28. public static void Initialize(IJitMemoryAllocator allocator)
  29. {
  30. if (_initialized)
  31. {
  32. return;
  33. }
  34. lock (_lock)
  35. {
  36. if (_initialized)
  37. {
  38. return;
  39. }
  40. _jitRegion = new ReservedRegion(allocator, CacheSize);
  41. if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
  42. {
  43. _jitCacheInvalidator = new JitCacheInvalidation(allocator);
  44. }
  45. _cacheAllocator = new CacheMemoryAllocator(CacheSize);
  46. if (OperatingSystem.IsWindows())
  47. {
  48. JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
  49. }
  50. _initialized = true;
  51. }
  52. }
  53. public static IntPtr Map(CompiledFunction func)
  54. {
  55. byte[] code = func.Code;
  56. lock (_lock)
  57. {
  58. Debug.Assert(_initialized);
  59. int funcOffset = Allocate(code.Length);
  60. IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
  61. if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
  62. {
  63. unsafe
  64. {
  65. fixed (byte* codePtr = code)
  66. {
  67. JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length);
  68. }
  69. }
  70. }
  71. else
  72. {
  73. ReprotectAsWritable(funcOffset, code.Length);
  74. Marshal.Copy(code, 0, funcPtr, code.Length);
  75. ReprotectAsExecutable(funcOffset, code.Length);
  76. if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
  77. {
  78. FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length);
  79. }
  80. else
  81. {
  82. _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
  83. }
  84. }
  85. Add(funcOffset, code.Length, func.UnwindInfo);
  86. return funcPtr;
  87. }
  88. }
  89. public static void Unmap(IntPtr pointer)
  90. {
  91. lock (_lock)
  92. {
  93. Debug.Assert(_initialized);
  94. int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
  95. if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
  96. {
  97. _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
  98. _cacheEntries.RemoveAt(entryIndex);
  99. }
  100. }
  101. }
  102. private static void ReprotectAsWritable(int offset, int size)
  103. {
  104. int endOffs = offset + size;
  105. int regionStart = offset & ~_pageMask;
  106. int regionEnd = (endOffs + _pageMask) & ~_pageMask;
  107. _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
  108. }
  109. private static void ReprotectAsExecutable(int offset, int size)
  110. {
  111. int endOffs = offset + size;
  112. int regionStart = offset & ~_pageMask;
  113. int regionEnd = (endOffs + _pageMask) & ~_pageMask;
  114. _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
  115. }
  116. private static int Allocate(int codeSize)
  117. {
  118. codeSize = AlignCodeSize(codeSize);
  119. int allocOffset = _cacheAllocator.Allocate(codeSize);
  120. if (allocOffset < 0)
  121. {
  122. throw new OutOfMemoryException("JIT Cache exhausted.");
  123. }
  124. _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
  125. return allocOffset;
  126. }
  127. private static int AlignCodeSize(int codeSize)
  128. {
  129. return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
  130. }
  131. private static void Add(int offset, int size, UnwindInfo unwindInfo)
  132. {
  133. CacheEntry entry = new(offset, size, unwindInfo);
  134. int index = _cacheEntries.BinarySearch(entry);
  135. if (index < 0)
  136. {
  137. index = ~index;
  138. }
  139. _cacheEntries.Insert(index, entry);
  140. }
  141. public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
  142. {
  143. lock (_lock)
  144. {
  145. int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
  146. if (index < 0)
  147. {
  148. index = ~index - 1;
  149. }
  150. if (index >= 0)
  151. {
  152. entry = _cacheEntries[index];
  153. entryIndex = index;
  154. return true;
  155. }
  156. }
  157. entry = default;
  158. entryIndex = 0;
  159. return false;
  160. }
  161. }
  162. }