instanced jit cache (#152)

- jit caches are no longer static.
- JitUnwindWindows now supports multiple jit caches.
- Jit caches should no longer cause crashes due to entry offset collisions.
- cpu tests now run concurrently.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/152
This commit is contained in:
LotP
2026-06-30 13:40:18 +00:00
committed by LotP
parent 59a6a3a5f7
commit 6fb71b08ae
26 changed files with 345 additions and 276 deletions

View File

@@ -8,7 +8,7 @@ namespace ARMeilleure.CodeGen
/// <summary> /// <summary>
/// Represents a compiled function. /// Represents a compiled function.
/// </summary> /// </summary>
readonly struct CompiledFunction public readonly struct CompiledFunction
{ {
/// <summary> /// <summary>
/// Gets the machine code of the <see cref="CompiledFunction"/>. /// Gets the machine code of the <see cref="CompiledFunction"/>.
@@ -44,10 +44,11 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function. /// <typeparamref name="T"/> pointing to the mapped function.
/// </summary> /// </summary>
/// <typeparam name="T">Type of delegate</typeparam> /// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns> /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T Map<T>() public T Map<T>(JitCache jitCache)
{ {
return MapWithPointer<T>(out _); return MapWithPointer<T>(jitCache, out _);
} }
/// <summary> /// <summary>
@@ -55,11 +56,12 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function. /// <typeparamref name="T"/> pointing to the mapped function.
/// </summary> /// </summary>
/// <typeparam name="T">Type of delegate</typeparam> /// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <param name="codePointer">Pointer to the function code in memory</param> /// <param name="codePointer">Pointer to the function code in memory</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns> /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T MapWithPointer<T>(out nint codePointer) public T MapWithPointer<T>(JitCache jitCache, out nint codePointer)
{ {
codePointer = JitCache.Map(this); codePointer = jitCache.Map(this);
return Marshal.GetDelegateForFunctionPointer<T>(codePointer); return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
} }

View File

@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents a relocation. /// Represents a relocation.
/// </summary> /// </summary>
readonly struct RelocEntry public readonly struct RelocEntry
{ {
public const int Stride = 13; // Bytes. public const int Stride = 13; // Bytes.

View File

@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents relocation information about a <see cref="CompiledFunction"/>. /// Represents relocation information about a <see cref="CompiledFunction"/>.
/// </summary> /// </summary>
readonly struct RelocInfo public readonly struct RelocInfo
{ {
/// <summary> /// <summary>
/// Gets an empty <see cref="RelocInfo"/>. /// Gets an empty <see cref="RelocInfo"/>.

View File

@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents a symbol. /// Represents a symbol.
/// </summary> /// </summary>
readonly struct Symbol public readonly struct Symbol
{ {
private readonly ulong _value; private readonly ulong _value;

View File

@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Types of <see cref="Symbol"/>. /// Types of <see cref="Symbol"/>.
/// </summary> /// </summary>
enum SymbolType : byte public enum SymbolType : byte
{ {
/// <summary> /// <summary>
/// Refers to nothing, i.e no symbol. /// Refers to nothing, i.e no symbol.

View File

@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
struct UnwindInfo public struct UnwindInfo
{ {
public const int Stride = 4; // Bytes. public const int Stride = 4; // Bytes.

View File

@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
enum UnwindPseudoOp public enum UnwindPseudoOp
{ {
PushReg = 0, PushReg = 0,
SetFrame = 1, SetFrame = 1,

View File

@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
struct UnwindPushEntry public struct UnwindPushEntry
{ {
public const int Stride = 16; // Bytes. public const int Stride = 16; // Bytes.

View File

@@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.Memory namespace ARMeilleure.Memory
{ {
public class ReservedRegion public class ReservedRegion : IDisposable
{ {
public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public const int DefaultGranularity = 65536; // Mapping granularity in Windows.

View File

@@ -1,5 +1,6 @@
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using ARMeilleure.Translation.Cache;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory; using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -17,7 +18,7 @@ namespace ARMeilleure.Signal
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState); public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr); public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr);
public static DebugPartialUnmap GenerateDebugPartialUnmap() public static DebugPartialUnmap GenerateDebugPartialUnmap(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -31,10 +32,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(jitCache);
} }
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(nint structPtr) public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(JitCache jitCache, nint structPtr)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -48,10 +49,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(jitCache);
} }
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop() public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -77,7 +78,7 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(); return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(jitCache);
} }
} }
} }

View File

@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ARMeilleure.Translation.Cache namespace ARMeilleure.Translation.Cache
{ {
readonly struct CacheEntry : IComparable<CacheEntry> public readonly struct CacheEntry : IComparable<CacheEntry>
{ {
public int Offset { get; } public int Offset { get; }
public int Size { get; } public int Size { get; }

View File

@@ -23,11 +23,30 @@ namespace ARMeilleure.Translation.Cache
} }
} }
private readonly int _regionSize;
private int _regionCount;
private readonly List<MemoryBlock> _blocks = []; private readonly List<MemoryBlock> _blocks = [];
public CacheMemoryAllocator(int capacity) public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
{ {
_blocks.Add(new MemoryBlock(0, capacity)); _regionCount = 0;
_regionSize = regionSize;
for (; initialRegionCount > 0; initialRegionCount--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public void AddNewBlocks(int count)
{
for (; count > 0; count--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
} }
public int Allocate(int size) public int Allocate(int size)
@@ -66,12 +85,13 @@ namespace ARMeilleure.Translation.Cache
index = ~index; index = ~index;
} }
if (index < _blocks.Count) int endOffs = block.Offset + block.Size;
// Don't merge blocks from different allocations
if (index < _blocks.Count && endOffs % _regionSize != 0)
{ {
MemoryBlock next = _blocks[index]; MemoryBlock next = _blocks[index];
int endOffs = block.Offset + block.Size;
if (next.Offset == endOffs) if (next.Offset == endOffs)
{ {
block = new MemoryBlock(block.Offset, block.Size + next.Size); block = new MemoryBlock(block.Offset, block.Size + next.Size);
@@ -79,7 +99,8 @@ namespace ARMeilleure.Translation.Cache
} }
} }
if (index > 0) // Don't merge blocks from different allocations
if (index > 0 && block.Offset % _regionSize != 0)
{ {
MemoryBlock prev = _blocks[index - 1]; MemoryBlock prev = _blocks[index - 1];

View File

@@ -14,62 +14,35 @@ using System.Threading;
namespace ARMeilleure.Translation.Cache namespace ARMeilleure.Translation.Cache
{ {
static partial class JitCache public partial class JitCache : IDisposable
{ {
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 1; private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 256 * 1024 * 1024; private const uint CacheSize = 256 * 1024 * 1024;
private static JitCacheInvalidation _jitCacheInvalidator; private readonly JitCacheInvalidation _jitCacheInvalidator;
private static readonly List<CacheMemoryAllocator> _cacheAllocators = []; private readonly CacheMemoryAllocator _cacheAllocator;
private static readonly List<CacheEntry> _cacheEntries = []; private readonly List<CacheEntry> _cacheEntries = [];
private static readonly Lock _lock = new(); private readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = []; private readonly List<ReservedRegion> _jitRegions = [];
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)] [LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize); private static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
public static void Initialize(IJitMemoryAllocator allocator) public JitCache(IJitMemoryAllocator allocator)
{ {
lock (_lock) lock (_lock)
{ {
if (_initialized) _jitRegions.Add(new(allocator, CacheSize));
{
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++) _cacheAllocator = new((int)CacheSize);
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
}
else
{
_initialized = true;
}
_activeRegionIndex = 0;
ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion);
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
@@ -79,23 +52,20 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler( JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize) this, _jitRegions[0].Pointer, CacheSize, _jitRegions[0].Pointer + Allocate(_pageSize)
); );
} }
} }
} }
public static nint Map(CompiledFunction func) public nint Map(CompiledFunction func)
{ {
byte[] code = func.Code; byte[] code = func.Code;
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length); int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; nint funcPtr = GetFunctionPtr(funcOffset);
nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -109,9 +79,9 @@ namespace ARMeilleure.Translation.Cache
} }
else else
{ {
ReprotectAsWritable(targetRegion, funcOffset, code.Length); ReprotectAsWritable(funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length); Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length); ReprotectAsExecutable(funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -129,25 +99,24 @@ namespace ARMeilleure.Translation.Cache
} }
} }
public static void Unmap(nint pointer) public void Unmap(nint pointer)
{ {
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized); for (int i = 0; i < _jitRegions.Count; i++)
foreach (ReservedRegion region in _jitRegions)
{ {
if (pointer.ToInt64() < region.Pointer.ToInt64() || ReservedRegion jitRegion = _jitRegions[i];
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) if (pointer.ToInt64() < jitRegion.Pointer.ToInt64() ||
pointer.ToInt64() >= (jitRegion.Pointer + (nint)CacheSize).ToInt64())
{ {
continue; continue;
} }
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64()); int funcOffset = (int)(pointer.ToInt64() - jitRegion.Pointer.ToInt64() + i * CacheSize);
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size)); _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex); _cacheEntries.RemoveAt(entryIndex);
} }
@@ -156,53 +125,63 @@ namespace ARMeilleure.Translation.Cache
} }
} }
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size) private void ReprotectAsWritable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size) private void ReprotectAsExecutable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static int Allocate(int codeSize) private int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize); int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0) if (allocOffset >= 0)
{ {
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset; return allocOffset;
} }
int exhaustedRegion = _activeRegionIndex; _cacheAllocator.AddNewBlocks(1);
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize); ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
_jitRegions.Add(newRegion); _jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation)."); allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffsetNew < 0)
{ {
throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
} }
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize); GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffsetNew; return allocOffset;
}
private nint GetFunctionPtr(int offset)
{
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
}
private ReservedRegion GetRegion(int offset)
{
int index = offset / (int)CacheSize;
return _jitRegions[index];
} }
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
@@ -210,7 +189,7 @@ namespace ARMeilleure.Translation.Cache
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
} }
private static void Add(int offset, int size, UnwindInfo unwindInfo) private void Add(int offset, int size, UnwindInfo unwindInfo)
{ {
CacheEntry entry = new(offset, size, unwindInfo); CacheEntry entry = new(offset, size, unwindInfo);
@@ -224,11 +203,9 @@ namespace ARMeilleure.Translation.Cache
_cacheEntries.Insert(index, entry); _cacheEntries.Insert(index, entry);
} }
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{ {
lock (_lock) lock (_lock)
{
foreach (ReservedRegion _ in _jitRegions)
{ {
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
@@ -244,11 +221,23 @@ namespace ARMeilleure.Translation.Cache
return true; return true;
} }
} }
}
entry = default; entry = default;
entryIndex = 0; entryIndex = 0;
return false; return false;
} }
public void Dispose()
{
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(_jitRegions[0].Pointer);
}
foreach (ReservedRegion jitRegion in _jitRegions)
{
jitRegion.Dispose();
}
}
} }
} }

View File

@@ -2,6 +2,8 @@
using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.Unwinding;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -27,6 +29,29 @@ namespace ARMeilleure.Translation.Cache
public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize]; public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize];
} }
private unsafe struct InternalFunctionHandler
{
public InternalFunctionHandler(JitCache jitCache, nint workBufferPtr)
{
_jitCache = jitCache;
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
}
readonly JitCache _jitCache;
readonly RuntimeFunction* _runtimeFunction;
readonly UnwindInfo* _unwindInfo;
public RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{
return JitUnwindWindows.FunctionTableHandler(_jitCache, _runtimeFunction, _unwindInfo, controlPc, context);
}
}
private enum UnwindOp private enum UnwindOp
{ {
PushNonvol = 0, PushNonvol = 0,
@@ -59,27 +84,28 @@ namespace ARMeilleure.Translation.Cache
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction; private static readonly int _sizeOfRuntimeFunction;
private unsafe static RuntimeFunction* _runtimeFunction; private static readonly ConcurrentDictionary<ulong, InternalFunctionHandler> _functionTableHandlers = new();
private unsafe static UnwindInfo* _unwindInfo; static JitUnwindWindows()
{
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
}
public static void InstallFunctionTableHandler(nint codeCachePointer, uint codeCacheLength, nint workBufferPtr) public static void InstallFunctionTableHandler(JitCache jitCache, nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
{ {
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
bool result; bool result;
InternalFunctionHandler handler;
unsafe unsafe
{ {
_runtimeFunction = (RuntimeFunction*)workBufferPtr; handler = new InternalFunctionHandler(jitCache, workBufferPtr);
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction); _getRuntimeFunctionCallback = handler.FunctionTableHandler;
_getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler);
result = RtlInstallFunctionTableCallback( result = RtlInstallFunctionTableCallback(
codeCachePtr | 3, codeCachePtr | 3,
@@ -94,6 +120,8 @@ namespace ARMeilleure.Translation.Cache
{ {
throw new InvalidOperationException("Failure installing function table callback."); throw new InvalidOperationException("Failure installing function table callback.");
} }
_functionTableHandlers.TryAdd(codeCachePtr, handler);
} }
public static void RemoveFunctionTableHandler(nint codeCachePointer) public static void RemoveFunctionTableHandler(nint codeCachePointer)
@@ -111,24 +139,26 @@ namespace ARMeilleure.Translation.Cache
{ {
throw new InvalidOperationException("Failure removing function table callback."); throw new InvalidOperationException("Failure removing function table callback.");
} }
_functionTableHandlers.Remove(codeCachePtr, out _);
} }
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context) private static unsafe RuntimeFunction* FunctionTableHandler(JitCache jitCache, RuntimeFunction* runtimeFunction, UnwindInfo* unwindInfo, ulong controlPc, nint context)
{ {
int offset = (int)((long)controlPc - context.ToInt64()); int offset = (int)((long)controlPc - context.ToInt64());
if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _)) if (!jitCache.TryFind(offset, out CacheEntry funcEntry, out _))
{ {
return null; // Not found. return null; // Not found.
} }
CodeGen.Unwinding.UnwindInfo unwindInfo = funcEntry.UnwindInfo; CodeGen.Unwinding.UnwindInfo funcUnwindInfo = funcEntry.UnwindInfo;
int codeIndex = 0; int codeIndex = 0;
for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) for (int index = funcUnwindInfo.PushEntries.Length - 1; index >= 0; index--)
{ {
UnwindPushEntry entry = unwindInfo.PushEntries[index]; UnwindPushEntry entry = funcUnwindInfo.PushEntries[index];
switch (entry.PseudoOp) switch (entry.PseudoOp)
{ {
@@ -140,14 +170,14 @@ namespace ARMeilleure.Translation.Cache
if (stackOffset <= 0xFFFF0) if (stackOffset <= 0xFFFF0)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
} }
else else
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
} }
break; break;
@@ -161,18 +191,18 @@ namespace ARMeilleure.Translation.Cache
if (allocSize <= 128) if (allocSize <= 128)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
} }
else if (allocSize <= 0x7FFF8) else if (allocSize <= 0x7FFF8)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
} }
else else
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
} }
break; break;
@@ -180,7 +210,7 @@ namespace ARMeilleure.Translation.Cache
case UnwindPseudoOp.PushReg: case UnwindPseudoOp.PushReg:
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
break; break;
} }
@@ -192,16 +222,16 @@ namespace ARMeilleure.Translation.Cache
Debug.Assert(codeIndex <= MaxUnwindCodesArraySize); Debug.Assert(codeIndex <= MaxUnwindCodesArraySize);
_unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler. unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
_unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize; unwindInfo->SizeOfProlog = (byte)funcUnwindInfo.PrologSize;
_unwindInfo->CountOfUnwindCodes = (byte)codeIndex; unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
_unwindInfo->FrameRegister = 0; unwindInfo->FrameRegister = 0;
_runtimeFunction->BeginAddress = (uint)funcEntry.Offset; runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
_runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size); runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
_runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction; runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
return _runtimeFunction; return runtimeFunction;
} }
private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo) private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo)

View File

@@ -4,6 +4,7 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common; using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using Humanizer; using Humanizer;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@@ -33,7 +34,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project. private const uint InternalVersion = 7016; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0"; private const string ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";
@@ -51,6 +52,8 @@ namespace ARMeilleure.Translation.PTC
public PtcProfiler Profiler { get; } public PtcProfiler Profiler { get; }
private readonly JitCache _jitCache;
// Carriers. // Carriers.
private MemoryStream _infosStream; private MemoryStream _infosStream;
private List<byte[]> _codesList; private List<byte[]> _codesList;
@@ -81,7 +84,7 @@ namespace ARMeilleure.Translation.PTC
private volatile int _translateTotalCount; private volatile int _translateTotalCount;
public event Action<PtcLoadingState, int, int> PtcStateChanged; public event Action<PtcLoadingState, int, int> PtcStateChanged;
public Ptc() public Ptc(JitCache jitCache)
{ {
Profiler = new PtcProfiler(this); Profiler = new PtcProfiler(this);
@@ -92,6 +95,8 @@ namespace ARMeilleure.Translation.PTC
_waitEvent = new ManualResetEvent(true); _waitEvent = new ManualResetEvent(true);
_jitCache = jitCache;
_disposed = false; _disposed = false;
TitleIdText = TitleIdTextDefault; TitleIdText = TitleIdTextDefault;
@@ -782,7 +787,7 @@ namespace ARMeilleure.Translation.PTC
return new UnwindInfo(pushEntries, prologueSize); return new UnwindInfo(pushEntries, prologueSize);
} }
private static TranslatedFunction FastTranslate( private TranslatedFunction FastTranslate(
byte[] code, byte[] code,
Counter<uint> callCounter, Counter<uint> callCounter,
ulong guestSize, ulong guestSize,
@@ -790,7 +795,7 @@ namespace ARMeilleure.Translation.PTC
bool highCq) bool highCq)
{ {
CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty); CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty);
GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(out nint gFuncPointer); GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(_jitCache, out nint gFuncPointer);
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq); return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
} }

View File

@@ -24,6 +24,7 @@ namespace ARMeilleure.Translation
private readonly IJitMemoryAllocator _allocator; private readonly IJitMemoryAllocator _allocator;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
public readonly JitCache JitCache;
private readonly Ptc _ptc; private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; } internal TranslatorCache<TranslatedFunction> Functions { get; }
@@ -43,16 +44,17 @@ namespace ARMeilleure.Translation
_oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>(); _oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
_ptc = new Ptc(); JitCache = new JitCache(allocator);
_ptc = new Ptc(JitCache);
Queue = new TranslatorQueue(); Queue = new TranslatorQueue();
JitCache.Initialize(allocator);
CountTable = new EntryTable<uint>(); CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>(); Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable; FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable); Stubs = new TranslatorStubs(JitCache, FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
} }
@@ -167,6 +169,7 @@ namespace ARMeilleure.Translation
} }
ClearJitCache(); ClearJitCache();
JitCache.Dispose();
Stubs.Dispose(); Stubs.Dispose();
FunctionTable.Dispose(); FunctionTable.Dispose();
@@ -305,7 +308,7 @@ namespace ARMeilleure.Translation
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
} }
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out nint funcPointer); GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(JitCache, out nint funcPointer);
Allocators.ResetAll(); Allocators.ResetAll();

View File

@@ -14,6 +14,8 @@ namespace ARMeilleure.Translation
/// </summary> /// </summary>
class TranslatorStubs : IDisposable class TranslatorStubs : IDisposable
{ {
private readonly JitCache _jitCache;
private readonly Lazy<nint> _slowDispatchStub; private readonly Lazy<nint> _slowDispatchStub;
private bool _disposed; private bool _disposed;
@@ -83,12 +85,15 @@ namespace ARMeilleure.Translation
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified /// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance. /// <see cref="Translator"/> instance.
/// </summary> /// </summary>
/// <param name="jitCache">Jit cache to map functions in</param>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param> /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(IAddressTable<ulong> functionTable) public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable)
{ {
ArgumentNullException.ThrowIfNull(functionTable); ArgumentNullException.ThrowIfNull(functionTable);
_jitCache = jitCache;
_functionTable = functionTable; _functionTable = functionTable;
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
@@ -115,12 +120,12 @@ namespace ARMeilleure.Translation
{ {
if (_dispatchStub.IsValueCreated) if (_dispatchStub.IsValueCreated)
{ {
JitCache.Unmap(_dispatchStub.Value); _jitCache.Unmap(_dispatchStub.Value);
} }
if (_dispatchLoop.IsValueCreated) if (_dispatchLoop.IsValueCreated)
{ {
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); _jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
} }
_disposed = true; _disposed = true;
@@ -188,7 +193,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(); GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func); return Marshal.GetFunctionPointerForDelegate(func);
} }
@@ -213,7 +218,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(); GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func); return Marshal.GetFunctionPointerForDelegate(func);
} }
@@ -290,7 +295,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.None; OperandType retType = OperandType.None;
OperandType[] argTypes = [OperandType.I64, OperandType.I64]; OperandType[] argTypes = [OperandType.I64, OperandType.I64];
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(); return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(_jitCache);
} }
/// <summary> /// <summary>
@@ -314,7 +319,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = [OperandType.I64, OperandType.I64]; OperandType[] argTypes = [OperandType.I64, OperandType.I64];
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(); return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(_jitCache);
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using ARMeilleure.CodeGen.X86; using ARMeilleure.CodeGen.X86;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory; using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -59,7 +60,7 @@ namespace ARMeilleure.Translation
return context.VectorExtract(OperandType.I32, vec, 0); return context.VectorExtract(OperandType.I32, vec, 0);
} }
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest() public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -141,7 +142,7 @@ namespace ARMeilleure.Translation
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(jitCache);
} }
} }
} }

View File

@@ -24,11 +24,30 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
} }
private readonly int _regionSize;
private int _regionCount;
private readonly List<MemoryBlock> _blocks = []; private readonly List<MemoryBlock> _blocks = [];
public CacheMemoryAllocator(int capacity) public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
{ {
_blocks.Add(new MemoryBlock(0, capacity)); _regionCount = 0;
_regionSize = regionSize;
for (; initialRegionCount > 0; initialRegionCount--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public void AddNewBlocks(int count)
{
for (; count > 0; count--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
} }
public int Allocate(int size) public int Allocate(int size)
@@ -101,12 +120,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
index = ~index; index = ~index;
} }
if (index < _blocks.Count) int endOffs = block.Offset + block.Size;
// Don't merge blocks from different allocations
if (index < _blocks.Count && endOffs % _regionSize != 0)
{ {
MemoryBlock next = _blocks[index]; MemoryBlock next = _blocks[index];
int endOffs = block.Offset + block.Size;
if (next.Offset == endOffs) if (next.Offset == endOffs)
{ {
block = new MemoryBlock(block.Offset, block.Size + next.Size); block = new MemoryBlock(block.Offset, block.Size + next.Size);
@@ -114,7 +134,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
} }
if (index > 0) // Don't merge blocks from different allocations
if (index > 0 && block.Offset % _regionSize != 0)
{ {
MemoryBlock prev = _blocks[index - 1]; MemoryBlock prev = _blocks[index - 1];

View File

@@ -11,76 +11,45 @@ using System.Threading;
namespace Ryujinx.Cpu.LightningJit.Cache namespace Ryujinx.Cpu.LightningJit.Cache
{ {
static partial class JitCache partial class JitCache : IDisposable
{ {
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 1; private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
// TODO: JIT Cache size should be application dependent, not global. private const uint CacheSize = 256 * 1024 * 1024; // Megabytes * Size of Megabytes (since its in bytes).
private const int CacheSize = 1024 * (1024 * 1024); // Megabytes * Size of Megabytes (since its in bytes).
private static JitCacheInvalidation _jitCacheInvalidator; private readonly JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private readonly CacheMemoryAllocator _cacheAllocator;
private static readonly List<CacheEntry> _cacheEntries = []; private readonly List<CacheEntry> _cacheEntries = [];
private static readonly Lock _lock = new(); private readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = [];
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")] private readonly List<ReservedRegion> _jitRegions = [];
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
[SupportedOSPlatform("macos")] public JitCache(IJitMemoryAllocator allocator)
[LibraryImport("libSystem.dylib", EntryPoint = "sys_icache_invalidate")]
internal static partial void SysICacheInvalidate(nint start, nuint len);
[SupportedOSPlatform("linux")]
[LibraryImport("libgcc_s.so.1", EntryPoint = "__clear_cache")]
internal static partial void ClearCache(nint begin, nint end);
public static void Initialize(IJitMemoryAllocator allocator)
{ {
if (_initialized)
{
return;
}
lock (_lock) lock (_lock)
{ {
if (_initialized) _jitRegions.Add(new(allocator, CacheSize));
{
return;
}
ReservedRegion firstRegion = new(allocator, CacheSize); _cacheAllocator = new CacheMemoryAllocator((int)CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
_jitCacheInvalidator = new JitCacheInvalidation(allocator); _jitCacheInvalidator = new JitCacheInvalidation(allocator);
} }
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
_initialized = true;
} }
} }
public unsafe static nint Map(ReadOnlySpan<byte> code) public nint Map(ReadOnlySpan<byte> code)
{ {
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length); int funcOffset = Allocate(code.Length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex]; nint funcPtr = GetFunctionPtr(funcOffset);
nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -94,9 +63,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
else else
{ {
ReprotectAsWritable(targetRegion, funcOffset, code.Length); ReprotectAsWritable(funcOffset, code.Length);
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length); Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length); ReprotectAsExecutable(funcOffset, code.Length);
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
} }
@@ -107,21 +76,20 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
} }
public static void Unmap(nint pointer) public void Unmap(nint pointer)
{ {
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized); for (int i = 0; i < _jitRegions.Count; i++)
foreach (ReservedRegion region in _jitRegions)
{ {
ReservedRegion region = _jitRegions[i];
if (pointer.ToInt64() < region.Pointer.ToInt64() || if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64()) pointer.ToInt64() >= (region.Pointer + (nint)CacheSize).ToInt64())
{ {
continue; continue;
} }
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64()); int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64() + i * CacheSize);
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
@@ -134,59 +102,63 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
} }
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size) private void ReprotectAsWritable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size) private void ReprotectAsExecutable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask; int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static int Allocate(int codeSize) private int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
int allocOffset = _cacheAllocator.Allocate(codeSize); int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0) if (allocOffset >= 0)
{ {
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset; return allocOffset;
} }
}
int exhaustedRegion = _activeRegionIndex; _cacheAllocator.AddNewBlocks(1);
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize); ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
_jitRegions.Add(newRegion); _jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex; allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
{ {
throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
} }
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize); GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffsetNew; return allocOffset;
}
private nint GetFunctionPtr(int offset)
{
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
}
private ReservedRegion GetRegion(int offset)
{
int index = offset / (int)CacheSize;
return _jitRegions[index];
} }
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
@@ -194,7 +166,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
} }
private static void Add(int offset, int size) private void Add(int offset, int size)
{ {
CacheEntry entry = new(offset, size); CacheEntry entry = new(offset, size);
@@ -208,7 +180,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
_cacheEntries.Insert(index, entry); _cacheEntries.Insert(index, entry);
} }
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{ {
lock (_lock) lock (_lock)
{ {
@@ -231,5 +203,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
entryIndex = 0; entryIndex = 0;
return false; return false;
} }
public void Dispose()
{
foreach (ReservedRegion jitRegion in _jitRegions)
{
jitRegion.Dispose();
}
}
} }
} }

View File

@@ -19,6 +19,7 @@ namespace Ryujinx.Cpu.LightningJit
private static bool IsNoWxPlatform => false; private static bool IsNoWxPlatform => false;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
private readonly JitCache _jitCache;
private readonly NoWxCache _noWxCache; private readonly NoWxCache _noWxCache;
private bool _disposed; private bool _disposed;
@@ -39,12 +40,12 @@ namespace Ryujinx.Cpu.LightningJit
} }
else else
{ {
JitCache.Initialize(new JitMemoryAllocator(forJit: true)); _jitCache = new(new JitMemoryAllocator(forJit: true));
} }
Functions = new TranslatorCache<TranslatedFunction>(); Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable; FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable, _noWxCache); Stubs = new TranslatorStubs(_jitCache, FunctionTable, _noWxCache);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
@@ -100,7 +101,7 @@ namespace Ryujinx.Cpu.LightningJit
if (oldFunc != func) if (oldFunc != func)
{ {
JitCache.Unmap(func.FuncPointer); _jitCache.Unmap(func.FuncPointer);
func = oldFunc; func = oldFunc;
} }
@@ -121,7 +122,7 @@ namespace Ryujinx.Cpu.LightningJit
private TranslatedFunction Translate(ulong address, ExecutionMode mode) private TranslatedFunction Translate(ulong address, ExecutionMode mode)
{ {
CompiledFunction func = Compile(address, mode); CompiledFunction func = Compile(address, mode);
nint funcPointer = JitCache.Map(func.Code); nint funcPointer = _jitCache.Map(func.Code);
return new TranslatedFunction(funcPointer, (ulong)func.GuestCodeLength); return new TranslatedFunction(funcPointer, (ulong)func.GuestCodeLength);
} }
@@ -164,14 +165,14 @@ namespace Ryujinx.Cpu.LightningJit
foreach (TranslatedFunction func in functions) foreach (TranslatedFunction func in functions)
{ {
JitCache.Unmap(func.FuncPointer); _jitCache.Unmap(func.FuncPointer);
} }
Functions.Clear(); Functions.Clear();
while (_oldFuncs.TryDequeue(out KeyValuePair<ulong, TranslatedFunction> kv)) while (_oldFuncs.TryDequeue(out KeyValuePair<ulong, TranslatedFunction> kv))
{ {
JitCache.Unmap(kv.Value.FuncPointer); _jitCache.Unmap(kv.Value.FuncPointer);
} }
} }
@@ -188,6 +189,7 @@ namespace Ryujinx.Cpu.LightningJit
else else
{ {
ClearJitCache(); ClearJitCache();
_jitCache.Dispose();
} }
Stubs.Dispose(); Stubs.Dispose();

View File

@@ -19,6 +19,8 @@ namespace Ryujinx.Cpu.LightningJit
{ {
private delegate ulong GetFunctionAddressDelegate(nint framePointer, ulong address); private delegate ulong GetFunctionAddressDelegate(nint framePointer, ulong address);
private readonly JitCache _jitCache;
private readonly Lazy<nint> _slowDispatchStub; private readonly Lazy<nint> _slowDispatchStub;
private bool _disposed; private bool _disposed;
@@ -76,13 +78,16 @@ namespace Ryujinx.Cpu.LightningJit
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified /// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance. /// <see cref="Translator"/> instance.
/// </summary> /// </summary>
/// <param name="jitCache">Jit cache to map functions in</param>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param> /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param> /// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(IAddressTable<ulong> functionTable, NoWxCache noWxCache) public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable, NoWxCache noWxCache)
{ {
ArgumentNullException.ThrowIfNull(functionTable); ArgumentNullException.ThrowIfNull(functionTable);
_jitCache = jitCache;
_functionTable = functionTable; _functionTable = functionTable;
_noWxCache = noWxCache; _noWxCache = noWxCache;
_getFunctionAddressRef = NativeInterface.GetFunctionAddress; _getFunctionAddressRef = NativeInterface.GetFunctionAddress;
@@ -113,12 +118,12 @@ namespace Ryujinx.Cpu.LightningJit
{ {
if (_dispatchStub.IsValueCreated) if (_dispatchStub.IsValueCreated)
{ {
JitCache.Unmap(_dispatchStub.Value); _jitCache.Unmap(_dispatchStub.Value);
} }
if (_dispatchLoop.IsValueCreated) if (_dispatchLoop.IsValueCreated)
{ {
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); _jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
} }
} }
@@ -363,7 +368,7 @@ namespace Ryujinx.Cpu.LightningJit
} }
else else
{ {
return JitCache.Map(code); return _jitCache.Map(code);
} }
} }

View File

@@ -10,6 +10,8 @@ using MemoryPermission = Ryujinx.Tests.Unicorn.MemoryPermission;
namespace Ryujinx.Tests.Cpu namespace Ryujinx.Tests.Cpu
{ {
[TestFixture] [TestFixture]
[Parallelizable(ParallelScope.All)]
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
public class CpuTest public class CpuTest
{ {
protected static readonly ulong Size = MemoryBlock.GetPageSize(); protected static readonly ulong Size = MemoryBlock.GetPageSize();

View File

@@ -10,6 +10,8 @@ using MemoryPermission = Ryujinx.Tests.Unicorn.MemoryPermission;
namespace Ryujinx.Tests.Cpu namespace Ryujinx.Tests.Cpu
{ {
[TestFixture] [TestFixture]
[Parallelizable(ParallelScope.All)]
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
public class CpuTest32 public class CpuTest32
{ {
protected static readonly uint Size = (uint)MemoryBlock.GetPageSize(); protected static readonly uint Size = (uint)MemoryBlock.GetPageSize();

View File

@@ -51,7 +51,7 @@ namespace Ryujinx.Tests.Cpu
bool methodCalled = false; bool methodCalled = false;
bool isFz = false; bool isFz = false;
TranslatorTestMethods.FpFlagsPInvokeTest method = TranslatorTestMethods.GenerateFpFlagsPInvokeTest(); TranslatorTestMethods.FpFlagsPInvokeTest method = TranslatorTestMethods.GenerateFpFlagsPInvokeTest(_translator.JitCache);
// This method sets flush-to-zero and then calls the managed method. // This method sets flush-to-zero and then calls the managed method.
// Before and after setting the flags, it ensures subnormal addition works as expected. // Before and after setting the flags, it ensures subnormal addition works as expected.

View File

@@ -245,7 +245,7 @@ namespace Ryujinx.Tests.Memory
// Create a large mapping. // Create a large mapping.
mainMemory.MapView(backing, 0, 0, vaSize); mainMemory.MapView(backing, 0, 0, vaSize);
TestMethods.DebugNativeWriteLoop writeFunc = TestMethods.GenerateDebugNativeWriteLoop(); TestMethods.DebugNativeWriteLoop writeFunc = TestMethods.GenerateDebugNativeWriteLoop(_translator.JitCache);
nint writePtr = mainMemory.GetPointer(vaSize - 0x1000, 4); nint writePtr = mainMemory.GetPointer(vaSize - 0x1000, 4);
Thread testThread = new(() => Thread testThread = new(() =>
@@ -339,7 +339,7 @@ namespace Ryujinx.Tests.Memory
fixed (void* localMap = &state.LocalCounts) fixed (void* localMap = &state.LocalCounts)
{ {
TestMethods.DebugThreadLocalMapGetOrReserve getOrReserve = TestMethods.GenerateDebugThreadLocalMapGetOrReserve((nint)localMap); TestMethods.DebugThreadLocalMapGetOrReserve getOrReserve = TestMethods.GenerateDebugThreadLocalMapGetOrReserve(_translator.JitCache, (nint)localMap);
for (int i = 0; i < ThreadLocalMap<int>.MapSize; i++) for (int i = 0; i < ThreadLocalMap<int>.MapSize; i++)
{ {