PPTC Follow-up. (#1712)

* Added support for offline invalidation, via PPTC, of low cq translations replaced by high cq translations; both on a single run and between runs.

Added invalidation of .cache files in the event of reuse on a different user operating system.

Added .info and .cache files invalidation in case of a failed stream decompression.

Nits.

* InternalVersion = 1712;

* Nits.

* Address comment.

* Get rid of BinaryFormatter.

Nits.

* Move Ptc.LoadTranslations().

Nits.

* Nits.

* Fixed corner cases (in case backup copies have to be used). Added save logs.

* Not core fixes.

* Complement to the previous commit. Added load logs. Removed BinaryFormatter leftovers.

* Add LoadTranslations log.

* Nits.

* Removed the search and management of LowCq overlapping functions.

* Final increment of .info and .cache flags.

* Nit.

* GetIndirectFunctionAddress(): Validate that writing actually takes place in dynamic table memory range (and not elsewhere).

* Fix Ptc.UpdateInfo() due to rebase.

* Nit for retrigger Checks.

* Nit for retrigger Checks.
This commit is contained in:
LDj3SNuD 2020-12-17 20:32:09 +01:00 committed by GitHub
parent 10aa11ce13
commit b5c215111d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 670 additions and 169 deletions

View file

@ -12,7 +12,7 @@ using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
@ -22,7 +22,7 @@ namespace ARMeilleure.Translation.PTC
{
private const string HeaderMagic = "PTChd";
private const int InternalVersion = 1519; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 1713; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -34,6 +34,7 @@ namespace ARMeilleure.Translation.PTC
internal const int JumpPointerIndex = -2; // Must be a negative value.
internal const int DynamicPointerIndex = -3; // Must be a negative value.
private const byte FillingByte = 0x00;
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
private static readonly MemoryStream _infosStream;
@ -43,8 +44,6 @@ namespace ARMeilleure.Translation.PTC
private static readonly BinaryWriter _infosWriter;
private static readonly BinaryFormatter _binaryFormatter;
private static readonly ManualResetEvent _waitEvent;
private static readonly AutoResetEvent _loggerEvent;
@ -54,7 +53,6 @@ namespace ARMeilleure.Translation.PTC
private static bool _disposed;
private static volatile int _translateCount;
private static volatile int _rejitCount;
internal static PtcJumpTable PtcJumpTable { get; private set; }
@ -75,8 +73,6 @@ namespace ARMeilleure.Translation.PTC
_infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true);
_binaryFormatter = new BinaryFormatter();
_waitEvent = new ManualResetEvent(true);
_loggerEvent = new AutoResetEvent(false);
@ -174,26 +170,26 @@ namespace ARMeilleure.Translation.PTC
if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
{
if (!Load(fileNameActual))
if (!Load(fileNameActual, false))
{
if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
{
Load(fileNameBackup);
Load(fileNameBackup, true);
}
}
}
else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
{
Load(fileNameBackup);
Load(fileNameBackup, true);
}
}
private static bool Load(string fileName)
private static bool Load(string fileName, bool isBackup)
{
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())
using (MD5 md5 = MD5.Create())
{
int hashSize = md5.HashSize / 8;
@ -247,6 +243,13 @@ namespace ARMeilleure.Translation.PTC
return false;
}
if (header.OSPlatform != GetOSPlatform())
{
InvalidateCompressedStream(compressedStream);
return false;
}
if (header.InfosLen % InfoEntry.Stride != 0)
{
InvalidateCompressedStream(compressedStream);
@ -266,7 +269,7 @@ namespace ARMeilleure.Translation.PTC
try
{
PtcJumpTable = (PtcJumpTable)_binaryFormatter.Deserialize(stream);
PtcJumpTable = PtcJumpTable.Deserialize(stream);
}
catch
{
@ -281,9 +284,13 @@ namespace ARMeilleure.Translation.PTC
_codesStream.Write(codesBuf, 0, header.CodesLen);
_relocsStream.Write(relocsBuf, 0, header.RelocsLen);
_unwindInfosStream.Write(unwindInfosBuf, 0, header.UnwindInfosLen);
return true;
}
long fileSize = new FileInfo(fileName).Length;
Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetInfosEntriesCount()}).");
return true;
}
private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
@ -299,8 +306,9 @@ namespace ARMeilleure.Translation.PTC
header.Magic = headerReader.ReadString();
header.CacheFileVersion = headerReader.ReadInt32();
header.CacheFileVersion = headerReader.ReadUInt32();
header.FeatureInfo = headerReader.ReadUInt64();
header.OSPlatform = headerReader.ReadUInt32();
header.InfosLen = headerReader.ReadInt32();
header.CodesLen = headerReader.ReadInt32();
@ -338,7 +346,7 @@ namespace ARMeilleure.Translation.PTC
private static void Save(string fileName)
{
using (MemoryStream stream = new MemoryStream())
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
using (MD5 md5 = MD5.Create())
{
int hashSize = md5.HashSize / 8;
@ -351,7 +359,7 @@ namespace ARMeilleure.Translation.PTC
_relocsStream.WriteTo(stream);
_unwindInfosStream.WriteTo(stream);
_binaryFormatter.Serialize(stream, PtcJumpTable);
PtcJumpTable.Serialize(stream, PtcJumpTable);
stream.Seek((long)hashSize, SeekOrigin.Begin);
byte[] hash = md5.ComputeHash(stream);
@ -377,6 +385,10 @@ namespace ARMeilleure.Translation.PTC
}
}
}
long fileSize = new FileInfo(fileName).Length;
Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {GetInfosEntriesCount()}).");
}
private static void WriteHeader(MemoryStream stream)
@ -385,8 +397,9 @@ namespace ARMeilleure.Translation.PTC
{
headerWriter.Write((string)HeaderMagic); // Header.Magic
headerWriter.Write((int)InternalVersion); // Header.CacheFileVersion
headerWriter.Write((uint)InternalVersion); // Header.CacheFileVersion
headerWriter.Write((ulong)GetFeatureInfo()); // Header.FeatureInfo
headerWriter.Write((uint)GetOSPlatform()); // Header.OSPlatform
headerWriter.Write((int)_infosStream.Length); // Header.InfosLen
headerWriter.Write((int)_codesStream.Length); // Header.CodesLen
@ -395,7 +408,7 @@ namespace ARMeilleure.Translation.PTC
}
}
internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IntPtr pageTablePointer, JumpTable jumpTable)
internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
{
if ((int)_infosStream.Length == 0 ||
(int)_codesStream.Length == 0 ||
@ -417,26 +430,44 @@ namespace ARMeilleure.Translation.PTC
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++)
for (int i = 0; i < GetInfosEntriesCount(); i++)
{
InfoEntry infoEntry = ReadInfo(infosReader);
byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
if (infoEntry.RelocEntriesCount != 0)
if (infoEntry.Stubbed)
{
RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
PatchCode(code, relocEntries, pageTablePointer, jumpTable);
SkipCode(infoEntry.CodeLen);
SkipReloc(infoEntry.RelocEntriesCount);
SkipUnwindInfo(unwindInfosReader);
}
else if (infoEntry.HighCq || !PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) || !value.highCq)
{
byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
if (infoEntry.RelocEntriesCount != 0)
{
RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
TranslatedFunction func = FastTranslate(code, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq);
PatchCode(code, relocEntries, memory.PageTablePointer, jumpTable);
}
funcs.AddOrUpdate(infoEntry.Address, func, (key, oldFunc) => func.HighCq && !oldFunc.HighCq ? func : oldFunc);
UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
TranslatedFunction func = FastTranslate(code, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq);
bool isAddressUnique = funcs.TryAdd(infoEntry.Address, func);
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
}
else
{
infoEntry.Stubbed = true;
UpdateInfo(infoEntry);
StubCode(infoEntry.CodeLen);
StubReloc(infoEntry.RelocEntriesCount);
StubUnwindInfo(unwindInfosReader);
}
}
}
@ -452,6 +483,13 @@ namespace ARMeilleure.Translation.PTC
PtcJumpTable.WriteJumpTable(jumpTable, funcs);
PtcJumpTable.WriteDynamicTable(jumpTable);
Logger.Info?.Print(LogClass.Ptc, $"{funcs.Count} translated functions loaded");
}
private static int GetInfosEntriesCount()
{
return (int)_infosStream.Length / InfoEntry.Stride;
}
private static InfoEntry ReadInfo(BinaryReader infosReader)
@ -461,12 +499,30 @@ namespace ARMeilleure.Translation.PTC
infoEntry.Address = infosReader.ReadUInt64();
infoEntry.GuestSize = infosReader.ReadUInt64();
infoEntry.HighCq = infosReader.ReadBoolean();
infoEntry.Stubbed = infosReader.ReadBoolean();
infoEntry.CodeLen = infosReader.ReadInt32();
infoEntry.RelocEntriesCount = infosReader.ReadInt32();
return infoEntry;
}
private static void SkipCode(int codeLen)
{
_codesStream.Seek(codeLen, SeekOrigin.Current);
}
private static void SkipReloc(int relocEntriesCount)
{
_relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current);
}
private static void SkipUnwindInfo(BinaryReader unwindInfosReader)
{
int pushEntriesLength = unwindInfosReader.ReadInt32();
_unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current);
}
private static byte[] ReadCode(BinaryReader codesReader, int codeLen)
{
byte[] codeBuf = new byte[codeLen];
@ -556,50 +612,79 @@ namespace ARMeilleure.Translation.PTC
return tFunc;
}
private static void UpdateInfo(InfoEntry infoEntry)
{
_infosStream.Seek(-InfoEntry.Stride, SeekOrigin.Current);
// WriteInfo.
_infosWriter.Write((ulong)infoEntry.Address);
_infosWriter.Write((ulong)infoEntry.GuestSize);
_infosWriter.Write((bool)infoEntry.HighCq);
_infosWriter.Write((bool)infoEntry.Stubbed);
_infosWriter.Write((int)infoEntry.CodeLen);
_infosWriter.Write((int)infoEntry.RelocEntriesCount);
}
private static void StubCode(int codeLen)
{
for (int i = 0; i < codeLen; i++)
{
_codesStream.WriteByte(FillingByte);
}
}
private static void StubReloc(int relocEntriesCount)
{
for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++)
{
_relocsStream.WriteByte(FillingByte);
}
}
private static void StubUnwindInfo(BinaryReader unwindInfosReader)
{
int pushEntriesLength = unwindInfosReader.ReadInt32();
for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++)
{
_unwindInfosStream.WriteByte(FillingByte);
}
}
internal static void MakeAndSaveTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
{
if (PtcProfiler.ProfiledFuncs.Count == 0)
var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(funcs);
if (profiledFuncsToTranslate.Count == 0)
{
return;
}
_translateCount = 0;
_rejitCount = 0;
ThreadPool.QueueUserWorkItem(TranslationLogger, (funcs.Count, PtcProfiler.ProfiledFuncs.Count));
ThreadPool.QueueUserWorkItem(TranslationLogger, profiledFuncsToTranslate.Count);
int maxDegreeOfParallelism = (Environment.ProcessorCount * 3) / 4;
Parallel.ForEach(PtcProfiler.ProfiledFuncs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (item, state) =>
Parallel.ForEach(profiledFuncsToTranslate, 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);
bool isAddressUnique = funcs.TryAdd(address, func);
Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");
if (func.HighCq)
{
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);
}
Interlocked.Increment(ref _translateCount);
if (State != PtcState.Enabled)
{
state.Stop();
@ -608,30 +693,27 @@ namespace ARMeilleure.Translation.PTC
_loggerEvent.Set();
if (_translateCount != 0 || _rejitCount != 0)
{
PtcJumpTable.Initialize(jumpTable);
PtcJumpTable.Initialize(jumpTable);
PtcJumpTable.ReadJumpTable(jumpTable);
PtcJumpTable.ReadDynamicTable(jumpTable);
PtcJumpTable.ReadJumpTable(jumpTable);
PtcJumpTable.ReadDynamicTable(jumpTable);
ThreadPool.QueueUserWorkItem(PreSave);
}
ThreadPool.QueueUserWorkItem(PreSave);
}
private static void TranslationLogger(object state)
{
const int refreshRate = 1; // Seconds.
(int funcsCount, int ProfiledFuncsCount) = ((int, int))state;
int profiledFuncsToTranslateCount = (int)state;
do
{
Logger.Info?.Print(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
}
while (!_loggerEvent.WaitOne(refreshRate * 1000));
Logger.Info?.Print(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
}
internal static void WriteInfoCodeReloc(ulong address, ulong guestSize, bool highCq, PtcInfo ptcInfo)
@ -642,6 +724,7 @@ namespace ARMeilleure.Translation.PTC
_infosWriter.Write((ulong)address); // InfoEntry.Address
_infosWriter.Write((ulong)guestSize); // InfoEntry.GuestSize
_infosWriter.Write((bool)highCq); // InfoEntry.HighCq
_infosWriter.Write((bool)false); // InfoEntry.Stubbed
_infosWriter.Write((int)ptcInfo.CodeStream.Length); // InfoEntry.CodeLen
_infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount
@ -661,12 +744,25 @@ namespace ARMeilleure.Translation.PTC
return (ulong)HardwareCapabilities.FeatureInfoEdx << 32 | (uint)HardwareCapabilities.FeatureInfoEcx;
}
private static uint GetOSPlatform()
{
uint osPlatform = 0u;
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ? 1u : 0u) << 0;
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 1u : 0u) << 1;
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1u : 0u) << 2;
osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1u : 0u) << 3;
return osPlatform;
}
private struct Header
{
public string Magic;
public int CacheFileVersion;
public uint CacheFileVersion;
public ulong FeatureInfo;
public uint OSPlatform;
public int InfosLen;
public int CodesLen;
@ -676,11 +772,12 @@ namespace ARMeilleure.Translation.PTC
private struct InfoEntry
{
public const int Stride = 25; // Bytes.
public const int Stride = 26; // Bytes.
public ulong Address;
public ulong GuestSize;
public bool HighCq;
public bool Stubbed;
public int CodeLen;
public int RelocEntriesCount;
}