Ptc.cs 34 KB

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