Ptc.cs 34 KB

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