Ptc.cs 38 KB

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