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