Ptc.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  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 = 4484; //! 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. }
  728. else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
  729. {
  730. return new FeatureInfo(
  731. (ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
  732. (ulong)X86HardwareCapabilities.FeatureInfo1Edx,
  733. (ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
  734. (ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
  735. }
  736. else
  737. {
  738. return new FeatureInfo(0, 0, 0, 0);
  739. }
  740. }
  741. private byte GetMemoryManagerMode()
  742. {
  743. return (byte)_memoryMode;
  744. }
  745. private static uint GetOSPlatform()
  746. {
  747. uint osPlatform = 0u;
  748. osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0;
  749. osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1;
  750. osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2;
  751. osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3;
  752. return osPlatform;
  753. }
  754. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
  755. private struct OuterHeader
  756. {
  757. public ulong Magic;
  758. public uint CacheFileVersion;
  759. public bool Endianness;
  760. public FeatureInfo FeatureInfo;
  761. public byte MemoryManagerMode;
  762. public uint OSPlatform;
  763. public uint Architecture;
  764. public long UncompressedStreamSize;
  765. public Hash128 HeaderHash;
  766. public void SetHeaderHash()
  767. {
  768. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  769. HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
  770. }
  771. public bool IsHeaderValid()
  772. {
  773. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  774. return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
  775. }
  776. }
  777. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
  778. private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
  779. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
  780. private struct InnerHeader
  781. {
  782. public ulong Magic;
  783. public int InfosLength;
  784. public long CodesLength;
  785. public int RelocsLength;
  786. public int UnwindInfosLength;
  787. public Hash128 InfosHash;
  788. public Hash128 CodesHash;
  789. public Hash128 RelocsHash;
  790. public Hash128 UnwindInfosHash;
  791. public Hash128 HeaderHash;
  792. public void SetHeaderHash()
  793. {
  794. Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  795. HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>()));
  796. }
  797. public bool IsHeaderValid()
  798. {
  799. Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  800. return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
  801. }
  802. }
  803. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)]
  804. private struct InfoEntry
  805. {
  806. public ulong Address;
  807. public ulong GuestSize;
  808. public Hash128 Hash;
  809. public bool HighCq;
  810. public bool Stubbed;
  811. public int CodeLength;
  812. public int RelocEntriesCount;
  813. }
  814. private void Enable()
  815. {
  816. State = PtcState.Enabled;
  817. }
  818. public void Continue()
  819. {
  820. if (State == PtcState.Enabled)
  821. {
  822. State = PtcState.Continuing;
  823. }
  824. }
  825. public void Close()
  826. {
  827. if (State == PtcState.Enabled ||
  828. State == PtcState.Continuing)
  829. {
  830. State = PtcState.Closing;
  831. }
  832. }
  833. public void Disable()
  834. {
  835. State = PtcState.Disabled;
  836. }
  837. private void Wait()
  838. {
  839. _waitEvent.WaitOne();
  840. }
  841. public void Dispose()
  842. {
  843. if (!_disposed)
  844. {
  845. _disposed = true;
  846. Wait();
  847. _waitEvent.Dispose();
  848. DisposeCarriers();
  849. }
  850. }
  851. }
  852. }