From 68f6b79fd3f06ea572d5f0edd5fc8cbaee1ae449 Mon Sep 17 00:00:00 2001
From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com>
Date: Tue, 12 Jan 2021 19:04:02 +0100
Subject: [PATCH] Add a simple Pools Limiter. (#1830)

* 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.

* Free up memory allocated by Pools during any PPTC translations at boot time.

* Nit due to rebase.

* Add a simple Pools Limiter.

* Nits.

* Fix missing JumpTable.RegisterFunction() due to rebase.

Clear MemoryStreams as soon as possible, when they are no longer needed.

* Code cleaning.

* Nit for retrigger Checks.

* Update Ptc.cs

* Contextual refactoring of Translator. Ignore resetting of pools for DirectCallStubs.

* Nit for retrigger Checks.
---
 ARMeilleure/Common/ThreadStaticPool.cs     | 84 +++++++++++++---------
 ARMeilleure/Translation/DirectCallStubs.cs |  2 -
 ARMeilleure/Translation/PTC/Ptc.cs         | 16 +++--
 ARMeilleure/Translation/Translator.cs      | 33 +++++----
 Ryujinx.Memory/MemoryBlock.cs              |  4 ++
 5 files changed, 87 insertions(+), 52 deletions(-)

diff --git a/ARMeilleure/Common/ThreadStaticPool.cs b/ARMeilleure/Common/ThreadStaticPool.cs
index 3fce28ec0f..e23bf1dce5 100644
--- a/ARMeilleure/Common/ThreadStaticPool.cs
+++ b/ARMeilleure/Common/ThreadStaticPool.cs
@@ -1,13 +1,13 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.Threading;
 
 namespace ARMeilleure.Common
 {
     class ThreadStaticPool<T> where T : class, new()
     {
-        private const int PoolSizeIncrement = 200;
+        private const int ChunkSizeLimit = 1000; // even
+        private const int PoolSizeIncrement = 200; // > 0
 
         [ThreadStatic]
         private static ThreadStaticPool<T> _instance;
@@ -25,11 +25,11 @@ namespace ARMeilleure.Common
             }
         }
 
-        private static ConcurrentDictionary<int, Stack<ThreadStaticPool<T>>> _pools = new ConcurrentDictionary<int, Stack<ThreadStaticPool<T>>>();
+        private static ConcurrentDictionary<int, Stack<ThreadStaticPool<T>>> _pools = new();
 
         private static Stack<ThreadStaticPool<T>> GetPools(int groupId)
         {
-            return _pools.GetOrAdd(groupId, x => new Stack<ThreadStaticPool<T>>());
+            return _pools.GetOrAdd(groupId, (groupId) => new());
         }
 
         public static void PreparePool(int groupId)
@@ -41,19 +41,20 @@ namespace ARMeilleure.Common
                 var pools = GetPools(groupId);
                 lock (pools)
                 {
-                    _instance = (pools.Count != 0) ? pools.Pop() : new ThreadStaticPool<T>(PoolSizeIncrement * 2);
+                    _instance = (pools.Count != 0) ? pools.Pop() : new();
                 }
             }
         }
 
         public static void ReturnPool(int groupId)
         {
-            // Reset and return the pool for this thread to the specified group.
+            // Reset, limit if necessary, and return the pool for this thread to the specified group.
 
             var pools = GetPools(groupId);
             lock (pools)
             {
                 _instance.Clear();
+                _instance.ChunkSizeLimiter();
                 pools.Push(_instance);
 
                 _instance = null;
@@ -66,58 +67,75 @@ namespace ARMeilleure.Common
 
             foreach (var pools in _pools.Values)
             {
+                foreach (var instance in pools)
+                {
+                    instance.Dispose();
+                }
+
                 pools.Clear();
             }
 
             _pools.Clear();
         }
 
-        private T[] _pool;
-        private int _poolUsed = -1;
-        private int _poolSize;
+        private List<T[]> _pool;
+        private int _chunkIndex = -1;
+        private int _poolIndex = -1;
 
-        public ThreadStaticPool(int initialSize)
+        private ThreadStaticPool()
         {
-            _pool = new T[initialSize];
+            _pool = new(ChunkSizeLimit * 2);
 
-            for (int i = 0; i < initialSize; i++)
-            {
-                _pool[i] = new T();
-            }
-
-            _poolSize = initialSize;
+            AddChunkIfNeeded();
         }
 
         public T Allocate()
         {
-            int index = Interlocked.Increment(ref _poolUsed);
-
-            if (index >= _poolSize)
+            if (++_poolIndex >= PoolSizeIncrement)
             {
-                IncreaseSize();
+                AddChunkIfNeeded();
+
+                _poolIndex = 0;
             }
 
-            return _pool[index];
+            return _pool[_chunkIndex][_poolIndex];
         }
 
-        private void IncreaseSize()
+        private void AddChunkIfNeeded()
         {
-            _poolSize += PoolSizeIncrement;
-
-            T[] newArray = new T[_poolSize];
-            Array.Copy(_pool, 0, newArray, 0, _pool.Length);
-
-            for (int i = _pool.Length; i < _poolSize; i++)
+            if (++_chunkIndex >= _pool.Count)
             {
-                newArray[i] = new T();
-            }
+                T[] pool = new T[PoolSizeIncrement];
 
-            Interlocked.Exchange(ref _pool, newArray);
+                for (int i = 0; i < PoolSizeIncrement; i++)
+                {
+                    pool[i] = new T();
+                }
+
+                _pool.Add(pool);
+            }
         }
 
         public void Clear()
         {
-            _poolUsed = -1;
+            _chunkIndex = 0;
+            _poolIndex = -1;
+        }
+
+        private void ChunkSizeLimiter()
+        {
+            if (_pool.Count >= ChunkSizeLimit)
+            {
+                int newChunkSize = ChunkSizeLimit / 2;
+
+                _pool.RemoveRange(newChunkSize, _pool.Count - newChunkSize);
+                _pool.Capacity = ChunkSizeLimit * 2;
+            }
+        }
+
+        private void Dispose()
+        {
+            _pool.Clear();
         }
     }
 }
diff --git a/ARMeilleure/Translation/DirectCallStubs.cs b/ARMeilleure/Translation/DirectCallStubs.cs
index 57397d14b7..df7ca16e7f 100644
--- a/ARMeilleure/Translation/DirectCallStubs.cs
+++ b/ARMeilleure/Translation/DirectCallStubs.cs
@@ -34,8 +34,6 @@ namespace ARMeilleure.Translation
                 _indirectCallStubPtr     = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateIndirectCallStub(false));
                 _indirectTailCallStubPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateIndirectCallStub(true));
 
-                Translator.ResetPools();
-
                 _initialized = true;
             }
         }
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index f382cc637e..8f250a5528 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -98,7 +98,6 @@ namespace ARMeilleure.Translation.PTC
             ClearMemoryStreams();
             PtcJumpTable.Clear();
 
-            PtcProfiler.Stop();
             PtcProfiler.Wait();
             PtcProfiler.ClearEntries();
 
@@ -345,6 +344,8 @@ namespace ARMeilleure.Translation.PTC
 
         private static void Save(string fileName)
         {
+            int translatedFuncsCount;
+
             using (MemoryStream stream = new MemoryStream())
             using (MD5 md5 = MD5.Create())
             {
@@ -361,6 +362,11 @@ namespace ARMeilleure.Translation.PTC
 
                 PtcJumpTable.Serialize(stream, PtcJumpTable);
 
+                translatedFuncsCount = GetInfosEntriesCount();
+
+                ClearMemoryStreams();
+                PtcJumpTable.Clear();
+
                 stream.Seek((long)hashSize, SeekOrigin.Begin);
                 byte[] hash = md5.ComputeHash(stream);
 
@@ -388,7 +394,7 @@ namespace ARMeilleure.Translation.PTC
 
             long fileSize = new FileInfo(fileName).Length;
 
-            Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {GetInfosEntriesCount()}).");
+            Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount}).");
         }
 
         private static void WriteHeader(MemoryStream stream)
@@ -709,10 +715,10 @@ namespace ARMeilleure.Translation.PTC
 
             threads.Clear();
 
-            _loggerEvent.Set();
-
             Translator.ResetPools();
 
+            _loggerEvent.Set();
+
             PtcJumpTable.Initialize(jumpTable);
 
             PtcJumpTable.ReadJumpTable(jumpTable);
@@ -736,7 +742,7 @@ namespace ARMeilleure.Translation.PTC
             Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
         }
 
-        internal static void WriteInfoCodeReloc(ulong address, ulong guestSize, bool highCq, PtcInfo ptcInfo)
+        internal static void WriteInfoCodeRelocUnwindInfo(ulong address, ulong guestSize, bool highCq, PtcInfo ptcInfo)
         {
             lock (_lock)
             {
diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs
index 612f66479c..2c32e9f00c 100644
--- a/ARMeilleure/Translation/Translator.cs
+++ b/ARMeilleure/Translation/Translator.cs
@@ -201,15 +201,14 @@ namespace ARMeilleure.Translation
         {
             ArmEmitterContext context = new ArmEmitterContext(memory, jumpTable, address, highCq, Aarch32Mode.User);
 
-            PrepareOperandPool(highCq);
-            PrepareOperationPool(highCq);
-
             Logger.StartPass(PassName.Decoding);
 
             Block[] blocks = Decoder.Decode(memory, address, mode, highCq, singleBlock: false);
 
             Logger.EndPass(PassName.Decoding);
 
+            PreparePool(highCq);
+
             Logger.StartPass(PassName.Translation);
 
             EmitSynchronization(context);
@@ -240,23 +239,33 @@ namespace ARMeilleure.Translation
             if (Ptc.State == PtcState.Disabled)
             {
                 func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options);
+
+                ReturnPool(highCq);
             }
-            else
+            else using (PtcInfo ptcInfo = new PtcInfo())
             {
-                using (PtcInfo ptcInfo = new PtcInfo())
-                {
-                    func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options, ptcInfo);
+                func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options, ptcInfo);
 
-                    Ptc.WriteInfoCodeReloc(address, funcSize, highCq, ptcInfo);
-                }
+                ReturnPool(highCq);
+
+                Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, highCq, ptcInfo);
             }
 
-            ReturnOperandPool(highCq);
-            ReturnOperationPool(highCq);
-
             return new TranslatedFunction(func, funcSize, highCq);
         }
 
+        internal static void PreparePool(bool highCq)
+        {
+            PrepareOperandPool(highCq);
+            PrepareOperationPool(highCq);
+        }
+
+        internal static void ReturnPool(bool highCq)
+        {
+            ReturnOperandPool(highCq);
+            ReturnOperationPool(highCq);
+        }
+
         internal static void ResetPools()
         {
             ResetOperandPools();
diff --git a/Ryujinx.Memory/MemoryBlock.cs b/Ryujinx.Memory/MemoryBlock.cs
index 198e275b4f..fadd50d44d 100644
--- a/Ryujinx.Memory/MemoryBlock.cs
+++ b/Ryujinx.Memory/MemoryBlock.cs
@@ -40,6 +40,8 @@ namespace Ryujinx.Memory
             }
 
             Size = size;
+
+            GC.AddMemoryPressure((long)Size);
         }
 
         /// <summary>
@@ -281,6 +283,8 @@ namespace Ryujinx.Memory
             if (ptr != IntPtr.Zero)
             {
                 MemoryManagement.Free(ptr);
+
+                GC.RemoveMemoryPressure((long)Size);
             }
         }