Ptc.cs 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. using ARMeilleure.CodeGen;
  2. using ARMeilleure.CodeGen.Unwinding;
  3. using ARMeilleure.CodeGen.X86;
  4. using ARMeilleure.Common;
  5. using ARMeilleure.Memory;
  6. using ARMeilleure.Translation.Cache;
  7. using Ryujinx.Common;
  8. using Ryujinx.Common.Configuration;
  9. using Ryujinx.Common.Logging;
  10. using System;
  11. using System.Buffers.Binary;
  12. using System.Collections.Concurrent;
  13. using System.Collections.Generic;
  14. using System.Diagnostics;
  15. using System.IO;
  16. using System.IO.Compression;
  17. using System.Runtime;
  18. using System.Runtime.CompilerServices;
  19. using System.Runtime.InteropServices;
  20. using System.Threading;
  21. using static ARMeilleure.Translation.PTC.PtcFormatter;
  22. namespace ARMeilleure.Translation.PTC
  23. {
  24. public static class Ptc
  25. {
  26. private const string OuterHeaderMagicString = "PTCohd\0\0";
  27. private const string InnerHeaderMagicString = "PTCihd\0\0";
  28. private const uint InternalVersion = 2305; //! To be incremented manually for each change to the ARMeilleure project.
  29. private const string ActualDir = "0";
  30. private const string BackupDir = "1";
  31. private const string TitleIdTextDefault = "0000000000000000";
  32. private const string DisplayVersionDefault = "0";
  33. internal const int PageTablePointerIndex = -1; // Must be a negative value.
  34. internal const int JumpPointerIndex = -2; // Must be a negative value.
  35. internal const int DynamicPointerIndex = -3; // Must be a negative value.
  36. internal const int CountTableIndex = -4; // Must be a negative value.
  37. private const byte FillingByte = 0x00;
  38. private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
  39. // Carriers.
  40. private static MemoryStream _infosStream;
  41. private static List<byte[]> _codesList;
  42. private static MemoryStream _relocsStream;
  43. private static MemoryStream _unwindInfosStream;
  44. private static readonly ulong _outerHeaderMagic;
  45. private static readonly ulong _innerHeaderMagic;
  46. private static readonly ManualResetEvent _waitEvent;
  47. private static readonly object _lock;
  48. private static bool _disposed;
  49. internal static PtcJumpTable PtcJumpTable { get; private set; }
  50. internal static string TitleIdText { get; private set; }
  51. internal static string DisplayVersion { get; private set; }
  52. internal static string CachePathActual { get; private set; }
  53. internal static string CachePathBackup { get; private set; }
  54. internal static PtcState State { get; private set; }
  55. // Progress reporting helpers.
  56. private static volatile int _translateCount;
  57. private static volatile int _translateTotalCount;
  58. public static event Action<PtcLoadingState, int, int> PtcStateChanged;
  59. static Ptc()
  60. {
  61. InitializeCarriers();
  62. _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
  63. _innerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(InnerHeaderMagicString).AsSpan());
  64. _waitEvent = new ManualResetEvent(true);
  65. _lock = new object();
  66. _disposed = false;
  67. PtcJumpTable = new PtcJumpTable();
  68. TitleIdText = TitleIdTextDefault;
  69. DisplayVersion = DisplayVersionDefault;
  70. CachePathActual = string.Empty;
  71. CachePathBackup = string.Empty;
  72. Disable();
  73. }
  74. public static void Initialize(string titleIdText, string displayVersion, bool enabled)
  75. {
  76. Wait();
  77. PtcProfiler.Wait();
  78. PtcProfiler.ClearEntries();
  79. Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
  80. if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
  81. {
  82. TitleIdText = TitleIdTextDefault;
  83. DisplayVersion = DisplayVersionDefault;
  84. CachePathActual = string.Empty;
  85. CachePathBackup = string.Empty;
  86. Disable();
  87. return;
  88. }
  89. TitleIdText = titleIdText;
  90. DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
  91. string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
  92. string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
  93. if (!Directory.Exists(workPathActual))
  94. {
  95. Directory.CreateDirectory(workPathActual);
  96. }
  97. if (!Directory.Exists(workPathBackup))
  98. {
  99. Directory.CreateDirectory(workPathBackup);
  100. }
  101. CachePathActual = Path.Combine(workPathActual, DisplayVersion);
  102. CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
  103. PreLoad();
  104. PtcProfiler.PreLoad();
  105. Enable();
  106. }
  107. private static void InitializeCarriers()
  108. {
  109. _infosStream = new MemoryStream();
  110. _codesList = new List<byte[]>();
  111. _relocsStream = new MemoryStream();
  112. _unwindInfosStream = new MemoryStream();
  113. }
  114. private static void DisposeCarriers()
  115. {
  116. _infosStream.Dispose();
  117. _codesList.Clear();
  118. _relocsStream.Dispose();
  119. _unwindInfosStream.Dispose();
  120. }
  121. private static bool AreCarriersEmpty()
  122. {
  123. return _infosStream.Length == 0L && _codesList.Count == 0 && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L;
  124. }
  125. private static void ResetCarriersIfNeeded()
  126. {
  127. if (AreCarriersEmpty())
  128. {
  129. return;
  130. }
  131. DisposeCarriers();
  132. InitializeCarriers();
  133. }
  134. private static void PreLoad()
  135. {
  136. string fileNameActual = string.Concat(CachePathActual, ".cache");
  137. string fileNameBackup = string.Concat(CachePathBackup, ".cache");
  138. FileInfo fileInfoActual = new FileInfo(fileNameActual);
  139. FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
  140. if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
  141. {
  142. if (!Load(fileNameActual, false))
  143. {
  144. if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
  145. {
  146. Load(fileNameBackup, true);
  147. }
  148. }
  149. }
  150. else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
  151. {
  152. Load(fileNameBackup, true);
  153. }
  154. }
  155. private static unsafe bool Load(string fileName, bool isBackup)
  156. {
  157. using (FileStream compressedStream = new(fileName, FileMode.Open))
  158. using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
  159. {
  160. OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
  161. if (!outerHeader.IsHeaderValid())
  162. {
  163. InvalidateCompressedStream(compressedStream);
  164. return false;
  165. }
  166. if (outerHeader.Magic != _outerHeaderMagic)
  167. {
  168. InvalidateCompressedStream(compressedStream);
  169. return false;
  170. }
  171. if (outerHeader.CacheFileVersion != InternalVersion)
  172. {
  173. InvalidateCompressedStream(compressedStream);
  174. return false;
  175. }
  176. if (outerHeader.Endianness != GetEndianness())
  177. {
  178. InvalidateCompressedStream(compressedStream);
  179. return false;
  180. }
  181. if (outerHeader.FeatureInfo != GetFeatureInfo())
  182. {
  183. InvalidateCompressedStream(compressedStream);
  184. return false;
  185. }
  186. if (outerHeader.OSPlatform != GetOSPlatform())
  187. {
  188. InvalidateCompressedStream(compressedStream);
  189. return false;
  190. }
  191. IntPtr intPtr = IntPtr.Zero;
  192. try
  193. {
  194. intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize));
  195. using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite))
  196. {
  197. try
  198. {
  199. deflateStream.CopyTo(stream);
  200. }
  201. catch
  202. {
  203. InvalidateCompressedStream(compressedStream);
  204. return false;
  205. }
  206. Debug.Assert(stream.Position == stream.Length);
  207. stream.Seek(0L, SeekOrigin.Begin);
  208. InnerHeader innerHeader = DeserializeStructure<InnerHeader>(stream);
  209. if (!innerHeader.IsHeaderValid())
  210. {
  211. InvalidateCompressedStream(compressedStream);
  212. return false;
  213. }
  214. if (innerHeader.Magic != _innerHeaderMagic)
  215. {
  216. InvalidateCompressedStream(compressedStream);
  217. return false;
  218. }
  219. ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
  220. stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
  221. Hash128 infosHash = XXHash128.ComputeHash(infosBytes);
  222. if (innerHeader.InfosHash != infosHash)
  223. {
  224. InvalidateCompressedStream(compressedStream);
  225. return false;
  226. }
  227. ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
  228. stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
  229. Hash128 codesHash = XXHash128.ComputeHash(codesBytes);
  230. if (innerHeader.CodesHash != codesHash)
  231. {
  232. InvalidateCompressedStream(compressedStream);
  233. return false;
  234. }
  235. ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
  236. stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
  237. Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes);
  238. if (innerHeader.RelocsHash != relocsHash)
  239. {
  240. InvalidateCompressedStream(compressedStream);
  241. return false;
  242. }
  243. ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
  244. stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
  245. Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
  246. if (innerHeader.UnwindInfosHash != unwindInfosHash)
  247. {
  248. InvalidateCompressedStream(compressedStream);
  249. return false;
  250. }
  251. ReadOnlySpan<byte> ptcJumpTableBytes = new(stream.PositionPointer, innerHeader.PtcJumpTableLength);
  252. stream.Seek(innerHeader.PtcJumpTableLength, SeekOrigin.Current);
  253. Debug.Assert(stream.Position == stream.Length);
  254. Hash128 ptcJumpTableHash = XXHash128.ComputeHash(ptcJumpTableBytes);
  255. if (innerHeader.PtcJumpTableHash != ptcJumpTableHash)
  256. {
  257. InvalidateCompressedStream(compressedStream);
  258. return false;
  259. }
  260. stream.Seek((long)Unsafe.SizeOf<InnerHeader>(), SeekOrigin.Begin);
  261. _infosStream.Write(infosBytes);
  262. stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
  263. _codesList.ReadFrom(stream);
  264. _relocsStream.Write(relocsBytes);
  265. stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
  266. _unwindInfosStream.Write(unwindInfosBytes);
  267. stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
  268. PtcJumpTable = PtcJumpTable.Deserialize(stream);
  269. Debug.Assert(stream.Position == stream.Length);
  270. }
  271. }
  272. finally
  273. {
  274. if (intPtr != IntPtr.Zero)
  275. {
  276. Marshal.FreeHGlobal(intPtr);
  277. }
  278. }
  279. }
  280. long fileSize = new FileInfo(fileName).Length;
  281. Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()}).");
  282. return true;
  283. }
  284. private static void InvalidateCompressedStream(FileStream compressedStream)
  285. {
  286. compressedStream.SetLength(0L);
  287. }
  288. private static void PreSave()
  289. {
  290. _waitEvent.Reset();
  291. try
  292. {
  293. string fileNameActual = string.Concat(CachePathActual, ".cache");
  294. string fileNameBackup = string.Concat(CachePathBackup, ".cache");
  295. FileInfo fileInfoActual = new FileInfo(fileNameActual);
  296. if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
  297. {
  298. File.Copy(fileNameActual, fileNameBackup, true);
  299. }
  300. Save(fileNameActual);
  301. }
  302. finally
  303. {
  304. ResetCarriersIfNeeded();
  305. PtcJumpTable.ClearIfNeeded();
  306. GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
  307. }
  308. _waitEvent.Set();
  309. }
  310. private static unsafe void Save(string fileName)
  311. {
  312. int translatedFuncsCount;
  313. InnerHeader innerHeader = new InnerHeader();
  314. innerHeader.Magic = _innerHeaderMagic;
  315. innerHeader.InfosLength = (int)_infosStream.Length;
  316. innerHeader.CodesLength = _codesList.Length();
  317. innerHeader.RelocsLength = (int)_relocsStream.Length;
  318. innerHeader.UnwindInfosLength = (int)_unwindInfosStream.Length;
  319. innerHeader.PtcJumpTableLength = PtcJumpTable.GetSerializeSize(PtcJumpTable);
  320. OuterHeader outerHeader = new OuterHeader();
  321. outerHeader.Magic = _outerHeaderMagic;
  322. outerHeader.CacheFileVersion = InternalVersion;
  323. outerHeader.Endianness = GetEndianness();
  324. outerHeader.FeatureInfo = GetFeatureInfo();
  325. outerHeader.OSPlatform = GetOSPlatform();
  326. outerHeader.UncompressedStreamSize =
  327. (long)Unsafe.SizeOf<InnerHeader>() +
  328. innerHeader.InfosLength +
  329. innerHeader.CodesLength +
  330. innerHeader.RelocsLength +
  331. innerHeader.UnwindInfosLength +
  332. innerHeader.PtcJumpTableLength;
  333. outerHeader.SetHeaderHash();
  334. IntPtr intPtr = IntPtr.Zero;
  335. try
  336. {
  337. intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize));
  338. using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite))
  339. {
  340. stream.Seek((long)Unsafe.SizeOf<InnerHeader>(), SeekOrigin.Begin);
  341. ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
  342. _infosStream.WriteTo(stream);
  343. ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
  344. _codesList.WriteTo(stream);
  345. ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
  346. _relocsStream.WriteTo(stream);
  347. ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
  348. _unwindInfosStream.WriteTo(stream);
  349. ReadOnlySpan<byte> ptcJumpTableBytes = new(stream.PositionPointer, innerHeader.PtcJumpTableLength);
  350. PtcJumpTable.Serialize(stream, PtcJumpTable);
  351. Debug.Assert(stream.Position == stream.Length);
  352. innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes);
  353. innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes);
  354. innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes);
  355. innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
  356. innerHeader.PtcJumpTableHash = XXHash128.ComputeHash(ptcJumpTableBytes);
  357. innerHeader.SetHeaderHash();
  358. stream.Seek(0L, SeekOrigin.Begin);
  359. SerializeStructure(stream, innerHeader);
  360. translatedFuncsCount = GetEntriesCount();
  361. ResetCarriersIfNeeded();
  362. PtcJumpTable.ClearIfNeeded();
  363. using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate))
  364. using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true))
  365. {
  366. try
  367. {
  368. SerializeStructure(compressedStream, outerHeader);
  369. stream.Seek(0L, SeekOrigin.Begin);
  370. stream.CopyTo(deflateStream);
  371. }
  372. catch
  373. {
  374. compressedStream.Position = 0L;
  375. }
  376. if (compressedStream.Position < compressedStream.Length)
  377. {
  378. compressedStream.SetLength(compressedStream.Position);
  379. }
  380. }
  381. }
  382. }
  383. finally
  384. {
  385. if (intPtr != IntPtr.Zero)
  386. {
  387. Marshal.FreeHGlobal(intPtr);
  388. }
  389. }
  390. long fileSize = new FileInfo(fileName).Length;
  391. if (fileSize != 0L)
  392. {
  393. Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount}).");
  394. }
  395. }
  396. internal static void LoadTranslations(
  397. ConcurrentDictionary<ulong, TranslatedFunction> funcs,
  398. IMemoryManager memory,
  399. JumpTable jumpTable,
  400. EntryTable<uint> countTable)
  401. {
  402. if (AreCarriersEmpty())
  403. {
  404. return;
  405. }
  406. long infosStreamLength = _infosStream.Length;
  407. long relocsStreamLength = _relocsStream.Length;
  408. long unwindInfosStreamLength = _unwindInfosStream.Length;
  409. _infosStream.Seek(0L, SeekOrigin.Begin);
  410. _relocsStream.Seek(0L, SeekOrigin.Begin);
  411. _unwindInfosStream.Seek(0L, SeekOrigin.Begin);
  412. using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true))
  413. using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
  414. {
  415. for (int index = 0; index < GetEntriesCount(); index++)
  416. {
  417. InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
  418. if (infoEntry.Stubbed)
  419. {
  420. SkipCode(index, infoEntry.CodeLength);
  421. SkipReloc(infoEntry.RelocEntriesCount);
  422. SkipUnwindInfo(unwindInfosReader);
  423. continue;
  424. }
  425. bool isEntryChanged = infoEntry.Hash != ComputeHash(memory, infoEntry.Address, infoEntry.GuestSize);
  426. if (isEntryChanged || (!infoEntry.HighCq && PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq))
  427. {
  428. infoEntry.Stubbed = true;
  429. infoEntry.CodeLength = 0;
  430. UpdateInfo(infoEntry);
  431. StubCode(index);
  432. StubReloc(infoEntry.RelocEntriesCount);
  433. StubUnwindInfo(unwindInfosReader);
  434. if (isEntryChanged)
  435. {
  436. PtcJumpTable.Clean(infoEntry.Address);
  437. Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})");
  438. }
  439. continue;
  440. }
  441. byte[] code = ReadCode(index, infoEntry.CodeLength);
  442. Counter<uint> callCounter = null;
  443. if (infoEntry.RelocEntriesCount != 0)
  444. {
  445. RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
  446. PatchCode(code, relocEntries, memory.PageTablePointer, jumpTable, countTable, out callCounter);
  447. }
  448. UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
  449. TranslatedFunction func = FastTranslate(code, callCounter, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq);
  450. bool isAddressUnique = funcs.TryAdd(infoEntry.Address, func);
  451. Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
  452. }
  453. }
  454. if (_infosStream.Length != infosStreamLength || _infosStream.Position != infosStreamLength ||
  455. _relocsStream.Length != relocsStreamLength || _relocsStream.Position != relocsStreamLength ||
  456. _unwindInfosStream.Length != unwindInfosStreamLength || _unwindInfosStream.Position != unwindInfosStreamLength)
  457. {
  458. throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end.");
  459. }
  460. jumpTable.Initialize(PtcJumpTable, funcs);
  461. PtcJumpTable.WriteJumpTable(jumpTable, funcs);
  462. PtcJumpTable.WriteDynamicTable(jumpTable);
  463. Logger.Info?.Print(LogClass.Ptc, $"{funcs.Count} translated functions loaded");
  464. }
  465. private static int GetEntriesCount()
  466. {
  467. return _codesList.Count;
  468. }
  469. [Conditional("DEBUG")]
  470. private static void SkipCode(int index, int codeLength)
  471. {
  472. Debug.Assert(_codesList[index].Length == 0);
  473. Debug.Assert(codeLength == 0);
  474. }
  475. private static void SkipReloc(int relocEntriesCount)
  476. {
  477. _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current);
  478. }
  479. private static void SkipUnwindInfo(BinaryReader unwindInfosReader)
  480. {
  481. int pushEntriesLength = unwindInfosReader.ReadInt32();
  482. _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current);
  483. }
  484. private static byte[] ReadCode(int index, int codeLength)
  485. {
  486. Debug.Assert(_codesList[index].Length == codeLength);
  487. return _codesList[index];
  488. }
  489. private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount)
  490. {
  491. RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount];
  492. for (int i = 0; i < relocEntriesCount; i++)
  493. {
  494. int position = relocsReader.ReadInt32();
  495. int index = relocsReader.ReadInt32();
  496. relocEntries[i] = new RelocEntry(position, index);
  497. }
  498. return relocEntries;
  499. }
  500. private static void PatchCode(
  501. Span<byte> code,
  502. RelocEntry[] relocEntries,
  503. IntPtr pageTablePointer,
  504. JumpTable jumpTable,
  505. EntryTable<uint> countTable,
  506. out Counter<uint> callCounter)
  507. {
  508. callCounter = null;
  509. foreach (RelocEntry relocEntry in relocEntries)
  510. {
  511. ulong imm;
  512. if (relocEntry.Index == PageTablePointerIndex)
  513. {
  514. imm = (ulong)pageTablePointer.ToInt64();
  515. }
  516. else if (relocEntry.Index == JumpPointerIndex)
  517. {
  518. imm = (ulong)jumpTable.JumpPointer.ToInt64();
  519. }
  520. else if (relocEntry.Index == DynamicPointerIndex)
  521. {
  522. imm = (ulong)jumpTable.DynamicPointer.ToInt64();
  523. }
  524. else if (relocEntry.Index == CountTableIndex)
  525. {
  526. callCounter = new Counter<uint>(countTable);
  527. unsafe { imm = (ulong)Unsafe.AsPointer(ref callCounter.Value); }
  528. }
  529. else if (Delegates.TryGetDelegateFuncPtrByIndex(relocEntry.Index, out IntPtr funcPtr))
  530. {
  531. imm = (ulong)funcPtr.ToInt64();
  532. }
  533. else
  534. {
  535. throw new Exception($"Unexpected reloc entry {relocEntry}.");
  536. }
  537. BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), imm);
  538. }
  539. }
  540. private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader)
  541. {
  542. int pushEntriesLength = unwindInfosReader.ReadInt32();
  543. UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength];
  544. for (int i = 0; i < pushEntriesLength; i++)
  545. {
  546. int pseudoOp = unwindInfosReader.ReadInt32();
  547. int prologOffset = unwindInfosReader.ReadInt32();
  548. int regIndex = unwindInfosReader.ReadInt32();
  549. int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32();
  550. pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize);
  551. }
  552. int prologueSize = unwindInfosReader.ReadInt32();
  553. return new UnwindInfo(pushEntries, prologueSize);
  554. }
  555. private static TranslatedFunction FastTranslate(
  556. byte[] code,
  557. Counter<uint> callCounter,
  558. ulong guestSize,
  559. UnwindInfo unwindInfo,
  560. bool highCq)
  561. {
  562. CompiledFunction cFunc = new CompiledFunction(code, unwindInfo);
  563. IntPtr codePtr = JitCache.Map(cFunc);
  564. GuestFunction gFunc = Marshal.GetDelegateForFunctionPointer<GuestFunction>(codePtr);
  565. TranslatedFunction tFunc = new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
  566. return tFunc;
  567. }
  568. private static void UpdateInfo(InfoEntry infoEntry)
  569. {
  570. _infosStream.Seek(-Unsafe.SizeOf<InfoEntry>(), SeekOrigin.Current);
  571. SerializeStructure(_infosStream, infoEntry);
  572. }
  573. private static void StubCode(int index)
  574. {
  575. _codesList[index] = Array.Empty<byte>();
  576. }
  577. private static void StubReloc(int relocEntriesCount)
  578. {
  579. for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++)
  580. {
  581. _relocsStream.WriteByte(FillingByte);
  582. }
  583. }
  584. private static void StubUnwindInfo(BinaryReader unwindInfosReader)
  585. {
  586. int pushEntriesLength = unwindInfosReader.ReadInt32();
  587. for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++)
  588. {
  589. _unwindInfosStream.WriteByte(FillingByte);
  590. }
  591. }
  592. internal static void MakeAndSaveTranslations(
  593. ConcurrentDictionary<ulong, TranslatedFunction> funcs,
  594. IMemoryManager memory,
  595. JumpTable jumpTable,
  596. EntryTable<uint> countTable)
  597. {
  598. var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(funcs);
  599. _translateCount = 0;
  600. _translateTotalCount = profiledFuncsToTranslate.Count;
  601. int degreeOfParallelism = new DegreeOfParallelism(4d, 75d, 12.5d).GetDegreeOfParallelism(0, 32);
  602. if (_translateTotalCount == 0 || degreeOfParallelism == 0)
  603. {
  604. ResetCarriersIfNeeded();
  605. PtcJumpTable.ClearIfNeeded();
  606. GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
  607. return;
  608. }
  609. Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}");
  610. PtcStateChanged?.Invoke(PtcLoadingState.Start, _translateCount, _translateTotalCount);
  611. using AutoResetEvent progressReportEvent = new AutoResetEvent(false);
  612. Thread progressReportThread = new Thread(ReportProgress)
  613. {
  614. Name = "Ptc.ProgressReporter",
  615. Priority = ThreadPriority.Lowest,
  616. IsBackground = true
  617. };
  618. progressReportThread.Start(progressReportEvent);
  619. void TranslateFuncs()
  620. {
  621. while (profiledFuncsToTranslate.TryDequeue(out var item))
  622. {
  623. ulong address = item.address;
  624. Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
  625. TranslatedFunction func = Translator.Translate(memory, jumpTable, countTable, address, item.funcProfile.Mode, item.funcProfile.HighCq);
  626. bool isAddressUnique = funcs.TryAdd(address, func);
  627. Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");
  628. if (func.HighCq)
  629. {
  630. jumpTable.RegisterFunction(address, func);
  631. }
  632. Interlocked.Increment(ref _translateCount);
  633. if (State != PtcState.Enabled)
  634. {
  635. break;
  636. }
  637. }
  638. Translator.DisposePools();
  639. }
  640. List<Thread> threads = new List<Thread>();
  641. for (int i = 0; i < degreeOfParallelism; i++)
  642. {
  643. Thread thread = new Thread(TranslateFuncs);
  644. thread.IsBackground = true;
  645. threads.Add(thread);
  646. }
  647. threads.ForEach((thread) => thread.Start());
  648. threads.ForEach((thread) => thread.Join());
  649. threads.Clear();
  650. progressReportEvent.Set();
  651. progressReportThread.Join();
  652. PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount);
  653. Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}");
  654. PtcJumpTable.Initialize(jumpTable);
  655. PtcJumpTable.ReadJumpTable(jumpTable);
  656. PtcJumpTable.ReadDynamicTable(jumpTable);
  657. Thread preSaveThread = new Thread(PreSave);
  658. preSaveThread.IsBackground = true;
  659. preSaveThread.Start();
  660. }
  661. private static void ReportProgress(object state)
  662. {
  663. const int refreshRate = 50; // ms.
  664. AutoResetEvent endEvent = (AutoResetEvent)state;
  665. int count = 0;
  666. do
  667. {
  668. int newCount = _translateCount;
  669. if (count != newCount)
  670. {
  671. PtcStateChanged?.Invoke(PtcLoadingState.Loading, newCount, _translateTotalCount);
  672. count = newCount;
  673. }
  674. }
  675. while (!endEvent.WaitOne(refreshRate));
  676. }
  677. internal static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
  678. {
  679. return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
  680. }
  681. internal static void WriteInfoCodeRelocUnwindInfo(ulong address, ulong guestSize, Hash128 hash, bool highCq, PtcInfo ptcInfo)
  682. {
  683. lock (_lock)
  684. {
  685. InfoEntry infoEntry = new InfoEntry();
  686. infoEntry.Address = address;
  687. infoEntry.GuestSize = guestSize;
  688. infoEntry.Hash = hash;
  689. infoEntry.HighCq = highCq;
  690. infoEntry.Stubbed = false;
  691. infoEntry.CodeLength = ptcInfo.Code.Length;
  692. infoEntry.RelocEntriesCount = ptcInfo.RelocEntriesCount;
  693. SerializeStructure(_infosStream, infoEntry);
  694. WriteCode(ptcInfo.Code.AsSpan());
  695. // WriteReloc.
  696. ptcInfo.RelocStream.WriteTo(_relocsStream);
  697. // WriteUnwindInfo.
  698. ptcInfo.UnwindInfoStream.WriteTo(_unwindInfosStream);
  699. }
  700. }
  701. private static void WriteCode(ReadOnlySpan<byte> code)
  702. {
  703. _codesList.Add(code.ToArray());
  704. }
  705. internal static bool GetEndianness()
  706. {
  707. return BitConverter.IsLittleEndian;
  708. }
  709. private static ulong GetFeatureInfo()
  710. {
  711. return (ulong)HardwareCapabilities.FeatureInfoEdx << 32 | (uint)HardwareCapabilities.FeatureInfoEcx;
  712. }
  713. private static uint GetOSPlatform()
  714. {
  715. uint osPlatform = 0u;
  716. osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ? 1u : 0u) << 0;
  717. osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 1u : 0u) << 1;
  718. osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1u : 0u) << 2;
  719. osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1u : 0u) << 3;
  720. return osPlatform;
  721. }
  722. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 49*/)]
  723. private struct OuterHeader
  724. {
  725. public ulong Magic;
  726. public uint CacheFileVersion;
  727. public bool Endianness;
  728. public ulong FeatureInfo;
  729. public uint OSPlatform;
  730. public long UncompressedStreamSize;
  731. public Hash128 HeaderHash;
  732. public void SetHeaderHash()
  733. {
  734. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  735. HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
  736. }
  737. public bool IsHeaderValid()
  738. {
  739. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  740. return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
  741. }
  742. }
  743. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
  744. private struct InnerHeader
  745. {
  746. public ulong Magic;
  747. public int InfosLength;
  748. public long CodesLength;
  749. public int RelocsLength;
  750. public int UnwindInfosLength;
  751. public int PtcJumpTableLength;
  752. public Hash128 InfosHash;
  753. public Hash128 CodesHash;
  754. public Hash128 RelocsHash;
  755. public Hash128 UnwindInfosHash;
  756. public Hash128 PtcJumpTableHash;
  757. public Hash128 HeaderHash;
  758. public void SetHeaderHash()
  759. {
  760. Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  761. HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>()));
  762. }
  763. public bool IsHeaderValid()
  764. {
  765. Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  766. return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
  767. }
  768. }
  769. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)]
  770. private struct InfoEntry
  771. {
  772. public ulong Address;
  773. public ulong GuestSize;
  774. public Hash128 Hash;
  775. public bool HighCq;
  776. public bool Stubbed;
  777. public int CodeLength;
  778. public int RelocEntriesCount;
  779. }
  780. private static void Enable()
  781. {
  782. State = PtcState.Enabled;
  783. }
  784. public static void Continue()
  785. {
  786. if (State == PtcState.Enabled)
  787. {
  788. State = PtcState.Continuing;
  789. }
  790. }
  791. public static void Close()
  792. {
  793. if (State == PtcState.Enabled ||
  794. State == PtcState.Continuing)
  795. {
  796. State = PtcState.Closing;
  797. }
  798. }
  799. internal static void Disable()
  800. {
  801. State = PtcState.Disabled;
  802. }
  803. private static void Wait()
  804. {
  805. _waitEvent.WaitOne();
  806. }
  807. public static void Dispose()
  808. {
  809. if (!_disposed)
  810. {
  811. _disposed = true;
  812. Wait();
  813. _waitEvent.Dispose();
  814. DisposeCarriers();
  815. }
  816. }
  817. }
  818. }