| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- using ARMeilleure.Memory;
- using Ryujinx.Common;
- using Ryujinx.Memory;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Threading;
- namespace Ryujinx.Cpu.LightningJit.Cache
- {
- class NoWxCache : IDisposable
- {
- private const int CodeAlignment = 4; // Bytes.
- private const int SharedCacheSize = 2047 * 1024 * 1024;
- private const int LocalCacheSize = 256 * 1024 * 1024;
- // How many calls to the same function we allow until we pad the shared cache to force the function to become available there
- // and allow the guest to take the fast path.
- private const int MinCallsForPad = 8;
- private class MemoryCache : IDisposable
- {
- private readonly ReservedRegion _region;
- private readonly CacheMemoryAllocator _cacheAllocator;
- public CacheMemoryAllocator Allocator => _cacheAllocator;
- public nint Pointer => _region.Block.Pointer;
- public MemoryCache(IJitMemoryAllocator allocator, ulong size)
- {
- _region = new(allocator, size);
- _cacheAllocator = new((int)size);
- }
- public int Allocate(int codeSize)
- {
- codeSize = AlignCodeSize(codeSize);
- int allocOffset = _cacheAllocator.Allocate(codeSize);
- if (allocOffset < 0)
- {
- throw new OutOfMemoryException("JIT Cache exhausted.");
- }
- _region.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
- return allocOffset;
- }
- public void Free(int offset, int size)
- {
- _cacheAllocator.Free(offset, size);
- }
- public void ReprotectAsRw(int offset, int size)
- {
- Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
- Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
- _region.Block.MapAsRw((ulong)offset, (ulong)size);
- }
- public void ReprotectAsRx(int offset, int size)
- {
- Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
- Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
- _region.Block.MapAsRx((ulong)offset, (ulong)size);
- if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
- {
- JitSupportDarwin.SysIcacheInvalidate(_region.Block.Pointer + offset, size);
- }
- else
- {
- throw new PlatformNotSupportedException();
- }
- }
- private static int AlignCodeSize(int codeSize)
- {
- return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _region.Dispose();
- _cacheAllocator.Clear();
- }
- }
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
- }
- private readonly IStackWalker _stackWalker;
- private readonly Translator _translator;
- private readonly MemoryCache _sharedCache;
- private readonly MemoryCache _localCache;
- private readonly PageAlignedRangeList _pendingMap;
- private readonly Lock _lock = new();
- class ThreadLocalCacheEntry
- {
- public readonly int Offset;
- public readonly int Size;
- public readonly nint FuncPtr;
- private int _useCount;
- public ThreadLocalCacheEntry(int offset, int size, nint funcPtr)
- {
- Offset = offset;
- Size = size;
- FuncPtr = funcPtr;
- _useCount = 0;
- }
- public int IncrementUseCount()
- {
- return ++_useCount;
- }
- }
- [ThreadStatic]
- private static Dictionary<ulong, ThreadLocalCacheEntry> _threadLocalCache;
- public NoWxCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator)
- {
- _stackWalker = stackWalker;
- _translator = translator;
- _sharedCache = new(allocator, SharedCacheSize);
- _localCache = new(allocator, LocalCacheSize);
- _pendingMap = new(_sharedCache.ReprotectAsRx, RegisterFunction);
- }
- public unsafe nint Map(nint framePointer, ReadOnlySpan<byte> code, ulong guestAddress, ulong guestSize)
- {
- if (TryGetThreadLocalFunction(guestAddress, out nint funcPtr))
- {
- return funcPtr;
- }
- lock (_lock)
- {
- if (!_pendingMap.Has(guestAddress) && !_translator.Functions.ContainsKey(guestAddress))
- {
- int funcOffset = _sharedCache.Allocate(code.Length);
- funcPtr = _sharedCache.Pointer + funcOffset;
- code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
- TranslatedFunction function = new(funcPtr, guestSize);
- _pendingMap.Add(funcOffset, code.Length, guestAddress, function);
- }
- ClearThreadLocalCache(framePointer);
- return AddThreadLocalFunction(code, guestAddress);
- }
- }
- public unsafe nint MapPageAligned(ReadOnlySpan<byte> code)
- {
- lock (_lock)
- {
- // Ensure we will get an aligned offset from the allocator.
- _pendingMap.Pad(_sharedCache.Allocator);
- int sizeAligned = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
- int funcOffset = _sharedCache.Allocate(sizeAligned);
- Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
- nint funcPtr = _sharedCache.Pointer + funcOffset;
- code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
- _sharedCache.ReprotectAsRx(funcOffset, sizeAligned);
- return funcPtr;
- }
- }
- private bool TryGetThreadLocalFunction(ulong guestAddress, out nint funcPtr)
- {
- if ((_threadLocalCache ??= new()).TryGetValue(guestAddress, out ThreadLocalCacheEntry entry))
- {
- if (entry.IncrementUseCount() >= MinCallsForPad)
- {
- // Function is being called often, let's make it available in the shared cache so that the guest code
- // can take the fast path and stop calling the emulator to get the function from the thread local cache.
- // To do that we pad all "pending" function until they complete a page of memory, allowing us to reprotect them as RX.
- lock (_lock)
- {
- _pendingMap.Pad(_sharedCache.Allocator);
- }
- }
- funcPtr = entry.FuncPtr;
- return true;
- }
- funcPtr = nint.Zero;
- return false;
- }
- private void ClearThreadLocalCache(nint framePointer)
- {
- // Try to delete functions that are already on the shared cache
- // and no longer being executed.
- if (_threadLocalCache == null)
- {
- return;
- }
- IEnumerable<ulong> callStack = _stackWalker.GetCallStack(
- framePointer,
- _localCache.Pointer,
- LocalCacheSize,
- _sharedCache.Pointer,
- SharedCacheSize);
- List<(ulong, ThreadLocalCacheEntry)> toDelete = [];
- foreach ((ulong address, ThreadLocalCacheEntry entry) in _threadLocalCache)
- {
- // We only want to delete if the function is already on the shared cache,
- // otherwise we will keep translating the same function over and over again.
- bool canDelete = !_pendingMap.Has(address);
- if (!canDelete)
- {
- continue;
- }
- // We can only delete if the function is not part of the current thread call stack,
- // otherwise we will crash the program when the thread returns to it.
- foreach (ulong funcAddress in callStack)
- {
- if (funcAddress >= (ulong)entry.FuncPtr && funcAddress < (ulong)entry.FuncPtr + (ulong)entry.Size)
- {
- canDelete = false;
- break;
- }
- }
- if (canDelete)
- {
- toDelete.Add((address, entry));
- }
- }
- int pageSize = (int)MemoryBlock.GetPageSize();
- foreach ((ulong address, ThreadLocalCacheEntry entry) in toDelete)
- {
- _threadLocalCache.Remove(address);
- int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
- _localCache.Free(entry.Offset, sizeAligned);
- _localCache.ReprotectAsRw(entry.Offset, sizeAligned);
- }
- }
- public void ClearEntireThreadLocalCache()
- {
- // Thread is exiting, delete everything.
- if (_threadLocalCache == null)
- {
- return;
- }
- int pageSize = (int)MemoryBlock.GetPageSize();
- foreach ((_, ThreadLocalCacheEntry entry) in _threadLocalCache)
- {
- int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
- _localCache.Free(entry.Offset, sizeAligned);
- _localCache.ReprotectAsRw(entry.Offset, sizeAligned);
- }
- _threadLocalCache.Clear();
- _threadLocalCache = null;
- }
- private unsafe nint AddThreadLocalFunction(ReadOnlySpan<byte> code, ulong guestAddress)
- {
- int alignedSize = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
- int funcOffset = _localCache.Allocate(alignedSize);
- Debug.Assert((funcOffset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
- nint funcPtr = _localCache.Pointer + funcOffset;
- code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
- (_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr));
- _localCache.ReprotectAsRx(funcOffset, alignedSize);
- return funcPtr;
- }
- private void RegisterFunction(ulong address, TranslatedFunction func)
- {
- TranslatedFunction oldFunc = _translator.Functions.GetOrAdd(address, func.GuestSize, func);
- Debug.Assert(oldFunc == func);
- _translator.RegisterFunction(address, func);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _localCache.Dispose();
- _sharedCache.Dispose();
- }
- }
- public void Dispose()
- {
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
- }
- }
|