From dc0adb533dc15a007e9ca2dc0533ef6a61f13393 Mon Sep 17 00:00:00 2001 From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com> Date: Mon, 22 Feb 2021 03:23:48 +0100 Subject: [PATCH] PPTC & Pool Enhancements. (#1968) * PPTC & Pool Enhancements. * Avoid buffer allocations in CodeGenContext.GetCode(). Avoid stream allocations in PTC.PtcInfo. Refactoring/nits. * Use XXHash128, for Ptc.Load & Ptc.Save, x10 faster than Md5. * Why not a nice Span. * Added a simple PtcFormatter library for deserialization/serialization, which does not require reflection, in use at PtcJumpTable and PtcProfiler; improves maintainability and simplicity/readability of affected code. * Nits. * Revert #1987. * Revert "Revert #1987." This reverts commit 998be765cf7f7da5ff0c1c08de704c9012b0f49c. --- .../RegisterAllocators/LinearScanAllocator.cs | 15 +- ARMeilleure/CodeGen/X86/CodeGenContext.cs | 15 +- ARMeilleure/CodeGen/X86/CodeGenerator.cs | 5 + ARMeilleure/Common/BitMap.cs | 4 +- ARMeilleure/Common/BitMapPool.cs | 26 +- ARMeilleure/Common/ThreadStaticPool.cs | 132 +++- ARMeilleure/Common/ThreadStaticPoolEnums.cs | 14 + .../OperandHelper.cs | 40 +- .../OperationHelper.cs | 24 +- ARMeilleure/State/ExecutionMode.cs | 2 +- ARMeilleure/Translation/DirectCallStubs.cs | 6 + ARMeilleure/Translation/EmitterContext.cs | 3 +- .../Translation/JumpTableEntryAllocator.cs | 72 --- ARMeilleure/Translation/PTC/Ptc.cs | 569 +++++++++++------- ARMeilleure/Translation/PTC/PtcFormatter.cs | 121 ++++ ARMeilleure/Translation/PTC/PtcInfo.cs | 10 +- ARMeilleure/Translation/PTC/PtcJumpTable.cs | 182 ++---- ARMeilleure/Translation/PTC/PtcProfiler.cs | 101 ++-- ARMeilleure/Translation/Translator.cs | 35 +- Ryujinx.Memory/MemoryBlock.cs | 4 - 20 files changed, 777 insertions(+), 603 deletions(-) create mode 100644 ARMeilleure/Common/ThreadStaticPoolEnums.cs delete mode 100644 ARMeilleure/Translation/JumpTableEntryAllocator.cs create mode 100644 ARMeilleure/Translation/PTC/PtcFormatter.cs diff --git a/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs index 71739d436..cd36bdc02 100644 --- a/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs +++ b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs @@ -50,6 +50,8 @@ namespace ARMeilleure.CodeGen.RegisterAllocators StackAlloc = stackAlloc; Masks = masks; + BitMapPool.PrepareBitMapPool(); + Active = BitMapPool.Allocate(intervalsCount); Inactive = BitMapPool.Allocate(intervalsCount); } @@ -73,7 +75,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators public void Dispose() { - BitMapPool.Release(); + BitMapPool.ResetBitMapPool(); } } @@ -84,7 +86,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators { NumberLocals(cfg); - AllocationContext context = new AllocationContext(stackAlloc, regMasks, _intervals.Count); + using AllocationContext context = new AllocationContext(stackAlloc, regMasks, _intervals.Count); BuildIntervals(cfg, context); @@ -127,14 +129,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators InsertSplitCopies(); InsertSplitCopiesAtEdges(cfg); - AllocationResult result = new AllocationResult( - context.IntUsedRegisters, - context.VecUsedRegisters, - context.StackAlloc.TotalSize); - - context.Dispose(); - - return result; + return new AllocationResult(context.IntUsedRegisters, context.VecUsedRegisters, context.StackAlloc.TotalSize); } private void AllocateInterval(AllocationContext context, LiveInterval current, int cIndex) diff --git a/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/ARMeilleure/CodeGen/X86/CodeGenContext.cs index da147cca2..fa726f2f8 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenContext.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -2,6 +2,7 @@ using ARMeilleure.CodeGen.RegisterAllocators; using ARMeilleure.Common; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Translation.PTC; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -301,15 +302,13 @@ namespace ARMeilleure.CodeGen.X86 { Assembler assembler = new Assembler(codeStream, _ptcInfo); - byte[] buffer; - for (int index = 0; index < _jumps.Count; index++) { Jump jump = _jumps[index]; - buffer = new byte[jump.JumpPosition - _stream.Position]; + Span buffer = new byte[jump.JumpPosition - _stream.Position]; - _stream.Read(buffer, 0, buffer.Length); + _stream.Read(buffer); _stream.Seek(_ptcDisabled ? ReservedBytesForJump : jump.InstSize, SeekOrigin.Current); codeStream.Write(buffer); @@ -324,13 +323,7 @@ namespace ARMeilleure.CodeGen.X86 } } - buffer = new byte[_stream.Length - _stream.Position]; - - _stream.Read(buffer, 0, buffer.Length); - - codeStream.Write(buffer); - - _ptcInfo?.WriteCode(codeStream); + _stream.CopyTo(codeStream); return codeStream.ToArray(); } diff --git a/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/ARMeilleure/CodeGen/X86/CodeGenerator.cs index ca1f1ceb6..3c14594db 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenerator.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -190,6 +190,11 @@ namespace ARMeilleure.CodeGen.X86 byte[] code = context.GetCode(); + if (ptcInfo != null) + { + ptcInfo.Code = code; + } + Logger.EndPass(PassName.CodeGeneration); return new CompiledFunction(code, unwindInfo); diff --git a/ARMeilleure/Common/BitMap.cs b/ARMeilleure/Common/BitMap.cs index b1c9a6735..f782ac8b9 100644 --- a/ARMeilleure/Common/BitMap.cs +++ b/ARMeilleure/Common/BitMap.cs @@ -35,7 +35,7 @@ namespace ARMeilleure.Common } } - public void Reset(int initialCapacity) + public BitMap Reset(int initialCapacity) { int count = (initialCapacity + IntMask) / IntSize; @@ -50,6 +50,8 @@ namespace ARMeilleure.Common { _masks.Add(0); } + + return this; } public bool Set(int bit) diff --git a/ARMeilleure/Common/BitMapPool.cs b/ARMeilleure/Common/BitMapPool.cs index aac32d55f..d8d297faf 100644 --- a/ARMeilleure/Common/BitMapPool.cs +++ b/ARMeilleure/Common/BitMapPool.cs @@ -4,15 +4,29 @@ { public static BitMap Allocate(int initialCapacity) { - BitMap result = ThreadStaticPool.Instance.Allocate(); - result.Reset(initialCapacity); - - return result; + return BitMap().Reset(initialCapacity); } - public static void Release() + #region "ThreadStaticPool" + public static void PrepareBitMapPool(int groupId = 0) { - ThreadStaticPool.Instance.Clear(); + ThreadStaticPool.PreparePool(groupId, ChunkSizeLimit.Small); } + + private static BitMap BitMap() + { + return ThreadStaticPool.Instance.Allocate(); + } + + public static void ResetBitMapPool(int groupId = 0) + { + ThreadStaticPool.ResetPool(groupId); + } + + public static void DisposeBitMapPools() + { + ThreadStaticPool.DisposePools(); + } + #endregion } } diff --git a/ARMeilleure/Common/ThreadStaticPool.cs b/ARMeilleure/Common/ThreadStaticPool.cs index e23bf1dce..bbe662f82 100644 --- a/ARMeilleure/Common/ThreadStaticPool.cs +++ b/ARMeilleure/Common/ThreadStaticPool.cs @@ -1,4 +1,5 @@ -using System; +using ARMeilleure.Translation.PTC; +using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -6,9 +7,6 @@ namespace ARMeilleure.Common { class ThreadStaticPool where T : class, new() { - private const int ChunkSizeLimit = 1000; // even - private const int PoolSizeIncrement = 200; // > 0 - [ThreadStatic] private static ThreadStaticPool _instance; @@ -18,21 +16,36 @@ namespace ARMeilleure.Common { if (_instance == null) { - PreparePool(0); // So that we can still use a pool when blindly initializing one. + PreparePool(); // So that we can still use a pool when blindly initializing one. } return _instance; } } - private static ConcurrentDictionary>> _pools = new(); + private static readonly ConcurrentDictionary>> _pools = new(); private static Stack> GetPools(int groupId) { return _pools.GetOrAdd(groupId, (groupId) => new()); } - public static void PreparePool(int groupId) + public static void PreparePool( + int groupId = 0, + ChunkSizeLimit chunkSizeLimit = ChunkSizeLimit.Large, + PoolSizeIncrement poolSizeIncrement = PoolSizeIncrement.Default) + { + if (Ptc.State == PtcState.Disabled) + { + PreparePoolDefault(groupId, (int)chunkSizeLimit, (int)poolSizeIncrement); + } + else + { + PreparePoolSlim((int)chunkSizeLimit, (int)poolSizeIncrement); + } + } + + private static void PreparePoolDefault(int groupId, int chunkSizeLimit, int poolSizeIncrement) { // Prepare the pool for this thread, ideally using an existing one from the specified group. @@ -41,27 +54,75 @@ namespace ARMeilleure.Common var pools = GetPools(groupId); lock (pools) { - _instance = (pools.Count != 0) ? pools.Pop() : new(); + _instance = (pools.Count != 0) ? pools.Pop() : new(chunkSizeLimit, poolSizeIncrement); } } } - public static void ReturnPool(int groupId) + private static void PreparePoolSlim(int chunkSizeLimit, int poolSizeIncrement) { - // Reset, limit if necessary, and return the pool for this thread to the specified group. + // Prepare the pool for this thread. - var pools = GetPools(groupId); - lock (pools) + if (_instance == null) { - _instance.Clear(); - _instance.ChunkSizeLimiter(); - pools.Push(_instance); - - _instance = null; + _instance = new(chunkSizeLimit, poolSizeIncrement); } } - public static void ResetPools() + public static void ResetPool(int groupId = 0) + { + if (Ptc.State == PtcState.Disabled) + { + ResetPoolDefault(groupId); + } + else + { + ResetPoolSlim(); + } + } + + private static void ResetPoolDefault(int groupId) + { + // Reset, limit if necessary, and return the pool for this thread to the specified group. + + if (_instance != null) + { + var pools = GetPools(groupId); + lock (pools) + { + _instance.Clear(); + _instance.ChunkSizeLimiter(); + pools.Push(_instance); + + _instance = null; + } + } + } + + private static void ResetPoolSlim() + { + // Reset, limit if necessary, the pool for this thread. + + if (_instance != null) + { + _instance.Clear(); + _instance.ChunkSizeLimiter(); + } + } + + public static void DisposePools() + { + if (Ptc.State == PtcState.Disabled) + { + DisposePoolsDefault(); + } + else + { + DisposePoolSlim(); + } + } + + private static void DisposePoolsDefault() { // Resets any static references to the pools used by threads for each group, allowing them to be garbage collected. @@ -78,20 +139,37 @@ namespace ARMeilleure.Common _pools.Clear(); } + private static void DisposePoolSlim() + { + // Dispose the pool for this thread. + + if (_instance != null) + { + _instance.Dispose(); + + _instance = null; + } + } + private List _pool; private int _chunkIndex = -1; private int _poolIndex = -1; + private int _chunkSizeLimit; + private int _poolSizeIncrement; - private ThreadStaticPool() + private ThreadStaticPool(int chunkSizeLimit, int poolSizeIncrement) { - _pool = new(ChunkSizeLimit * 2); + _chunkSizeLimit = chunkSizeLimit; + _poolSizeIncrement = poolSizeIncrement; + + _pool = new(chunkSizeLimit * 2); AddChunkIfNeeded(); } public T Allocate() { - if (++_poolIndex >= PoolSizeIncrement) + if (++_poolIndex >= _poolSizeIncrement) { AddChunkIfNeeded(); @@ -105,9 +183,9 @@ namespace ARMeilleure.Common { if (++_chunkIndex >= _pool.Count) { - T[] pool = new T[PoolSizeIncrement]; + T[] pool = new T[_poolSizeIncrement]; - for (int i = 0; i < PoolSizeIncrement; i++) + for (int i = 0; i < _poolSizeIncrement; i++) { pool[i] = new T(); } @@ -124,18 +202,18 @@ namespace ARMeilleure.Common private void ChunkSizeLimiter() { - if (_pool.Count >= ChunkSizeLimit) + if (_pool.Count >= _chunkSizeLimit) { - int newChunkSize = ChunkSizeLimit / 2; + int newChunkSize = _chunkSizeLimit / 2; _pool.RemoveRange(newChunkSize, _pool.Count - newChunkSize); - _pool.Capacity = ChunkSizeLimit * 2; + _pool.Capacity = _chunkSizeLimit * 2; } } private void Dispose() { - _pool.Clear(); + _pool = null; } } } diff --git a/ARMeilleure/Common/ThreadStaticPoolEnums.cs b/ARMeilleure/Common/ThreadStaticPoolEnums.cs new file mode 100644 index 000000000..0d1d98d3c --- /dev/null +++ b/ARMeilleure/Common/ThreadStaticPoolEnums.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Common +{ + public enum PoolSizeIncrement + { + Default = 200 + } + + public enum ChunkSizeLimit + { + Large = 200000 / PoolSizeIncrement.Default, + Medium = 100000 / PoolSizeIncrement.Default, + Small = 50000 / PoolSizeIncrement.Default + } +} \ No newline at end of file diff --git a/ARMeilleure/IntermediateRepresentation/OperandHelper.cs b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs index f7381d869..26d664783 100644 --- a/ARMeilleure/IntermediateRepresentation/OperandHelper.cs +++ b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs @@ -4,16 +4,6 @@ namespace ARMeilleure.IntermediateRepresentation { static class OperandHelper { - private static MemoryOperand MemoryOperand() - { - return ThreadStaticPool.Instance.Allocate(); - } - - private static Operand Operand() - { - return ThreadStaticPool.Instance.Allocate(); - } - public static Operand Const(OperandType type, long value) { return type == OperandType.I32 ? Operand().With((int)value) : Operand().With(value); @@ -84,22 +74,34 @@ namespace ARMeilleure.IntermediateRepresentation return MemoryOperand().With(type, baseAddress, index, scale, displacement); } - public static void PrepareOperandPool(bool highCq) + #region "ThreadStaticPool" + public static void PrepareOperandPool(int groupId = 0) { - ThreadStaticPool.PreparePool(highCq ? 1 : 0); - ThreadStaticPool.PreparePool(highCq ? 1 : 0); + ThreadStaticPool.PreparePool(groupId, ChunkSizeLimit.Large); + ThreadStaticPool.PreparePool(groupId, ChunkSizeLimit.Small); } - public static void ReturnOperandPool(bool highCq) + private static Operand Operand() { - ThreadStaticPool.ReturnPool(highCq ? 1 : 0); - ThreadStaticPool.ReturnPool(highCq ? 1 : 0); + return ThreadStaticPool.Instance.Allocate(); } - public static void ResetOperandPools() + private static MemoryOperand MemoryOperand() { - ThreadStaticPool.ResetPools(); - ThreadStaticPool.ResetPools(); + return ThreadStaticPool.Instance.Allocate(); } + + public static void ResetOperandPool(int groupId = 0) + { + ThreadStaticPool.ResetPool(groupId); + ThreadStaticPool.ResetPool(groupId); + } + + public static void DisposeOperandPools() + { + ThreadStaticPool.DisposePools(); + ThreadStaticPool.DisposePools(); + } + #endregion } } diff --git a/ARMeilleure/IntermediateRepresentation/OperationHelper.cs b/ARMeilleure/IntermediateRepresentation/OperationHelper.cs index 538bdac48..0e560ee0a 100644 --- a/ARMeilleure/IntermediateRepresentation/OperationHelper.cs +++ b/ARMeilleure/IntermediateRepresentation/OperationHelper.cs @@ -4,11 +4,6 @@ namespace ARMeilleure.IntermediateRepresentation { static class OperationHelper { - public static Operation Operation() - { - return ThreadStaticPool.Instance.Allocate(); - } - public static Operation Operation(Instruction instruction, Operand destination) { return Operation().With(instruction, destination); @@ -46,19 +41,26 @@ namespace ARMeilleure.IntermediateRepresentation return Operation().With(instruction, destinations, sources); } - public static void PrepareOperationPool(bool highCq) + #region "ThreadStaticPool" + public static void PrepareOperationPool(int groupId = 0) { - ThreadStaticPool.PreparePool(highCq ? 1 : 0); + ThreadStaticPool.PreparePool(groupId, ChunkSizeLimit.Medium); } - public static void ReturnOperationPool(bool highCq) + private static Operation Operation() { - ThreadStaticPool.ReturnPool(highCq ? 1 : 0); + return ThreadStaticPool.Instance.Allocate(); } - public static void ResetOperationPools() + public static void ResetOperationPool(int groupId = 0) { - ThreadStaticPool.ResetPools(); + ThreadStaticPool.ResetPool(groupId); } + + public static void DisposeOperationPools() + { + ThreadStaticPool.DisposePools(); + } + #endregion } } diff --git a/ARMeilleure/State/ExecutionMode.cs b/ARMeilleure/State/ExecutionMode.cs index f43c5569f..29154a255 100644 --- a/ARMeilleure/State/ExecutionMode.cs +++ b/ARMeilleure/State/ExecutionMode.cs @@ -1,6 +1,6 @@ namespace ARMeilleure.State { - enum ExecutionMode + enum ExecutionMode : int { Aarch32Arm = 0, Aarch32Thumb = 1, diff --git a/ARMeilleure/Translation/DirectCallStubs.cs b/ARMeilleure/Translation/DirectCallStubs.cs index df7ca16e7..85af69018 100644 --- a/ARMeilleure/Translation/DirectCallStubs.cs +++ b/ARMeilleure/Translation/DirectCallStubs.cs @@ -29,11 +29,17 @@ namespace ARMeilleure.Translation { if (_initialized) return; + Translator.PreparePool(); + _directCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateDirectCallStub(false)); _directTailCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateDirectCallStub(true)); _indirectCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateIndirectCallStub(false)); _indirectTailCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateIndirectCallStub(true)); + Translator.ResetPool(); + + Translator.DisposePools(); + _initialized = true; } } diff --git a/ARMeilleure/Translation/EmitterContext.cs b/ARMeilleure/Translation/EmitterContext.cs index f41f3464b..5c608b3d3 100644 --- a/ARMeilleure/Translation/EmitterContext.cs +++ b/ARMeilleure/Translation/EmitterContext.cs @@ -1,6 +1,7 @@ using ARMeilleure.Diagnostics; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.State; +using ARMeilleure.Translation.PTC; using System; using System.Collections.Generic; using System.Reflection; @@ -9,8 +10,6 @@ using static ARMeilleure.IntermediateRepresentation.OperandHelper; namespace ARMeilleure.Translation { - using PTC; - class EmitterContext { private readonly Dictionary _irLabels; diff --git a/ARMeilleure/Translation/JumpTableEntryAllocator.cs b/ARMeilleure/Translation/JumpTableEntryAllocator.cs deleted file mode 100644 index 3b918628f..000000000 --- a/ARMeilleure/Translation/JumpTableEntryAllocator.cs +++ /dev/null @@ -1,72 +0,0 @@ -using ARMeilleure.Common; -using System.Collections.Generic; -using System.Diagnostics; - -namespace ARMeilleure.Translation -{ - class JumpTableEntryAllocator - { - private readonly BitMap _bitmap; - private int _freeHint; - - public JumpTableEntryAllocator() - { - _bitmap = new BitMap(); - } - - public bool EntryIsValid(int entryIndex) - { - lock (_bitmap) - { - return _bitmap.IsSet(entryIndex); - } - } - - public void SetEntry(int entryIndex) - { - lock (_bitmap) - { - _bitmap.Set(entryIndex); - } - } - - public int AllocateEntry() - { - lock (_bitmap) - { - int entryIndex; - - if (!_bitmap.IsSet(_freeHint)) - { - entryIndex = _freeHint; - } - else - { - entryIndex = _bitmap.FindFirstUnset(); - } - - _freeHint = entryIndex + 1; - - bool wasSet = _bitmap.Set(entryIndex); - Debug.Assert(wasSet); - - return entryIndex; - } - } - - public void FreeEntry(int entryIndex) - { - lock (_bitmap) - { - _bitmap.Clear(entryIndex); - - _freeHint = entryIndex; - } - } - - public IEnumerable GetEntries() - { - return _bitmap; - } - } -} diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs index 6dd902bc3..163e3efae 100644 --- a/ARMeilleure/Translation/PTC/Ptc.cs +++ b/ARMeilleure/Translation/PTC/Ptc.cs @@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.X86; using ARMeilleure.Memory; using ARMeilleure.Translation.Cache; +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using System; @@ -12,17 +13,20 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Runtime; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Security.Cryptography; using System.Threading; +using static ARMeilleure.Translation.PTC.PtcFormatter; + namespace ARMeilleure.Translation.PTC { public static class Ptc { - private const string HeaderMagic = "PTChd"; + private const string HeaderMagicString = "PTChd\0\0\0"; - private const int InternalVersion = 2010; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 1968; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; @@ -37,12 +41,14 @@ namespace ARMeilleure.Translation.PTC private const byte FillingByte = 0x00; 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 MemoryStream _infosStream; + private static MemoryStream _codesStream; + private static MemoryStream _relocsStream; + private static MemoryStream _unwindInfosStream; - private static readonly BinaryWriter _infosWriter; + private static BinaryWriter _infosWriter; + + private static readonly ulong _headerMagic; private static readonly ManualResetEvent _waitEvent; @@ -66,12 +72,9 @@ namespace ARMeilleure.Translation.PTC static Ptc() { - _infosStream = new MemoryStream(); - _codesStream = new MemoryStream(); - _relocsStream = new MemoryStream(); - _unwindInfosStream = new MemoryStream(); + InitializeMemoryStreams(); - _infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true); + _headerMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(HeaderMagicString).AsSpan()); _waitEvent = new ManualResetEvent(true); @@ -95,13 +98,13 @@ namespace ARMeilleure.Translation.PTC public static void Initialize(string titleIdText, string displayVersion, bool enabled) { Wait(); - ClearMemoryStreams(); - PtcJumpTable.Clear(); PtcProfiler.Wait(); PtcProfiler.ClearEntries(); - if (String.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault) + Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled})."); + + if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault) { TitleIdText = TitleIdTextDefault; DisplayVersion = DisplayVersionDefault; @@ -114,55 +117,72 @@ namespace ARMeilleure.Translation.PTC return; } - Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled})."); - TitleIdText = titleIdText; - DisplayVersion = !String.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; + DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; - if (enabled) + string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); + string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir); + + if (!Directory.Exists(workPathActual)) { - string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); - string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, 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(); + Directory.CreateDirectory(workPathActual); } - else + + if (!Directory.Exists(workPathBackup)) { - CachePathActual = string.Empty; - CachePathBackup = string.Empty; - - Disable(); + Directory.CreateDirectory(workPathBackup); } + + CachePathActual = Path.Combine(workPathActual, DisplayVersion); + CachePathBackup = Path.Combine(workPathBackup, DisplayVersion); + + PreLoad(); + PtcProfiler.PreLoad(); + + Enable(); } - internal static void ClearMemoryStreams() + private static void InitializeMemoryStreams() { - _infosStream.SetLength(0L); - _codesStream.SetLength(0L); - _relocsStream.SetLength(0L); - _unwindInfosStream.SetLength(0L); + _infosStream = new MemoryStream(); + _codesStream = new MemoryStream(); + _relocsStream = new MemoryStream(); + _unwindInfosStream = new MemoryStream(); + + _infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true); + } + + private static void DisposeMemoryStreams() + { + _infosWriter.Dispose(); + + _infosStream.Dispose(); + _codesStream.Dispose(); + _relocsStream.Dispose(); + _unwindInfosStream.Dispose(); + } + + private static bool AreMemoryStreamsEmpty() + { + return _infosStream.Length == 0L && _codesStream.Length == 0L && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L; + } + + private static void ResetMemoryStreamsIfNeeded() + { + if (AreMemoryStreamsEmpty()) + { + return; + } + + DisposeMemoryStreams(); + + InitializeMemoryStreams(); } private static void PreLoad() { - string fileNameActual = String.Concat(CachePathActual, ".cache"); - string fileNameBackup = String.Concat(CachePathBackup, ".cache"); + string fileNameActual = string.Concat(CachePathActual, ".cache"); + string fileNameBackup = string.Concat(CachePathBackup, ".cache"); FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoBackup = new FileInfo(fileNameBackup); @@ -183,106 +203,144 @@ namespace ARMeilleure.Translation.PTC } } - private static bool Load(string fileName, bool isBackup) + private static unsafe 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 (MD5 md5 = MD5.Create()) + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) { - int hashSize = md5.HashSize / 8; + Hash128 currentSizeHash = DeserializeStructure(compressedStream); + + Span sizeBytes = new byte[sizeof(int)]; + compressedStream.Read(sizeBytes); + Hash128 expectedSizeHash = XXHash128.ComputeHash(sizeBytes); + + if (currentSizeHash != expectedSizeHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + int size = BinaryPrimitives.ReadInt32LittleEndian(sizeBytes); + + IntPtr intPtr = IntPtr.Zero; try { - deflateStream.CopyTo(stream); + intPtr = Marshal.AllocHGlobal(size); + + using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), size, size, FileAccess.ReadWrite)) + { + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + int hashSize = Unsafe.SizeOf(); + + stream.Seek(0L, SeekOrigin.Begin); + Hash128 currentHash = DeserializeStructure(stream); + + ReadOnlySpan streamBytes = new(stream.PositionPointer, (int)(stream.Length - stream.Position)); + Hash128 expectedHash = XXHash128.ComputeHash(streamBytes); + + if (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.Endianness != GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (header.FeatureInfo != GetFeatureInfo()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (header.OSPlatform != GetOSPlatform()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (header.InfosLen % InfoEntry.Stride != 0) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan infosBuf = new(stream.PositionPointer, header.InfosLen); + stream.Seek(header.InfosLen, SeekOrigin.Current); + + ReadOnlySpan codesBuf = new(stream.PositionPointer, header.CodesLen); + stream.Seek(header.CodesLen, SeekOrigin.Current); + + ReadOnlySpan relocsBuf = new(stream.PositionPointer, header.RelocsLen); + stream.Seek(header.RelocsLen, SeekOrigin.Current); + + ReadOnlySpan unwindInfosBuf = new(stream.PositionPointer, header.UnwindInfosLen); + stream.Seek(header.UnwindInfosLen, SeekOrigin.Current); + + try + { + PtcJumpTable = PtcJumpTable.Deserialize(stream); + } + catch + { + PtcJumpTable = new PtcJumpTable(); + + InvalidateCompressedStream(compressedStream); + + return false; + } + + _infosStream.Write(infosBuf); + _codesStream.Write(codesBuf); + _relocsStream.Write(relocsBuf); + _unwindInfosStream.Write(unwindInfosBuf); + } } - catch + finally { - InvalidateCompressedStream(compressedStream); - - return false; + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } } - - 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.OSPlatform != GetOSPlatform()) - { - 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.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); } long fileSize = new FileInfo(fileName).Length; @@ -292,20 +350,16 @@ namespace ARMeilleure.Translation.PTC return true; } - private static bool CompareHash(ReadOnlySpan currentHash, ReadOnlySpan expectedHash) + private static Header ReadHeader(Stream stream) { - return currentHash.SequenceEqual(expectedHash); - } - - private static Header ReadHeader(MemoryStream stream) - { - using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader headerReader = new(stream, EncodingCache.UTF8NoBOM, true)) { Header header = new Header(); - header.Magic = headerReader.ReadString(); + header.Magic = headerReader.ReadUInt64(); header.CacheFileVersion = headerReader.ReadUInt32(); + header.Endianness = headerReader.ReadBoolean(); header.FeatureInfo = headerReader.ReadUInt64(); header.OSPlatform = headerReader.ReadUInt32(); @@ -323,87 +377,133 @@ namespace ARMeilleure.Translation.PTC compressedStream.SetLength(0L); } - private static void PreSave(object state) + private static void PreSave() { _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) + try { - File.Copy(fileNameActual, fileNameBackup, true); - } + string fileNameActual = string.Concat(CachePathActual, ".cache"); + string fileNameBackup = string.Concat(CachePathBackup, ".cache"); - Save(fileNameActual); + FileInfo fileInfoActual = new FileInfo(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + } + finally + { + ResetMemoryStreamsIfNeeded(); + PtcJumpTable.ClearIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + } _waitEvent.Set(); } - private static void Save(string fileName) + private static unsafe void Save(string fileName) { int translatedFuncsCount; - using (MemoryStream stream = new MemoryStream()) - using (MD5 md5 = MD5.Create()) + int hashSize = Unsafe.SizeOf(); + + int size = hashSize + Header.Size + GetMemoryStreamsLength() + PtcJumpTable.GetSerializeSize(PtcJumpTable); + + Span sizeBytes = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(sizeBytes, size); + Hash128 sizeHash = XXHash128.ComputeHash(sizeBytes); + + Span sizeHashBytes = new byte[hashSize]; + MemoryMarshal.Write(sizeHashBytes, ref sizeHash); + + IntPtr intPtr = IntPtr.Zero; + + try { - int hashSize = md5.HashSize / 8; + intPtr = Marshal.AllocHGlobal(size); - stream.Seek((long)hashSize, SeekOrigin.Begin); - - WriteHeader(stream); - - _infosStream.WriteTo(stream); - _codesStream.WriteTo(stream); - _relocsStream.WriteTo(stream); - _unwindInfosStream.WriteTo(stream); - - PtcJumpTable.Serialize(stream, PtcJumpTable); - - translatedFuncsCount = GetInfosEntriesCount(); - - ClearMemoryStreams(); - PtcJumpTable.Clear(); - - 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)) + using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), size, size, FileAccess.ReadWrite)) { - try - { - stream.WriteTo(deflateStream); - } - catch - { - compressedStream.Position = 0L; - } + stream.Seek((long)hashSize, SeekOrigin.Begin); - if (compressedStream.Position < compressedStream.Length) + WriteHeader(stream); + + _infosStream.WriteTo(stream); + _codesStream.WriteTo(stream); + _relocsStream.WriteTo(stream); + _unwindInfosStream.WriteTo(stream); + + PtcJumpTable.Serialize(stream, PtcJumpTable); + + stream.Seek((long)hashSize, SeekOrigin.Begin); + ReadOnlySpan streamBytes = new(stream.PositionPointer, (int)(stream.Length - stream.Position)); + Hash128 hash = XXHash128.ComputeHash(streamBytes); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, hash); + + translatedFuncsCount = GetInfosEntriesCount(); + + ResetMemoryStreamsIfNeeded(); + PtcJumpTable.ClearIfNeeded(); + + using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate)) + using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true)) { - compressedStream.SetLength(compressedStream.Position); + try + { + compressedStream.Write(sizeHashBytes); + compressedStream.Write(sizeBytes); + + stream.Seek(0L, SeekOrigin.Begin); + stream.CopyTo(deflateStream); + } + catch + { + compressedStream.Position = 0L; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } } } } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } long fileSize = new FileInfo(fileName).Length; - Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount})."); + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount})."); + } } - private static void WriteHeader(MemoryStream stream) + private static int GetMemoryStreamsLength() { - using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true)) + return (int)_infosStream.Length + (int)_codesStream.Length + (int)_relocsStream.Length + (int)_unwindInfosStream.Length; + } + + private static void WriteHeader(Stream stream) + { + using (BinaryWriter headerWriter = new(stream, EncodingCache.UTF8NoBOM, true)) { - headerWriter.Write((string)HeaderMagic); // Header.Magic + headerWriter.Write((ulong)_headerMagic); // Header.Magic headerWriter.Write((uint)InternalVersion); // Header.CacheFileVersion + headerWriter.Write((bool)GetEndianness()); // Header.Endianness headerWriter.Write((ulong)GetFeatureInfo()); // Header.FeatureInfo headerWriter.Write((uint)GetOSPlatform()); // Header.OSPlatform @@ -416,10 +516,7 @@ namespace ARMeilleure.Translation.PTC internal static void LoadTranslations(ConcurrentDictionary funcs, IMemoryManager memory, JumpTable jumpTable) { - if ((int)_infosStream.Length == 0 || - (int)_codesStream.Length == 0 || - (int)_relocsStream.Length == 0 || - (int)_unwindInfosStream.Length == 0) + if (AreMemoryStreamsEmpty()) { return; } @@ -431,10 +528,10 @@ namespace ARMeilleure.Translation.PTC _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)) + using (BinaryReader infosReader = new(_infosStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader codesReader = new(_codesStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true)) { for (int i = 0; i < GetInfosEntriesCount(); i++) { @@ -446,9 +543,9 @@ namespace ARMeilleure.Translation.PTC SkipReloc(infoEntry.RelocEntriesCount); SkipUnwindInfo(unwindInfosReader); } - else if (infoEntry.HighCq || !PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) || !value.highCq) + else if (infoEntry.HighCq || !PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) || !value.HighCq) { - byte[] code = ReadCode(codesReader, infoEntry.CodeLen); + Span code = ReadCode(codesReader, infoEntry.CodeLen); if (infoEntry.RelocEntriesCount != 0) { @@ -529,11 +626,11 @@ namespace ARMeilleure.Translation.PTC _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current); } - private static byte[] ReadCode(BinaryReader codesReader, int codeLen) + private static Span ReadCode(BinaryReader codesReader, int codeLen) { - byte[] codeBuf = new byte[codeLen]; + Span codeBuf = new byte[codeLen]; - codesReader.Read(codeBuf, 0, codeLen); + codesReader.Read(codeBuf); return codeBuf; } @@ -605,9 +702,9 @@ namespace ARMeilleure.Translation.PTC return new UnwindInfo(pushEntries, prologueSize); } - private static TranslatedFunction FastTranslate(byte[] code, ulong guestSize, UnwindInfo unwindInfo, bool highCq) + private static TranslatedFunction FastTranslate(ReadOnlySpan code, ulong guestSize, UnwindInfo unwindInfo, bool highCq) { - CompiledFunction cFunc = new CompiledFunction(code, unwindInfo); + CompiledFunction cFunc = new CompiledFunction(code.ToArray(), unwindInfo); IntPtr codePtr = JitCache.Map(cFunc); @@ -663,6 +760,11 @@ namespace ARMeilleure.Translation.PTC if (profiledFuncsToTranslate.Count == 0) { + ResetMemoryStreamsIfNeeded(); + PtcJumpTable.ClearIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + return; } @@ -696,6 +798,8 @@ namespace ARMeilleure.Translation.PTC break; } } + + Translator.DisposePools(); } int maxDegreeOfParallelism = (Environment.ProcessorCount * 3) / 4; @@ -715,8 +819,6 @@ namespace ARMeilleure.Translation.PTC threads.Clear(); - Translator.ResetPools(); - _loggerEvent.Set(); PtcJumpTable.Initialize(jumpTable); @@ -724,7 +826,9 @@ namespace ARMeilleure.Translation.PTC PtcJumpTable.ReadJumpTable(jumpTable); PtcJumpTable.ReadDynamicTable(jumpTable); - ThreadPool.QueueUserWorkItem(PreSave); + Thread preSaveThread = new Thread(PreSave); + preSaveThread.IsBackground = true; + preSaveThread.Start(); } private static void TranslationLogger(object state) @@ -751,11 +855,11 @@ namespace ARMeilleure.Translation.PTC _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.Code.Length); // InfoEntry.CodeLen _infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount // WriteCode. - ptcInfo.CodeStream.WriteTo(_codesStream); + _codesStream.Write(ptcInfo.Code.AsSpan()); // WriteReloc. ptcInfo.RelocStream.WriteTo(_relocsStream); @@ -765,6 +869,11 @@ namespace ARMeilleure.Translation.PTC } } + private static bool GetEndianness() + { + return BitConverter.IsLittleEndian; + } + private static ulong GetFeatureInfo() { return (ulong)HardwareCapabilities.FeatureInfoEdx << 32 | (uint)HardwareCapabilities.FeatureInfoEcx; @@ -784,9 +893,12 @@ namespace ARMeilleure.Translation.PTC private struct Header { - public string Magic; + public const int Size = 41; // Bytes. + + public ulong Magic; public uint CacheFileVersion; + public bool Endianness; public ulong FeatureInfo; public uint OSPlatform; @@ -849,12 +961,9 @@ namespace ARMeilleure.Translation.PTC Wait(); _waitEvent.Dispose(); - _infosWriter.Dispose(); + _loggerEvent.Dispose(); - _infosStream.Dispose(); - _codesStream.Dispose(); - _relocsStream.Dispose(); - _unwindInfosStream.Dispose(); + DisposeMemoryStreams(); } } } diff --git a/ARMeilleure/Translation/PTC/PtcFormatter.cs b/ARMeilleure/Translation/PTC/PtcFormatter.cs new file mode 100644 index 000000000..7346b4844 --- /dev/null +++ b/ARMeilleure/Translation/PTC/PtcFormatter.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.PTC +{ + public class PtcFormatter + { + #region "Deserialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeDictionary(Stream stream, Func valueFunc) where TKey : unmanaged + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List DeserializeList(Stream stream) where T : unmanaged + { + List list = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + T item = DeserializeStructure(stream); + + list.Add(item); + } + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T DeserializeStructure(Stream stream) where T : unmanaged + { + T structure = default(T); + + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + stream.Read(MemoryMarshal.AsBytes(spanT)); + + return structure; + } + #endregion + + #region "GetSerializeSize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeDictionary(Dictionary dictionary, Func valueFunc) where TKey : unmanaged + { + int size = 0; + + size += Unsafe.SizeOf(); + + foreach ((_, TValue value) in dictionary) + { + size += Unsafe.SizeOf(); + size += valueFunc(value); + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeList(List list) where T : unmanaged + { + int size = 0; + + size += Unsafe.SizeOf(); + + size += list.Count * Unsafe.SizeOf(); + + return size; + } + #endregion + + #region "Serialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeDictionary(Stream stream, Dictionary dictionary, Action valueAction) where TKey : unmanaged + { + SerializeStructure(stream, dictionary.Count); + + foreach ((TKey key, TValue value) in dictionary) + { + SerializeStructure(stream, key); + valueAction(stream, value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeList(Stream stream, List list) where T : unmanaged + { + SerializeStructure(stream, list.Count); + + foreach (T item in list) + { + SerializeStructure(stream, item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeStructure(Stream stream, T structure) where T : unmanaged + { + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + stream.Write(MemoryMarshal.AsBytes(spanT)); + } + #endregion + } +} \ No newline at end of file diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs index 547fe6d8a..bbecb56f2 100644 --- a/ARMeilleure/Translation/PTC/PtcInfo.cs +++ b/ARMeilleure/Translation/PTC/PtcInfo.cs @@ -9,7 +9,8 @@ namespace ARMeilleure.Translation.PTC private readonly BinaryWriter _relocWriter; private readonly BinaryWriter _unwindInfoWriter; - public MemoryStream CodeStream { get; } + public byte[] Code { get; set; } + public MemoryStream RelocStream { get; } public MemoryStream UnwindInfoStream { get; } @@ -17,7 +18,6 @@ namespace ARMeilleure.Translation.PTC public PtcInfo() { - CodeStream = new MemoryStream(); RelocStream = new MemoryStream(); UnwindInfoStream = new MemoryStream(); @@ -27,11 +27,6 @@ namespace ARMeilleure.Translation.PTC RelocEntriesCount = 0; } - public void WriteCode(MemoryStream codeStream) - { - codeStream.WriteTo(CodeStream); - } - public void WriteRelocEntry(RelocEntry relocEntry) { _relocWriter.Write((int)relocEntry.Position); @@ -60,7 +55,6 @@ namespace ARMeilleure.Translation.PTC _relocWriter.Dispose(); _unwindInfoWriter.Dispose(); - CodeStream.Dispose(); RelocStream.Dispose(); UnwindInfoStream.Dispose(); } diff --git a/ARMeilleure/Translation/PTC/PtcJumpTable.cs b/ARMeilleure/Translation/PTC/PtcJumpTable.cs index d52d08743..75dcba50c 100644 --- a/ARMeilleure/Translation/PTC/PtcJumpTable.cs +++ b/ARMeilleure/Translation/PTC/PtcJumpTable.cs @@ -6,15 +6,18 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using static ARMeilleure.Translation.PTC.PtcFormatter; + namespace ARMeilleure.Translation.PTC { class PtcJumpTable { + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 16*/)] public struct TableEntry { public int EntryIndex; public long GuestAddress; - public TAddress HostAddress; + public TAddress HostAddress; // int public TableEntry(int entryIndex, long guestAddress, TAddress hostAddress) { @@ -24,14 +27,14 @@ namespace ARMeilleure.Translation.PTC } } - public enum DirectHostAddress + public enum DirectHostAddress : int { CallStub = 0, TailCallStub = 1, Host = 2 } - public enum IndirectHostAddress + public enum IndirectHostAddress : int { CallStub = 0, TailCallStub = 1 @@ -66,152 +69,40 @@ namespace ARMeilleure.Translation.PTC Owners = owners; } - public static PtcJumpTable Deserialize(MemoryStream stream) + public static PtcJumpTable Deserialize(Stream stream) { - using (BinaryReader reader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true)) - { - var jumpTable = new List>(); + var jumpTable = DeserializeList>(stream); + var dynamicTable = DeserializeList>(stream); - int jumpTableCount = reader.ReadInt32(); + var targets = DeserializeList(stream); + var dependants = DeserializeDictionary>(stream, (stream) => DeserializeList(stream)); + var owners = DeserializeDictionary>(stream, (stream) => DeserializeList(stream)); - for (int i = 0; i < jumpTableCount; i++) - { - int entryIndex = reader.ReadInt32(); - long guestAddress = reader.ReadInt64(); - DirectHostAddress hostAddress = (DirectHostAddress)reader.ReadInt32(); - - jumpTable.Add(new TableEntry(entryIndex, guestAddress, hostAddress)); - } - - var dynamicTable = new List>(); - - int dynamicTableCount = reader.ReadInt32(); - - for (int i = 0; i < dynamicTableCount; i++) - { - int entryIndex = reader.ReadInt32(); - long guestAddress = reader.ReadInt64(); - IndirectHostAddress hostAddress = (IndirectHostAddress)reader.ReadInt32(); - - dynamicTable.Add(new TableEntry(entryIndex, guestAddress, hostAddress)); - } - - var targets = new List(); - - int targetsCount = reader.ReadInt32(); - - for (int i = 0; i < targetsCount; i++) - { - ulong address = reader.ReadUInt64(); - - targets.Add(address); - } - - var dependants = new Dictionary>(); - - int dependantsCount = reader.ReadInt32(); - - for (int i = 0; i < dependantsCount; i++) - { - ulong address = reader.ReadUInt64(); - - var entries = new List(); - - int entriesCount = reader.ReadInt32(); - - for (int j = 0; j < entriesCount; j++) - { - int entry = reader.ReadInt32(); - - entries.Add(entry); - } - - dependants.Add(address, entries); - } - - var owners = new Dictionary>(); - - int ownersCount = reader.ReadInt32(); - - for (int i = 0; i < ownersCount; i++) - { - ulong address = reader.ReadUInt64(); - - var entries = new List(); - - int entriesCount = reader.ReadInt32(); - - for (int j = 0; j < entriesCount; j++) - { - int entry = reader.ReadInt32(); - - entries.Add(entry); - } - - owners.Add(address, entries); - } - - return new PtcJumpTable(jumpTable, dynamicTable, targets, dependants, owners); - } + return new PtcJumpTable(jumpTable, dynamicTable, targets, dependants, owners); } - public static void Serialize(MemoryStream stream, PtcJumpTable ptcJumpTable) + public static int GetSerializeSize(PtcJumpTable ptcJumpTable) { - using (BinaryWriter writer = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true)) - { - writer.Write((int)ptcJumpTable._jumpTable.Count); + int size = 0; - foreach (var tableEntry in ptcJumpTable._jumpTable) - { - writer.Write((int)tableEntry.EntryIndex); - writer.Write((long)tableEntry.GuestAddress); - writer.Write((int)tableEntry.HostAddress); - } + size += GetSerializeSizeList(ptcJumpTable._jumpTable); + size += GetSerializeSizeList(ptcJumpTable._dynamicTable); - writer.Write((int)ptcJumpTable._dynamicTable.Count); + size += GetSerializeSizeList(ptcJumpTable.Targets); + size += GetSerializeSizeDictionary(ptcJumpTable.Dependants, (list) => GetSerializeSizeList(list)); + size += GetSerializeSizeDictionary(ptcJumpTable.Owners, (list) => GetSerializeSizeList(list)); - foreach (var tableEntry in ptcJumpTable._dynamicTable) - { - writer.Write((int)tableEntry.EntryIndex); - writer.Write((long)tableEntry.GuestAddress); - writer.Write((int)tableEntry.HostAddress); - } + return size; + } - writer.Write((int)ptcJumpTable.Targets.Count); + public static void Serialize(Stream stream, PtcJumpTable ptcJumpTable) + { + SerializeList(stream, ptcJumpTable._jumpTable); + SerializeList(stream, ptcJumpTable._dynamicTable); - foreach (ulong address in ptcJumpTable.Targets) - { - writer.Write((ulong)address); - } - - writer.Write((int)ptcJumpTable.Dependants.Count); - - foreach (var kv in ptcJumpTable.Dependants) - { - writer.Write((ulong)kv.Key); // address - - writer.Write((int)kv.Value.Count); - - foreach (int entry in kv.Value) - { - writer.Write((int)entry); - } - } - - writer.Write((int)ptcJumpTable.Owners.Count); - - foreach (var kv in ptcJumpTable.Owners) - { - writer.Write((ulong)kv.Key); // address - - writer.Write((int)kv.Value.Count); - - foreach (int entry in kv.Value) - { - writer.Write((int)entry); - } - } - } + SerializeList(stream, ptcJumpTable.Targets); + SerializeDictionary(stream, ptcJumpTable.Dependants, (stream, list) => SerializeList(stream, list)); + SerializeDictionary(stream, ptcJumpTable.Owners, (stream, list) => SerializeList(stream, list)); } public void Initialize(JumpTable jumpTable) @@ -270,14 +161,25 @@ namespace ARMeilleure.Translation.PTC Owners.Remove(guestAddress); } - public void Clear() + public void ClearIfNeeded() { + if (_jumpTable.Count == 0 && _dynamicTable.Count == 0 && + Targets.Count == 0 && Dependants.Count == 0 && Owners.Count == 0) + { + return; + } + _jumpTable.Clear(); + _jumpTable.TrimExcess(); _dynamicTable.Clear(); + _dynamicTable.TrimExcess(); Targets.Clear(); + Targets.TrimExcess(); Dependants.Clear(); + Dependants.TrimExcess(); Owners.Clear(); + Owners.TrimExcess(); } public void WriteJumpTable(JumpTable jumpTable, ConcurrentDictionary funcs) @@ -307,7 +209,7 @@ namespace ARMeilleure.Translation.PTC } else { - if (!PtcProfiler.ProfiledFuncs.TryGetValue((ulong)guestAddress, out var value) || !value.highCq) + if (!PtcProfiler.ProfiledFuncs.TryGetValue((ulong)guestAddress, out var value) || !value.HighCq) { throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})"); } diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs index 0def32c32..8782a7945 100644 --- a/ARMeilleure/Translation/PTC/PtcProfiler.cs +++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -6,9 +6,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading; +using static ARMeilleure.Translation.PTC.PtcFormatter; + namespace ARMeilleure.Translation.PTC { public static class PtcProfiler @@ -29,7 +32,9 @@ namespace ARMeilleure.Translation.PTC private static bool _disposed; - internal static Dictionary ProfiledFuncs { get; private set; } + private static byte[] _lastHash; + + internal static Dictionary ProfiledFuncs { get; private set; } internal static bool Enabled { get; private set; } @@ -47,7 +52,7 @@ namespace ARMeilleure.Translation.PTC _disposed = false; - ProfiledFuncs = new Dictionary(); + ProfiledFuncs = new Dictionary(); Enabled = false; } @@ -60,7 +65,7 @@ namespace ARMeilleure.Translation.PTC lock (_lock) { - ProfiledFuncs.TryAdd(address, (mode, highCq: false)); + ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false)); } } } @@ -75,7 +80,7 @@ namespace ARMeilleure.Translation.PTC { Debug.Assert(ProfiledFuncs.ContainsKey(address)); - ProfiledFuncs[address] = (mode, highCq: true); + ProfiledFuncs[address] = new FuncProfile(mode, highCq: true); } } } @@ -95,7 +100,7 @@ namespace ARMeilleure.Translation.PTC if (!funcs.ContainsKey(address)) { - profiledFuncsToTranslate.Enqueue((address, profiledFunc.Value.mode, profiledFunc.Value.highCq)); + profiledFuncsToTranslate.Enqueue((address, profiledFunc.Value.Mode, profiledFunc.Value.HighCq)); } } @@ -105,12 +110,15 @@ namespace ARMeilleure.Translation.PTC internal static void ClearEntries() { ProfiledFuncs.Clear(); + ProfiledFuncs.TrimExcess(); } internal static void PreLoad() { - string fileNameActual = String.Concat(Ptc.CachePathActual, ".info"); - string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info"); + _lastHash = Array.Empty(); + + string fileNameActual = string.Concat(Ptc.CachePathActual, ".info"); + string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info"); FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoBackup = new FileInfo(fileNameBackup); @@ -138,8 +146,6 @@ namespace ARMeilleure.Translation.PTC using (MemoryStream stream = new MemoryStream()) using (MD5 md5 = MD5.Create()) { - int hashSize = md5.HashSize / 8; - try { deflateStream.CopyTo(stream); @@ -151,6 +157,8 @@ namespace ARMeilleure.Translation.PTC return false; } + int hashSize = md5.HashSize / 8; + stream.Seek(0L, SeekOrigin.Begin); byte[] currentHash = new byte[hashSize]; @@ -189,12 +197,14 @@ namespace ARMeilleure.Translation.PTC } catch { - ProfiledFuncs = new Dictionary(); + ProfiledFuncs = new Dictionary(); InvalidateCompressedStream(compressedStream); return false; } + + _lastHash = expectedHash; } long fileSize = new FileInfo(fileName).Length; @@ -209,7 +219,7 @@ namespace ARMeilleure.Translation.PTC return currentHash.SequenceEqual(expectedHash); } - private static Header ReadHeader(MemoryStream stream) + private static Header ReadHeader(Stream stream) { using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true)) { @@ -223,26 +233,9 @@ namespace ARMeilleure.Translation.PTC } } - private static Dictionary Deserialize(MemoryStream stream) + private static Dictionary Deserialize(Stream stream) { - using (BinaryReader reader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true)) - { - var profiledFuncs = new Dictionary(); - - int profiledFuncsCount = reader.ReadInt32(); - - for (int i = 0; i < profiledFuncsCount; i++) - { - ulong address = reader.ReadUInt64(); - - ExecutionMode mode = (ExecutionMode)reader.ReadInt32(); - bool highCq = reader.ReadBoolean(); - - profiledFuncs.Add(address, (mode, highCq)); - } - - return profiledFuncs; - } + return DeserializeDictionary(stream, (stream) => DeserializeStructure(stream)); } private static void InvalidateCompressedStream(FileStream compressedStream) @@ -254,8 +247,8 @@ namespace ARMeilleure.Translation.PTC { _waitEvent.Reset(); - string fileNameActual = String.Concat(Ptc.CachePathActual, ".info"); - string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info"); + string fileNameActual = string.Concat(Ptc.CachePathActual, ".info"); + string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info"); FileInfo fileInfoActual = new FileInfo(fileNameActual); @@ -295,16 +288,25 @@ namespace ARMeilleure.Translation.PTC stream.Seek(0L, SeekOrigin.Begin); stream.Write(hash, 0, hashSize); + if (CompareHash(hash, _lastHash)) + { + return; + } + using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate)) using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true)) { try { stream.WriteTo(deflateStream); + + _lastHash = hash; } catch { compressedStream.Position = 0L; + + _lastHash = Array.Empty(); } if (compressedStream.Position < compressedStream.Length) @@ -316,10 +318,13 @@ namespace ARMeilleure.Translation.PTC long fileSize = new FileInfo(fileName).Length; - Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount})."); + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount})."); + } } - private static void WriteHeader(MemoryStream stream) + private static void WriteHeader(Stream stream) { using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true)) { @@ -329,20 +334,9 @@ namespace ARMeilleure.Translation.PTC } } - private static void Serialize(MemoryStream stream, Dictionary profiledFuncs) + private static void Serialize(Stream stream, Dictionary profiledFuncs) { - using (BinaryWriter writer = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true)) - { - writer.Write((int)profiledFuncs.Count); - - foreach (var kv in profiledFuncs) - { - writer.Write((ulong)kv.Key); // address - - writer.Write((int)kv.Value.mode); - writer.Write((bool)kv.Value.highCq); - } - } + SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure)); } private struct Header @@ -352,6 +346,19 @@ namespace ARMeilleure.Translation.PTC public uint InfoFileVersion; } + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)] + internal struct FuncProfile + { + public ExecutionMode Mode; + public bool HighCq; + + public FuncProfile(ExecutionMode mode, bool highCq) + { + Mode = mode; + HighCq = highCq; + } + } + internal static void Start() { if (Ptc.State == PtcState.Enabled || diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs index ac96475f9..f64912b3b 100644 --- a/ARMeilleure/Translation/Translator.cs +++ b/ARMeilleure/Translation/Translator.cs @@ -11,8 +11,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime; using System.Threading; +using static ARMeilleure.Common.BitMapPool; using static ARMeilleure.IntermediateRepresentation.OperandHelper; using static ARMeilleure.IntermediateRepresentation.OperationHelper; @@ -148,10 +150,12 @@ namespace ARMeilleure.Translation ClearJitCache(); - ResetPools(); + DisposePools(); _jumpTable.Dispose(); _jumpTable = null; + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; } } @@ -207,7 +211,7 @@ namespace ARMeilleure.Translation Logger.EndPass(PassName.Decoding); - PreparePool(highCq); + PreparePool(highCq ? 1 : 0); Logger.StartPass(PassName.Translation); @@ -240,13 +244,15 @@ namespace ARMeilleure.Translation { func = Compiler.Compile(cfg, argTypes, OperandType.I64, options); - ReturnPool(highCq); + ResetPool(highCq ? 1 : 0); } - else using (PtcInfo ptcInfo = new PtcInfo()) + else { + using PtcInfo ptcInfo = new PtcInfo(); + func = Compiler.Compile(cfg, argTypes, OperandType.I64, options, ptcInfo); - ReturnPool(highCq); + ResetPool(highCq ? 1 : 0); Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, highCq, ptcInfo); } @@ -254,22 +260,23 @@ namespace ARMeilleure.Translation return new TranslatedFunction(func, funcSize, highCq); } - internal static void PreparePool(bool highCq) + internal static void PreparePool(int groupId = 0) { - PrepareOperandPool(highCq); - PrepareOperationPool(highCq); + PrepareOperandPool(groupId); + PrepareOperationPool(groupId); } - internal static void ReturnPool(bool highCq) + internal static void ResetPool(int groupId = 0) { - ReturnOperandPool(highCq); - ReturnOperationPool(highCq); + ResetOperationPool(groupId); + ResetOperandPool(groupId); } - internal static void ResetPools() + internal static void DisposePools() { - ResetOperandPools(); - ResetOperationPools(); + DisposeOperandPools(); + DisposeOperationPools(); + DisposeBitMapPools(); } private struct Range diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs index 3b7a54ae9..4e775bba7 100644 --- a/Ryujinx.Memory/MemoryBlock.cs +++ b/Ryujinx.Memory/MemoryBlock.cs @@ -40,8 +40,6 @@ namespace Ryujinx.Memory } Size = size; - - GC.AddMemoryPressure((long)Size); } /// @@ -283,8 +281,6 @@ namespace Ryujinx.Memory if (ptr != IntPtr.Zero) { MemoryManagement.Free(ptr); - - GC.RemoveMemoryPressure((long)Size); } }