Skip to content

Commit

Permalink
JIT Cache Regions + HLE SoNoSigpipe BSD socket mapping (#615)
Browse files Browse the repository at this point in the history
Instead of one big 2048MB JIT Cache that'd crash the emulator when maxed
out, we now have it where we add 256MB JIT Cache regions when needed,
helping reduce allocated memory where games don't use the JIT cache for
it, and helping bigger games that DO need JIT cache bigger than 2048MB!

![image](https://github.com/user-attachments/assets/ff17dc48-6028-4377-8c73-746ab21ab83b)
(SSBU goes past the 2048MB JIT Cache limit that would normally crash
Ryujinx ^)

Also I added a BSD socket that Baba is You's networking for downloading
custom levels uses.
  • Loading branch information
FluffyOMC authored Feb 10, 2025
1 parent 55fdb3f commit faacec9
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 69 deletions.
2 changes: 2 additions & 0 deletions src/ARMeilleure/Memory/ReservedRegion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class ReservedRegion
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.

public IJitMemoryBlock Block { get; }
public IJitMemoryAllocator Allocator { get; }

public nint Pointer => Block.Pointer;

Expand All @@ -21,6 +22,7 @@ public ReservedRegion(IJitMemoryAllocator allocator, ulong maxSize, ulong granul
granularity = DefaultGranularity;
}

Allocator = allocator;
Block = allocator.Reserve(maxSize);
_maxSize = maxSize;
_sizeGranularity = granularity;
Expand Down
112 changes: 77 additions & 35 deletions src/ARMeilleure/Translation/Cache/JitCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory;
using ARMeilleure.Native;
using Humanizer;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
Expand All @@ -18,9 +20,8 @@ static partial class JitCache
private static readonly int _pageMask = _pageSize - 1;

private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;
private const int CacheSize = 256 * 1024 * 1024;

private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;

private static CacheMemoryAllocator _cacheAllocator;
Expand All @@ -30,6 +31,9 @@ static partial class JitCache
private static readonly Lock _lock = new();
private static bool _initialized;

private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;

[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
Expand All @@ -48,7 +52,9 @@ public static void Initialize(IJitMemoryAllocator allocator)
return;
}

_jitRegion = new ReservedRegion(allocator, CacheSize);
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;

if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
Expand All @@ -59,7 +65,9 @@ public static void Initialize(IJitMemoryAllocator allocator)

if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
}

_initialized = true;
Expand All @@ -75,8 +83,8 @@ public static nint Map(CompiledFunction func)
Debug.Assert(_initialized);

int funcOffset = Allocate(code.Length);

nint funcPtr = _jitRegion.Pointer + funcOffset;
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = targetRegion.Pointer + funcOffset;

if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
Expand All @@ -90,9 +98,9 @@ public static nint Map(CompiledFunction func)
}
else
{
ReprotectAsWritable(funcOffset, code.Length);
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(funcOffset, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);

if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
Expand All @@ -116,52 +124,83 @@ public static void Unmap(nint pointer)
{
Debug.Assert(_initialized);

int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());

if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
foreach (var region in _jitRegions)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}

int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());

if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}

return;
}
}
}

private static void ReprotectAsWritable(int offset, int size)
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;

int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;

_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}

private static void ReprotectAsExecutable(int offset, int size)
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;

int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;

_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}

private static int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);

int allocOffset = _cacheAllocator.Allocate(codeSize);

if (allocOffset < 0)
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
throw new OutOfMemoryException("JIT Cache exhausted.");
int allocOffset = _cacheAllocator.Allocate(codeSize);

if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
}

_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;

int newRegionNumber = _activeRegionIndex;

Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");

_cacheAllocator = new CacheMemoryAllocator(CacheSize);

return allocOffset;
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}

newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
}


private static int AlignCodeSize(int codeSize)
{
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
Expand All @@ -185,18 +224,21 @@ public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{
lock (_lock)
{
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));

if (index < 0)
foreach (var region in _jitRegions)
{
index = ~index - 1;
}
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));

if (index >= 0)
{
entry = _cacheEntries[index];
entryIndex = index;
return true;
if (index < 0)
{
index = ~index - 1;
}

if (index >= 0)
{
entry = _cacheEntries[index];
entryIndex = index;
return true;
}
}
}

Expand Down
94 changes: 61 additions & 33 deletions src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using ARMeilleure.Memory;
using Humanizer;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
Expand All @@ -15,9 +17,8 @@ static partial class JitCache
private static readonly int _pageMask = _pageSize - 1;

private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;
private const int CacheSize = 256 * 1024 * 1024;

private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;

private static CacheMemoryAllocator _cacheAllocator;
Expand All @@ -26,6 +27,8 @@ static partial class JitCache

private static readonly Lock _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;

[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
Expand All @@ -45,7 +48,9 @@ public static void Initialize(IJitMemoryAllocator allocator)
return;
}

_jitRegion = new ReservedRegion(allocator, CacheSize);
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;

if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
Expand All @@ -65,8 +70,8 @@ public unsafe static nint Map(ReadOnlySpan<byte> code)
Debug.Assert(_initialized);

int funcOffset = Allocate(code.Length);

nint funcPtr = _jitRegion.Pointer + funcOffset;
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
nint funcPtr = targetRegion.Pointer + funcOffset;

if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
Expand All @@ -80,18 +85,11 @@ public unsafe static nint Map(ReadOnlySpan<byte> code)
}
else
{
ReprotectAsWritable(funcOffset, code.Length);
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
ReprotectAsExecutable(funcOffset, code.Length);
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);

if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
}
else
{
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}

Add(funcOffset, code.Length);
Expand All @@ -106,50 +104,80 @@ public static void Unmap(nint pointer)
{
Debug.Assert(_initialized);

int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());

if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
foreach (var region in _jitRegions)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}

int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());

if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}

return;
}
}
}

private static void ReprotectAsWritable(int offset, int size)
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;

int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;

_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}

private static void ReprotectAsExecutable(int offset, int size)
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;

int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;

_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}

private static int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);

int allocOffset = _cacheAllocator.Allocate(codeSize);

if (allocOffset < 0)
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
throw new OutOfMemoryException("JIT Cache exhausted.");
int allocOffset = _cacheAllocator.Allocate(codeSize);

if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
}

_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;

int newRegionNumber = _activeRegionIndex;

Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((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!");
}

return allocOffset;
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
}

private static int AlignCodeSize(int codeSize)
Expand Down
Loading

0 comments on commit faacec9

Please sign in to comment.