aboutsummaryrefslogtreecommitdiff
path: root/ARMeilleure/Translation/PTC
diff options
context:
space:
mode:
Diffstat (limited to 'ARMeilleure/Translation/PTC')
-rw-r--r--ARMeilleure/Translation/PTC/EncodingCache.cs9
-rw-r--r--ARMeilleure/Translation/PTC/Ptc.cs768
-rw-r--r--ARMeilleure/Translation/PTC/PtcInfo.cs68
-rw-r--r--ARMeilleure/Translation/PTC/PtcJumpTable.cs222
-rw-r--r--ARMeilleure/Translation/PTC/PtcProfiler.cs267
-rw-r--r--ARMeilleure/Translation/PTC/PtcState.cs10
-rw-r--r--ARMeilleure/Translation/PTC/RelocEntry.cs19
7 files changed, 1363 insertions, 0 deletions
diff --git a/ARMeilleure/Translation/PTC/EncodingCache.cs b/ARMeilleure/Translation/PTC/EncodingCache.cs
new file mode 100644
index 00000000..b87e0d7a
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/EncodingCache.cs
@@ -0,0 +1,9 @@
+using System.Text;
+
+namespace ARMeilleure.Translation.PTC
+{
+ internal static class EncodingCache
+ {
+ internal static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
new file mode 100644
index 00000000..76d3d7e1
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -0,0 +1,768 @@
+using ARMeilleure.CodeGen;
+using ARMeilleure.CodeGen.Unwinding;
+using ARMeilleure.Memory;
+using Ryujinx.Common.Logging;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ARMeilleure.Translation.PTC
+{
+ public static class Ptc
+ {
+ private const string HeaderMagic = "PTChd";
+
+ private const int InternalVersion = 0; //! To be incremented manually for each change to the ARMeilleure project.
+
+ private const string BaseDir = "Ryujinx";
+
+ private const string ActualDir = "0";
+ private const string BackupDir = "1";
+
+ private const string TitleIdTextDefault = "0000000000000000";
+ private const string DisplayVersionDefault = "0";
+
+ internal const int PageTablePointerIndex = -1; // Must be a negative value.
+ internal const int JumpPointerIndex = -2; // Must be a negative value.
+ internal const int DynamicPointerIndex = -3; // Must be a negative value.
+
+ private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
+
+ private static readonly MemoryStream _infosStream;
+ private static readonly MemoryStream _codesStream;
+ private static readonly MemoryStream _relocsStream;
+ private static readonly MemoryStream _unwindInfosStream;
+
+ private static readonly BinaryWriter _infosWriter;
+
+ private static readonly BinaryFormatter _binaryFormatter;
+
+ private static readonly ManualResetEvent _waitEvent;
+
+ private static readonly AutoResetEvent _loggerEvent;
+
+ private static readonly string _basePath;
+
+ private static readonly object _lock;
+
+ private static bool _disposed;
+
+ private static volatile int _translateCount;
+ private static volatile int _rejitCount;
+
+ internal static PtcJumpTable PtcJumpTable { get; private set; }
+
+ internal static string TitleIdText { get; private set; }
+ internal static string DisplayVersion { get; private set; }
+
+ internal static string CachePathActual { get; private set; }
+ internal static string CachePathBackup { get; private set; }
+
+ internal static PtcState State { get; private set; }
+
+ static Ptc()
+ {
+ _infosStream = new MemoryStream();
+ _codesStream = new MemoryStream();
+ _relocsStream = new MemoryStream();
+ _unwindInfosStream = new MemoryStream();
+
+ _infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true);
+
+ _binaryFormatter = new BinaryFormatter();
+
+ _waitEvent = new ManualResetEvent(true);
+
+ _loggerEvent = new AutoResetEvent(false);
+
+ _basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), BaseDir);
+
+ _lock = new object();
+
+ _disposed = false;
+
+ PtcJumpTable = new PtcJumpTable();
+
+ TitleIdText = TitleIdTextDefault;
+ DisplayVersion = DisplayVersionDefault;
+
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+ }
+
+ public static void Initialize(string titleIdText, string displayVersion, bool enabled)
+ {
+ Wait();
+ ClearMemoryStreams();
+ PtcJumpTable.Clear();
+
+ PtcProfiler.Stop();
+ PtcProfiler.Wait();
+ PtcProfiler.ClearEntries();
+
+ if (String.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
+ {
+ TitleIdText = TitleIdTextDefault;
+ DisplayVersion = DisplayVersionDefault;
+
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+
+ return;
+ }
+
+ Logger.PrintInfo(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
+
+ TitleIdText = titleIdText;
+ DisplayVersion = !String.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
+
+ if (enabled)
+ {
+ string workPathActual = Path.Combine(_basePath, "games", TitleIdText, "cache", "cpu", ActualDir);
+ string workPathBackup = Path.Combine(_basePath, "games", TitleIdText, "cache", "cpu", BackupDir);
+
+ if (!Directory.Exists(workPathActual))
+ {
+ Directory.CreateDirectory(workPathActual);
+ }
+
+ if (!Directory.Exists(workPathBackup))
+ {
+ Directory.CreateDirectory(workPathBackup);
+ }
+
+ CachePathActual = Path.Combine(workPathActual, DisplayVersion);
+ CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
+
+ Enable();
+
+ PreLoad();
+ PtcProfiler.PreLoad();
+ }
+ else
+ {
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+ }
+ }
+
+ internal static void ClearMemoryStreams()
+ {
+ _infosStream.SetLength(0L);
+ _codesStream.SetLength(0L);
+ _relocsStream.SetLength(0L);
+ _unwindInfosStream.SetLength(0L);
+ }
+
+ private static void PreLoad()
+ {
+ string fileNameActual = String.Concat(CachePathActual, ".cache");
+ string fileNameBackup = String.Concat(CachePathBackup, ".cache");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+ FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ if (!Load(fileNameActual))
+ {
+ if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+ }
+ else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+
+ private static bool Load(string fileName)
+ {
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
+ using (MemoryStream stream = new MemoryStream())
+ using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ deflateStream.CopyTo(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ byte[] currentHash = new byte[hashSize];
+ stream.Read(currentHash, 0, hashSize);
+
+ byte[] expectedHash = md5.ComputeHash(stream);
+
+ if (!CompareHash(currentHash, expectedHash))
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ Header header = ReadHeader(stream);
+
+ if (header.Magic != HeaderMagic)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.CacheFileVersion != InternalVersion)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.FeatureInfo != GetFeatureInfo())
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.InfosLen % InfoEntry.Stride != 0)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ byte[] infosBuf = new byte[header.InfosLen];
+ byte[] codesBuf = new byte[header.CodesLen];
+ byte[] relocsBuf = new byte[header.RelocsLen];
+ byte[] unwindInfosBuf = new byte[header.UnwindInfosLen];
+
+ stream.Read(infosBuf, 0, header.InfosLen);
+ stream.Read(codesBuf, 0, header.CodesLen);
+ stream.Read(relocsBuf, 0, header.RelocsLen);
+ stream.Read(unwindInfosBuf, 0, header.UnwindInfosLen);
+
+ try
+ {
+ PtcJumpTable = (PtcJumpTable)_binaryFormatter.Deserialize(stream);
+ }
+ catch
+ {
+ PtcJumpTable = new PtcJumpTable();
+
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ _infosStream.Write(infosBuf, 0, header.InfosLen);
+ _codesStream.Write(codesBuf, 0, header.CodesLen);
+ _relocsStream.Write(relocsBuf, 0, header.RelocsLen);
+ _unwindInfosStream.Write(unwindInfosBuf, 0, header.UnwindInfosLen);
+
+ return true;
+ }
+ }
+
+ private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
+ {
+ return currentHash.SequenceEqual(expectedHash);
+ }
+
+ private static Header ReadHeader(MemoryStream stream)
+ {
+ using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
+ {
+ Header header = new Header();
+
+ header.Magic = headerReader.ReadString();
+
+ header.CacheFileVersion = headerReader.ReadInt32();
+ header.FeatureInfo = headerReader.ReadUInt64();
+
+ header.InfosLen = headerReader.ReadInt32();
+ header.CodesLen = headerReader.ReadInt32();
+ header.RelocsLen = headerReader.ReadInt32();
+ header.UnwindInfosLen = headerReader.ReadInt32();
+
+ return header;
+ }
+ }
+
+ private static void InvalidateCompressedStream(FileStream compressedStream)
+ {
+ compressedStream.SetLength(0L);
+ }
+
+ private static void PreSave(object state)
+ {
+ _waitEvent.Reset();
+
+ string fileNameActual = String.Concat(CachePathActual, ".cache");
+ string fileNameBackup = String.Concat(CachePathBackup, ".cache");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ File.Copy(fileNameActual, fileNameBackup, true);
+ }
+
+ Save(fileNameActual);
+
+ _waitEvent.Set();
+ }
+
+ private static void Save(string fileName)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ WriteHeader(stream);
+
+ _infosStream.WriteTo(stream);
+ _codesStream.WriteTo(stream);
+ _relocsStream.WriteTo(stream);
+ _unwindInfosStream.WriteTo(stream);
+
+ _binaryFormatter.Serialize(stream, PtcJumpTable);
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+ byte[] hash = md5.ComputeHash(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+ stream.Write(hash, 0, hashSize);
+
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
+ {
+ try
+ {
+ stream.WriteTo(deflateStream);
+ }
+ catch
+ {
+ compressedStream.Position = 0L;
+ }
+
+ if (compressedStream.Position < compressedStream.Length)
+ {
+ compressedStream.SetLength(compressedStream.Position);
+ }
+ }
+ }
+ }
+
+ private static void WriteHeader(MemoryStream stream)
+ {
+ using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
+ {
+ headerWriter.Write((string)HeaderMagic); // Header.Magic
+
+ headerWriter.Write((int)InternalVersion); // Header.CacheFileVersion
+ headerWriter.Write((ulong)GetFeatureInfo()); // Header.FeatureInfo
+
+ headerWriter.Write((int)_infosStream.Length); // Header.InfosLen
+ headerWriter.Write((int)_codesStream.Length); // Header.CodesLen
+ headerWriter.Write((int)_relocsStream.Length); // Header.RelocsLen
+ headerWriter.Write((int)_unwindInfosStream.Length); // Header.UnwindInfosLen
+ }
+ }
+
+ internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IntPtr pageTablePointer, JumpTable jumpTable)
+ {
+ if ((int)_infosStream.Length == 0 ||
+ (int)_codesStream.Length == 0 ||
+ (int)_relocsStream.Length == 0 ||
+ (int)_unwindInfosStream.Length == 0)
+ {
+ return;
+ }
+
+ Debug.Assert(funcs.Count == 0);
+
+ _infosStream.Seek(0L, SeekOrigin.Begin);
+ _codesStream.Seek(0L, SeekOrigin.Begin);
+ _relocsStream.Seek(0L, SeekOrigin.Begin);
+ _unwindInfosStream.Seek(0L, SeekOrigin.Begin);
+
+ using (BinaryReader infosReader = new BinaryReader(_infosStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader codesReader = new BinaryReader(_codesStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader relocsReader = new BinaryReader(_relocsStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader unwindInfosReader = new BinaryReader(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
+ {
+ int infosEntriesCount = (int)_infosStream.Length / InfoEntry.Stride;
+
+ for (int i = 0; i < infosEntriesCount; i++)
+ {
+ InfoEntry infoEntry = ReadInfo(infosReader);
+
+ byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
+
+ if (infoEntry.RelocEntriesCount != 0)
+ {
+ RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
+
+ PatchCode(code, relocEntries, pageTablePointer, jumpTable);
+ }
+
+ UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
+
+ TranslatedFunction func = FastTranslate(code, unwindInfo, infoEntry.HighCq);
+
+ funcs.AddOrUpdate((ulong)infoEntry.Address, func, (key, oldFunc) => func.HighCq && !oldFunc.HighCq ? func : oldFunc);
+ }
+ }
+
+ if (_infosStream.Position < _infosStream.Length ||
+ _codesStream.Position < _codesStream.Length ||
+ _relocsStream.Position < _relocsStream.Length ||
+ _unwindInfosStream.Position < _unwindInfosStream.Length)
+ {
+ throw new Exception("Could not reach the end of one or more memory streams.");
+ }
+
+ jumpTable.Initialize(PtcJumpTable, funcs);
+
+ PtcJumpTable.WriteJumpTable(jumpTable, funcs);
+ PtcJumpTable.WriteDynamicTable(jumpTable);
+ }
+
+ private static InfoEntry ReadInfo(BinaryReader infosReader)
+ {
+ InfoEntry infoEntry = new InfoEntry();
+
+ infoEntry.Address = infosReader.ReadInt64();
+ infoEntry.HighCq = infosReader.ReadBoolean();
+ infoEntry.CodeLen = infosReader.ReadInt32();
+ infoEntry.RelocEntriesCount = infosReader.ReadInt32();
+
+ return infoEntry;
+ }
+
+ private static byte[] ReadCode(BinaryReader codesReader, int codeLen)
+ {
+ byte[] codeBuf = new byte[codeLen];
+
+ codesReader.Read(codeBuf, 0, codeLen);
+
+ return codeBuf;
+ }
+
+ private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount)
+ {
+ RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount];
+
+ for (int i = 0; i < relocEntriesCount; i++)
+ {
+ int position = relocsReader.ReadInt32();
+ int index = relocsReader.ReadInt32();
+
+ relocEntries[i] = new RelocEntry(position, index);
+ }
+
+ return relocEntries;
+ }
+
+ private static void PatchCode(Span<byte> code, RelocEntry[] relocEntries, IntPtr pageTablePointer, JumpTable jumpTable)
+ {
+ foreach (RelocEntry relocEntry in relocEntries)
+ {
+ ulong imm;
+
+ if (relocEntry.Index == PageTablePointerIndex)
+ {
+ imm = (ulong)pageTablePointer.ToInt64();
+ }
+ else if (relocEntry.Index == JumpPointerIndex)
+ {
+ imm = (ulong)jumpTable.JumpPointer.ToInt64();
+ }
+ else if (relocEntry.Index == DynamicPointerIndex)
+ {
+ imm = (ulong)jumpTable.DynamicPointer.ToInt64();
+ }
+ else if (Delegates.TryGetDelegateFuncPtrByIndex(relocEntry.Index, out IntPtr funcPtr))
+ {
+ imm = (ulong)funcPtr.ToInt64();
+ }
+ else
+ {
+ throw new Exception($"Unexpected reloc entry {relocEntry}.");
+ }
+
+ BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), imm);
+ }
+ }
+
+ private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader)
+ {
+ int pushEntriesLength = unwindInfosReader.ReadInt32();
+
+ UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength];
+
+ for (int i = 0; i < pushEntriesLength; i++)
+ {
+ int pseudoOp = unwindInfosReader.ReadInt32();
+ int prologOffset = unwindInfosReader.ReadInt32();
+ int regIndex = unwindInfosReader.ReadInt32();
+ int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32();
+
+ pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize);
+ }
+
+ int prologueSize = unwindInfosReader.ReadInt32();
+
+ return new UnwindInfo(pushEntries, prologueSize);
+ }
+
+ private static TranslatedFunction FastTranslate(byte[] code, UnwindInfo unwindInfo, bool highCq)
+ {
+ CompiledFunction cFunc = new CompiledFunction(code, unwindInfo);
+
+ IntPtr codePtr = JitCache.Map(cFunc);
+
+ GuestFunction gFunc = Marshal.GetDelegateForFunctionPointer<GuestFunction>(codePtr);
+
+ TranslatedFunction tFunc = new TranslatedFunction(gFunc, highCq);
+
+ return tFunc;
+ }
+
+ internal static void MakeAndSaveTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
+ {
+ if (PtcProfiler.ProfiledFuncs.Count == 0)
+ {
+ return;
+ }
+
+ _translateCount = 0;
+ _rejitCount = 0;
+
+ ThreadPool.QueueUserWorkItem(TranslationLogger, (funcs.Count, PtcProfiler.ProfiledFuncs.Count));
+
+ int maxDegreeOfParallelism = (Environment.ProcessorCount * 3) / 4;
+
+ Parallel.ForEach(PtcProfiler.ProfiledFuncs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (item, state) =>
+ {
+ ulong address = item.Key;
+
+ Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
+
+ if (!funcs.ContainsKey(address))
+ {
+ TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, item.Value.highCq);
+
+ funcs.TryAdd(address, func);
+
+ if (func.HighCq)
+ {
+ jumpTable.RegisterFunction(address, func);
+ }
+
+ Interlocked.Increment(ref _translateCount);
+ }
+ else if (item.Value.highCq && !funcs[address].HighCq)
+ {
+ TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, highCq: true);
+
+ funcs[address] = func;
+
+ jumpTable.RegisterFunction(address, func);
+
+ Interlocked.Increment(ref _rejitCount);
+ }
+
+ if (State != PtcState.Enabled)
+ {
+ state.Stop();
+ }
+ });
+
+ _loggerEvent.Set();
+
+ if (_translateCount != 0 || _rejitCount != 0)
+ {
+ PtcJumpTable.Initialize(jumpTable);
+
+ PtcJumpTable.ReadJumpTable(jumpTable);
+ PtcJumpTable.ReadDynamicTable(jumpTable);
+
+ ThreadPool.QueueUserWorkItem(PreSave);
+ }
+ }
+
+ private static void TranslationLogger(object state)
+ {
+ const int refreshRate = 1; // Seconds.
+
+ (int funcsCount, int ProfiledFuncsCount) = ((int, int))state;
+
+ do
+ {
+ Logger.PrintInfo(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+ }
+ while (!_loggerEvent.WaitOne(refreshRate * 1000));
+
+ Logger.PrintInfo(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+ }
+
+ internal static void WriteInfoCodeReloc(long address, bool highCq, PtcInfo ptcInfo)
+ {
+ lock (_lock)
+ {
+ // WriteInfo.
+ _infosWriter.Write((long)address); // InfoEntry.Address
+ _infosWriter.Write((bool)highCq); // InfoEntry.HighCq
+ _infosWriter.Write((int)ptcInfo.CodeStream.Length); // InfoEntry.CodeLen
+ _infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount
+
+ // WriteCode.
+ ptcInfo.CodeStream.WriteTo(_codesStream);
+
+ // WriteReloc.
+ ptcInfo.RelocStream.WriteTo(_relocsStream);
+
+ // WriteUnwindInfo.
+ ptcInfo.UnwindInfoStream.WriteTo(_unwindInfosStream);
+ }
+ }
+
+ private static ulong GetFeatureInfo()
+ {
+ ulong featureInfo = 0ul;
+
+ featureInfo |= (Sse3.IsSupported ? 1ul : 0ul) << 0;
+ featureInfo |= (Pclmulqdq.IsSupported ? 1ul : 0ul) << 1;
+ featureInfo |= (Ssse3.IsSupported ? 1ul : 0ul) << 9;
+ featureInfo |= (Fma.IsSupported ? 1ul : 0ul) << 12;
+ featureInfo |= (Sse41.IsSupported ? 1ul : 0ul) << 19;
+ featureInfo |= (Sse42.IsSupported ? 1ul : 0ul) << 20;
+ featureInfo |= (Popcnt.IsSupported ? 1ul : 0ul) << 23;
+ featureInfo |= (Aes.IsSupported ? 1ul : 0ul) << 25;
+ featureInfo |= (Avx.IsSupported ? 1ul : 0ul) << 28;
+ featureInfo |= (Sse.IsSupported ? 1ul : 0ul) << 57;
+ featureInfo |= (Sse2.IsSupported ? 1ul : 0ul) << 58;
+
+ return featureInfo;
+ }
+
+ private struct Header
+ {
+ public string Magic;
+
+ public int CacheFileVersion;
+ public ulong FeatureInfo;
+
+ public int InfosLen;
+ public int CodesLen;
+ public int RelocsLen;
+ public int UnwindInfosLen;
+ }
+
+ private struct InfoEntry
+ {
+ public const int Stride = 17; // Bytes.
+
+ public long Address;
+ public bool HighCq;
+ public int CodeLen;
+ public int RelocEntriesCount;
+ }
+
+ private static void Enable()
+ {
+ State = PtcState.Enabled;
+ }
+
+ public static void Continue()
+ {
+ if (State == PtcState.Enabled)
+ {
+ State = PtcState.Continuing;
+ }
+ }
+
+ public static void Close()
+ {
+ if (State == PtcState.Enabled ||
+ State == PtcState.Continuing)
+ {
+ State = PtcState.Closing;
+ }
+ }
+
+ internal static void Disable()
+ {
+ State = PtcState.Disabled;
+ }
+
+ private static void Wait()
+ {
+ _waitEvent.WaitOne();
+ }
+
+ public static void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
+ AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit;
+
+ Wait();
+ _waitEvent.Dispose();
+
+ _infosWriter.Dispose();
+
+ _infosStream.Dispose();
+ _codesStream.Dispose();
+ _relocsStream.Dispose();
+ _unwindInfosStream.Dispose();
+ }
+ }
+
+ private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ Close();
+ PtcProfiler.Stop();
+
+ if (e.IsTerminating)
+ {
+ Dispose();
+ PtcProfiler.Dispose();
+ }
+ }
+
+ private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
+ {
+ Dispose();
+ PtcProfiler.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs
new file mode 100644
index 00000000..f03eb6ba
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcInfo.cs
@@ -0,0 +1,68 @@
+using ARMeilleure.CodeGen.Unwinding;
+using System;
+using System.IO;
+
+namespace ARMeilleure.Translation.PTC
+{
+ sealed class PtcInfo : IDisposable
+ {
+ private readonly BinaryWriter _relocWriter;
+ private readonly BinaryWriter _unwindInfoWriter;
+
+ public MemoryStream CodeStream { get; }
+ public MemoryStream RelocStream { get; }
+ public MemoryStream UnwindInfoStream { get; }
+
+ public int RelocEntriesCount { get; private set; }
+
+ public PtcInfo()
+ {
+ CodeStream = new MemoryStream();
+ RelocStream = new MemoryStream();
+ UnwindInfoStream = new MemoryStream();
+
+ _relocWriter = new BinaryWriter(RelocStream, EncodingCache.UTF8NoBOM, true);
+ _unwindInfoWriter = new BinaryWriter(UnwindInfoStream, EncodingCache.UTF8NoBOM, true);
+
+ RelocEntriesCount = 0;
+ }
+
+ public void WriteCode(MemoryStream codeStream)
+ {
+ codeStream.WriteTo(CodeStream);
+ }
+
+ public void WriteRelocEntry(RelocEntry relocEntry)
+ {
+ _relocWriter.Write((int)relocEntry.Position);
+ _relocWriter.Write((int)relocEntry.Index);
+
+ RelocEntriesCount++;
+ }
+
+ public void WriteUnwindInfo(UnwindInfo unwindInfo)
+ {
+ _unwindInfoWriter.Write((int)unwindInfo.PushEntries.Length);
+
+ foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries)
+ {
+ _unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp);
+ _unwindInfoWriter.Write((int)unwindPushEntry.PrologOffset);
+ _unwindInfoWriter.Write((int)unwindPushEntry.RegIndex);
+ _unwindInfoWriter.Write((int)unwindPushEntry.StackOffsetOrAllocSize);
+ }
+
+ _unwindInfoWriter.Write((int)unwindInfo.PrologSize);
+ }
+
+ public void Dispose()
+ {
+ _relocWriter.Dispose();
+ _unwindInfoWriter.Dispose();
+
+ CodeStream.Dispose();
+ RelocStream.Dispose();
+ UnwindInfoStream.Dispose();
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/PTC/PtcJumpTable.cs b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
new file mode 100644
index 00000000..0a3ae240
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Translation.PTC
+{
+ [Serializable]
+ class PtcJumpTable
+ {
+ private readonly List<KeyValuePair<long, DirectHostAddress>> _jumpTable;
+ private readonly List<KeyValuePair<long, IndirectHostAddress>> _dynamicTable;
+
+ private readonly List<ulong> _targets;
+ private readonly Dictionary<ulong, LinkedList<int>> _dependants;
+
+ public int TableEnd => _jumpTable.Count;
+ public int DynTableEnd => _dynamicTable.Count;
+
+ public List<ulong> Targets => _targets;
+ public Dictionary<ulong, LinkedList<int>> Dependants => _dependants;
+
+ public PtcJumpTable()
+ {
+ _jumpTable = new List<KeyValuePair<long, DirectHostAddress>>();
+ _dynamicTable = new List<KeyValuePair<long, IndirectHostAddress>>();
+
+ _targets = new List<ulong>();
+ _dependants = new Dictionary<ulong, LinkedList<int>>();
+ }
+
+ public void Initialize(JumpTable jumpTable)
+ {
+ _targets.Clear();
+
+ foreach (ulong guestAddress in jumpTable.Targets.Keys)
+ {
+ _targets.Add(guestAddress);
+ }
+
+ _dependants.Clear();
+
+ foreach (var item in jumpTable.Dependants)
+ {
+ _dependants.Add(item.Key, new LinkedList<int>(item.Value));
+ }
+ }
+
+ public void Clear()
+ {
+ _jumpTable.Clear();
+ _dynamicTable.Clear();
+
+ _targets.Clear();
+ _dependants.Clear();
+ }
+
+ public void WriteJumpTable(JumpTable jumpTable, ConcurrentDictionary<ulong, TranslatedFunction> funcs)
+ {
+ jumpTable.ExpandIfNeededJumpTable(TableEnd);
+
+ int entry = 0;
+
+ foreach (var item in _jumpTable)
+ {
+ entry += 1;
+
+ long guestAddress = item.Key;
+ DirectHostAddress directHostAddress = item.Value;
+
+ long hostAddress;
+
+ if (directHostAddress == DirectHostAddress.CallStub)
+ {
+ hostAddress = DirectCallStubs.DirectCallStub(false).ToInt64();
+ }
+ else if (directHostAddress == DirectHostAddress.TailCallStub)
+ {
+ hostAddress = DirectCallStubs.DirectCallStub(true).ToInt64();
+ }
+ else if (directHostAddress == DirectHostAddress.Host)
+ {
+ if (funcs.TryGetValue((ulong)guestAddress, out TranslatedFunction func))
+ {
+ hostAddress = func.FuncPtr.ToInt64();
+ }
+ else
+ {
+ throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})");
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException(nameof(directHostAddress));
+ }
+
+ IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry);
+
+ Marshal.WriteInt64(addr, 0, guestAddress);
+ Marshal.WriteInt64(addr, 8, hostAddress);
+ }
+ }
+
+ public void WriteDynamicTable(JumpTable jumpTable)
+ {
+ if (JumpTable.DynamicTableElems > 1)
+ {
+ throw new NotSupportedException();
+ }
+
+ jumpTable.ExpandIfNeededDynamicTable(DynTableEnd);
+
+ int entry = 0;
+
+ foreach (var item in _dynamicTable)
+ {
+ entry += 1;
+
+ long guestAddress = item.Key;
+ IndirectHostAddress indirectHostAddress = item.Value;
+
+ long hostAddress;
+
+ if (indirectHostAddress == IndirectHostAddress.CallStub)
+ {
+ hostAddress = DirectCallStubs.IndirectCallStub(false).ToInt64();
+ }
+ else if (indirectHostAddress == IndirectHostAddress.TailCallStub)
+ {
+ hostAddress = DirectCallStubs.IndirectCallStub(true).ToInt64();
+ }
+ else
+ {
+ throw new InvalidOperationException(nameof(indirectHostAddress));
+ }
+
+ IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry);
+
+ Marshal.WriteInt64(addr, 0, guestAddress);
+ Marshal.WriteInt64(addr, 8, hostAddress);
+ }
+ }
+
+ public void ReadJumpTable(JumpTable jumpTable)
+ {
+ _jumpTable.Clear();
+
+ for (int entry = 1; entry <= jumpTable.TableEnd; entry++)
+ {
+ IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry);
+
+ long guestAddress = Marshal.ReadInt64(addr, 0);
+ long hostAddress = Marshal.ReadInt64(addr, 8);
+
+ DirectHostAddress directHostAddress;
+
+ if (hostAddress == DirectCallStubs.DirectCallStub(false).ToInt64())
+ {
+ directHostAddress = DirectHostAddress.CallStub;
+ }
+ else if (hostAddress == DirectCallStubs.DirectCallStub(true).ToInt64())
+ {
+ directHostAddress = DirectHostAddress.TailCallStub;
+ }
+ else
+ {
+ directHostAddress = DirectHostAddress.Host;
+ }
+
+ _jumpTable.Add(new KeyValuePair<long, DirectHostAddress>(guestAddress, directHostAddress));
+ }
+ }
+
+ public void ReadDynamicTable(JumpTable jumpTable)
+ {
+ if (JumpTable.DynamicTableElems > 1)
+ {
+ throw new NotSupportedException();
+ }
+
+ _dynamicTable.Clear();
+
+ for (int entry = 1; entry <= jumpTable.DynTableEnd; entry++)
+ {
+ IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry);
+
+ long guestAddress = Marshal.ReadInt64(addr, 0);
+ long hostAddress = Marshal.ReadInt64(addr, 8);
+
+ IndirectHostAddress indirectHostAddress;
+
+ if (hostAddress == DirectCallStubs.IndirectCallStub(false).ToInt64())
+ {
+ indirectHostAddress = IndirectHostAddress.CallStub;
+ }
+ else if (hostAddress == DirectCallStubs.IndirectCallStub(true).ToInt64())
+ {
+ indirectHostAddress = IndirectHostAddress.TailCallStub;
+ }
+ else
+ {
+ throw new InvalidOperationException($"({nameof(hostAddress)} = 0x{hostAddress:X16})");
+ }
+
+ _dynamicTable.Add(new KeyValuePair<long, IndirectHostAddress>(guestAddress, indirectHostAddress));
+ }
+ }
+
+ private enum DirectHostAddress
+ {
+ CallStub,
+ TailCallStub,
+ Host
+ }
+
+ private enum IndirectHostAddress
+ {
+ CallStub,
+ TailCallStub
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs
new file mode 100644
index 00000000..dcc31275
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -0,0 +1,267 @@
+using ARMeilleure.State;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Threading;
+
+namespace ARMeilleure.Translation.PTC
+{
+ public static class PtcProfiler
+ {
+ private const int SaveInterval = 30; // Seconds.
+
+ private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
+
+ private static readonly BinaryFormatter _binaryFormatter;
+
+ private static readonly System.Timers.Timer _timer;
+
+ private static readonly ManualResetEvent _waitEvent;
+
+ private static readonly object _lock;
+
+ private static bool _disposed;
+
+ internal static Dictionary<ulong, (ExecutionMode mode, bool highCq)> ProfiledFuncs { get; private set; } //! Not to be modified.
+
+ internal static bool Enabled { get; private set; }
+
+ public static ulong StaticCodeStart { internal get; set; }
+ public static int StaticCodeSize { internal get; set; }
+
+ static PtcProfiler()
+ {
+ _binaryFormatter = new BinaryFormatter();
+
+ _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
+ _timer.Elapsed += PreSave;
+
+ _waitEvent = new ManualResetEvent(true);
+
+ _lock = new object();
+
+ _disposed = false;
+
+ ProfiledFuncs = new Dictionary<ulong, (ExecutionMode, bool)>();
+
+ Enabled = false;
+ }
+
+ internal static void AddEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ lock (_lock)
+ {
+ Debug.Assert(!highCq && !ProfiledFuncs.ContainsKey(address));
+
+ ProfiledFuncs.TryAdd(address, (mode, highCq));
+ }
+ }
+ }
+
+ internal static void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ lock (_lock)
+ {
+ Debug.Assert(highCq && ProfiledFuncs.ContainsKey(address));
+
+ ProfiledFuncs[address] = (mode, highCq);
+ }
+ }
+ }
+
+ internal static bool IsAddressInStaticCodeRange(ulong address)
+ {
+ return address >= StaticCodeStart && address < StaticCodeStart + (ulong)StaticCodeSize;
+ }
+
+ internal static void ClearEntries()
+ {
+ ProfiledFuncs.Clear();
+ }
+
+ internal static void PreLoad()
+ {
+ string fileNameActual = String.Concat(Ptc.CachePathActual, ".info");
+ string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+ FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ if (!Load(fileNameActual))
+ {
+ if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+ }
+ else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+
+ private static bool Load(string fileName)
+ {
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
+ using (MemoryStream stream = new MemoryStream())
+ using (MD5 md5 = MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ deflateStream.CopyTo(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ byte[] currentHash = new byte[hashSize];
+ stream.Read(currentHash, 0, hashSize);
+
+ byte[] expectedHash = md5.ComputeHash(stream);
+
+ if (!CompareHash(currentHash, expectedHash))
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ try
+ {
+ ProfiledFuncs = (Dictionary<ulong, (ExecutionMode, bool)>)_binaryFormatter.Deserialize(stream);
+ }
+ catch
+ {
+ ProfiledFuncs = new Dictionary<ulong, (ExecutionMode, bool)>();
+
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
+ {
+ return currentHash.SequenceEqual(expectedHash);
+ }
+
+ private static void InvalidateCompressedStream(FileStream compressedStream)
+ {
+ compressedStream.SetLength(0L);
+ }
+
+ private static void PreSave(object source, System.Timers.ElapsedEventArgs e)
+ {
+ _waitEvent.Reset();
+
+ string fileNameActual = String.Concat(Ptc.CachePathActual, ".info");
+ string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ File.Copy(fileNameActual, fileNameBackup, true);
+ }
+
+ Save(fileNameActual);
+
+ _waitEvent.Set();
+ }
+
+ private static void Save(string fileName)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ using (MD5 md5 = MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ lock (_lock)
+ {
+ _binaryFormatter.Serialize(stream, ProfiledFuncs);
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+ byte[] hash = md5.ComputeHash(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+ stream.Write(hash, 0, hashSize);
+
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
+ {
+ try
+ {
+ stream.WriteTo(deflateStream);
+ }
+ catch
+ {
+ compressedStream.Position = 0L;
+ }
+
+ if (compressedStream.Position < compressedStream.Length)
+ {
+ compressedStream.SetLength(compressedStream.Position);
+ }
+ }
+ }
+ }
+
+ internal static void Start()
+ {
+ if (Ptc.State == PtcState.Enabled ||
+ Ptc.State == PtcState.Continuing)
+ {
+ Enabled = true;
+
+ _timer.Enabled = true;
+ }
+ }
+
+ public static void Stop()
+ {
+ Enabled = false;
+
+ if (!_disposed)
+ {
+ _timer.Enabled = false;
+ }
+ }
+
+ internal static void Wait()
+ {
+ _waitEvent.WaitOne();
+ }
+
+ public static void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _timer.Elapsed -= PreSave;
+ _timer.Dispose();
+
+ Wait();
+ _waitEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcState.cs b/ARMeilleure/Translation/PTC/PtcState.cs
new file mode 100644
index 00000000..ca4f4108
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcState.cs
@@ -0,0 +1,10 @@
+namespace ARMeilleure.Translation.PTC
+{
+ enum PtcState
+ {
+ Enabled,
+ Continuing,
+ Closing,
+ Disabled
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/RelocEntry.cs b/ARMeilleure/Translation/PTC/RelocEntry.cs
new file mode 100644
index 00000000..3d729fbb
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/RelocEntry.cs
@@ -0,0 +1,19 @@
+namespace ARMeilleure.Translation.PTC
+{
+ struct RelocEntry
+ {
+ public int Position;
+ public int Index;
+
+ public RelocEntry(int position, int index)
+ {
+ Position = position;
+ Index = index;
+ }
+
+ public override string ToString()
+ {
+ return $"({nameof(Position)} = {Position}, {nameof(Index)} = {Index})";
+ }
+ }
+}