diff --git a/ARMeilleure/Translation/JitCache.cs b/ARMeilleure/Translation/JitCache.cs index 73f04a966d..ad54598792 100644 --- a/ARMeilleure/Translation/JitCache.cs +++ b/ARMeilleure/Translation/JitCache.cs @@ -17,7 +17,7 @@ namespace ARMeilleure.Translation private static IntPtr _basePointer; - private static int _offset; + private static JitCacheMemoryAllocator _allocator; private static List _cacheEntries; @@ -27,14 +27,20 @@ namespace ARMeilleure.Translation { _basePointer = MemoryManagement.Allocate(CacheSize); + int startOffset = 0; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { JitUnwindWindows.InstallFunctionTableHandler(_basePointer, CacheSize); // The first page is used for the table based SEH structs. - _offset = PageSize; + startOffset = PageSize; } + ReprotectRange(startOffset, CacheSize - startOffset); + + _allocator = new JitCacheMemoryAllocator(CacheSize, startOffset); + _cacheEntries = new List(); _lock = new object(); @@ -52,8 +58,6 @@ namespace ARMeilleure.Translation Marshal.Copy(code, 0, funcPtr, code.Length); - ReprotectRange(funcOffset, code.Length); - Add(new JitCacheEntry(funcOffset, code.Length, func.UnwindInfo)); return funcPtr; @@ -76,16 +80,7 @@ namespace ARMeilleure.Translation { IntPtr funcPtr = _basePointer + pageStart; - MemoryManagement.Reprotect(funcPtr, (ulong)fullPagesSize, MemoryProtection.ReadAndExecute); - } - - int remaining = endOffs - pageEnd; - - if (remaining != 0) - { - IntPtr funcPtr = _basePointer + pageEnd; - - MemoryManagement.Reprotect(funcPtr, (ulong)remaining, MemoryProtection.ReadWriteExecute); + MemoryManagement.Reprotect(funcPtr, (ulong)fullPagesSize, MemoryProtection.ReadWriteExecute); } } @@ -93,18 +88,28 @@ namespace ARMeilleure.Translation { codeSize = checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); - int allocOffset = _offset; - - _offset += codeSize; - - if ((ulong)(uint)_offset > CacheSize) - { - throw new OutOfMemoryException(); - } + int allocOffset = _allocator.Allocate(codeSize); return allocOffset; } + public static void Free(ulong address) + { + ulong offset = address - (ulong)_basePointer; + + lock (_lock) + { + if (TryFind((int)offset, out JitCacheEntry entry)) + { + _cacheEntries.Remove(entry); + + int size = checked(entry.Size + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + + _allocator.Free((int)entry.Offset, size); + } + } + } + private static void Add(JitCacheEntry entry) { _cacheEntries.Add(entry); diff --git a/ARMeilleure/Translation/JitCacheMemoryAllocator.cs b/ARMeilleure/Translation/JitCacheMemoryAllocator.cs new file mode 100644 index 0000000000..4744b70fb4 --- /dev/null +++ b/ARMeilleure/Translation/JitCacheMemoryAllocator.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Translation +{ + class JitCacheMemoryAllocator + { + private int _size; + + private LinkedList<(int Start, int End)> _memoryRanges; + + public JitCacheMemoryAllocator(int size, int startPosition) + { + _size = size; + + _memoryRanges = new LinkedList<(int start, int end)>(); + + _memoryRanges.AddFirst((startPosition, startPosition - 1)); + } + + public int Allocate(int size) + { + var node = _memoryRanges.First; + + int offset; + + while (true) + { + if (node.Value.End > (_size - 1) - size) + { + throw new OutOfMemoryException(); + } + + if (node.Next == null) + { + offset = node.Value.End + 1; + + node.Value = (node.Value.Start, node.Value.End + size); + + break; + } + else + { + if (node.Next.Value.Start - node.Value.End <= 1) + { + node.Value = (node.Value.Start, node.Next.Value.End); + + _memoryRanges.Remove(node.Next); + } + + if (node.Next.Value.Start - size > node.Value.End) + { + offset = node.Value.End + 1; + + if (node.Next.Value.Start - offset == size) + { + node.Value = (node.Value.Start, node.Next.Value.End); + + _memoryRanges.Remove(node.Next); + + break; + } + + node.Value = (node.Value.Start, offset + size - 1); + + break; + } + + node = node.Next; + } + } + + return offset; + } + + public void Free(int offset, int size) + { + if ((uint)offset >= (ulong)_size) + { + throw new ArgumentOutOfRangeException(); + } + + var node = _memoryRanges.First; + + while (true) + { + if (node == null) + { + throw new ArgumentOutOfRangeException(); + } + + if (offset <= node.Value.End) + { + int newRangeStart = offset + size; + + _memoryRanges.AddAfter(node, (newRangeStart, node.Value.End)); + + break; + } + + node = node.Next; + } + + node.Value = (node.Value.Start, offset - 1); + } + + } +} diff --git a/ARMeilleure/Translation/TranslatedFunction.cs b/ARMeilleure/Translation/TranslatedFunction.cs index 06069cf8fe..831c99d04e 100644 --- a/ARMeilleure/Translation/TranslatedFunction.cs +++ b/ARMeilleure/Translation/TranslatedFunction.cs @@ -1,17 +1,24 @@ +using System; using System.Threading; +using System.Runtime.InteropServices; namespace ARMeilleure.Translation { class TranslatedFunction { + public ulong Pointer => (ulong)Marshal.GetFunctionPointerForDelegate(_func); + + public int EntryCount; + private const int MinCallsForRejit = 100; private GuestFunction _func; - private bool _rejit; - private int _callCount; + private ulong _address; + private bool _rejit; + private int _callCount; - public TranslatedFunction(GuestFunction func, bool rejit) + public TranslatedFunction(GuestFunction func, ulong address, bool rejit) { _func = func; _rejit = rejit; @@ -19,7 +26,16 @@ namespace ARMeilleure.Translation public ulong Execute(State.ExecutionContext context) { - return _func(context.NativeContextPtr); + if (Interlocked.Increment(ref EntryCount) == 0) + { + return _address; + } + + var nextAddress = _func(context.NativeContextPtr); + + Interlocked.Decrement(ref EntryCount); + + return nextAddress; } public bool ShouldRejit() diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs index 3008303e76..6d19320136 100644 --- a/ARMeilleure/Translation/Translator.cs +++ b/ARMeilleure/Translation/Translator.cs @@ -6,6 +6,7 @@ using ARMeilleure.Memory; using ARMeilleure.State; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using static ARMeilleure.IntermediateRepresentation.OperandHelper; @@ -20,6 +21,8 @@ namespace ARMeilleure.Translation private ConcurrentDictionary _funcs; + private Queue _oldFunctions; + private PriorityQueue _backgroundQueue; private AutoResetEvent _backgroundTranslatorEvent; @@ -35,6 +38,8 @@ namespace ARMeilleure.Translation _backgroundQueue = new PriorityQueue(2); _backgroundTranslatorEvent = new AutoResetEvent(false); + + _oldFunctions = new Queue(); } private void TranslateQueuedSubs() @@ -45,7 +50,23 @@ namespace ARMeilleure.Translation { TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true); - _funcs.AddOrUpdate(request.Address, func, (key, oldFunc) => func); + _funcs.AddOrUpdate(request.Address, func, (key, oldFunc) => + { + _oldFunctions.Enqueue(oldFunc); + + return func; + }); + } + else if (_oldFunctions.TryDequeue(out TranslatedFunction function)) + { + if (Interlocked.CompareExchange(ref function.EntryCount, -1, 0) != 0) + { + _oldFunctions.Enqueue(function); + } + else + { + JitCache.Free(function.Pointer); + } } else { @@ -161,7 +182,7 @@ namespace ARMeilleure.Translation GuestFunction func = Compiler.Compile(cfg, argTypes, OperandType.I64, options); - return new TranslatedFunction(func, rejit: !highCq); + return new TranslatedFunction(func, address, rejit: !highCq); } private static ControlFlowGraph EmitAndGetCFG(ArmEmitterContext context, Block[] blocks)