Ptc.cs 39 KB

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