Ptc.cs 34 KB

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