Ptc.cs 38 KB

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