Implement JIT Cache Regions and Add Handheld Input

This commit is contained in:
Stossy11 2025-02-19 11:16:50 +11:00
parent cb33b04f2b
commit 3b99631dfb
6 changed files with 117 additions and 79 deletions

View File

@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
public IJitMemoryBlock Block { get; } public IJitMemoryBlock Block { get; }
public IJitMemoryAllocator Allocator { get; }
public IntPtr Pointer => Block.Pointer; public IntPtr Pointer => Block.Pointer;
@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
granularity = DefaultGranularity; granularity = DefaultGranularity;
} }
Allocator = allocator;
Block = allocator.Reserve(maxSize); Block = allocator.Reserve(maxSize);
_maxSize = maxSize; _maxSize = maxSize;
_sizeGranularity = granularity; _sizeGranularity = granularity;

View File

@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.Native; using ARMeilleure.Native;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -18,7 +19,7 @@ namespace ARMeilleure.Translation.Cache
private static readonly int _pageMask = _pageSize - 4; private static readonly int _pageMask = _pageSize - 4;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 1024 * 1024 * 1024; private const int CacheSize = 128 * 1024 * 1024;
private const int CacheSizeIOS = 128 * 1024 * 1024; private const int CacheSizeIOS = 128 * 1024 * 1024;
private static ReservedRegion _jitRegion; private static ReservedRegion _jitRegion;
@ -31,6 +32,10 @@ namespace ARMeilleure.Translation.Cache
private static readonly object _lock = new(); private static readonly object _lock = new();
private static bool _initialized; private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)] [LibraryImport("kernel32.dll", SetLastError = true)]
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize); public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
@ -49,7 +54,11 @@ namespace ARMeilleure.Translation.Cache
return; return;
} }
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize)); var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
{ {
@ -60,7 +69,9 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize)); JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
} }
_initialized = true; _initialized = true;
@ -73,7 +84,9 @@ namespace ARMeilleure.Translation.Cache
{ {
while (_deferredRxProtect.TryDequeue(out var result)) while (_deferredRxProtect.TryDequeue(out var result))
{ {
ReprotectAsExecutable(result.funcOffset, result.length); ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
} }
} }
@ -87,21 +100,14 @@ namespace ARMeilleure.Translation.Cache
int funcOffset = Allocate(code.Length, deferProtect); int funcOffset = Allocate(code.Length, deferProtect);
IntPtr funcPtr = _jitRegion.Pointer + funcOffset; ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsIOS()) if (OperatingSystem.IsIOS())
{ {
Marshal.Copy(code, 0, funcPtr, code.Length); Marshal.Copy(code, 0, funcPtr, code.Length);
if (deferProtect) ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
{ JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
_deferredRxProtect.Enqueue((funcOffset, code.Length));
}
else
{
ReprotectAsExecutable(funcOffset, code.Length);
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
}
} }
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64) else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@ -115,9 +121,9 @@ namespace ARMeilleure.Translation.Cache
} }
else else
{ {
ReprotectAsWritable(funcOffset, code.Length); ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, 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) if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@ -139,41 +145,50 @@ namespace ARMeilleure.Translation.Cache
{ {
if (OperatingSystem.IsIOS()) if (OperatingSystem.IsIOS())
{ {
return; // return;
} }
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized); foreach (var region in _jitRegions)
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); if (pointer.ToInt64() < region.Pointer.ToInt64() ||
_cacheEntries.RemoveAt(entryIndex); 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 endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_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 endOffs = offset + size;
int regionStart = offset & ~_pageMask; int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_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, bool deferProtect = false) private static int Allocate(int codeSize, bool deferProtect = false)
@ -187,20 +202,35 @@ namespace ARMeilleure.Translation.Cache
alignment = 0x4000; alignment = 0x4000;
} }
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment); for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
//DEBUG: Show JIT Memory Allocation
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
if (allocOffset < 0)
{ {
throw new OutOfMemoryException("JIT Cache exhausted."); int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
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;
return allocOffset; int newRegionNumber = _activeRegionIndex;
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(ref codeSize, alignment);
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, bool deferProtect = false) private static int AlignCodeSize(int codeSize, bool deferProtect = false)

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key> <key>Ryujinx.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>4</integer> <integer>1</integer>
</dict> </dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key> <key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>3</integer> <integer>2</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -77,6 +77,7 @@ class Ryujinx {
var disablevsync: Bool var disablevsync: Bool
var language: SystemLanguage var language: SystemLanguage
var regioncode: SystemRegionCode var regioncode: SystemRegionCode
var handHeldController: Bool
init(gamepath: String, init(gamepath: String,
@ -102,7 +103,8 @@ class Ryujinx {
disablePTC: Bool = false, disablePTC: Bool = false,
disablevsync: Bool = false, disablevsync: Bool = false,
language: SystemLanguage = .americanEnglish, language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false
) { ) {
self.gamepath = gamepath self.gamepath = gamepath
self.inputids = inputids self.inputids = inputids
@ -128,6 +130,7 @@ class Ryujinx {
self.disablevsync = disablevsync self.disablevsync = disablevsync
self.language = language self.language = language
self.regioncode = regioncode self.regioncode = regioncode
self.handHeldController = handHeldController
} }
} }
@ -321,10 +324,14 @@ class Ryujinx {
args.append("--list-inputs-ids") args.append("--list-inputs-ids")
} }
// Append the input ids (limit to 4 just in case) // Append the input ids (limit to 8 (used to be 4) just in case)
if !config.inputids.isEmpty { if !config.inputids.isEmpty {
config.inputids.prefix(4).enumerated().forEach { index, inputId in config.inputids.prefix(8).enumerated().forEach { index, inputId in
args.append(contentsOf: ["--input-id-\(index + 1)", inputId]) if config.handHeldController {
args.append(contentsOf: ["\(index == 0 ? "--input-id-handheld" : "--input-id-\(index + 1)")", inputId])
} else {
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
}
} }
} }

View File

@ -272,42 +272,12 @@ struct SettingsView: View {
Text("Select input devices and on-screen controls to play with. ") Text("Select input devices and on-screen controls to play with. ")
} }
// Language and Region Settings
Section {
Picker(selection: $config.language) {
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Language", iconName: "character.bubble")
}
Picker(selection: $config.regioncode) {
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Region", iconName: "globe")
}
// globe
} header: {
Text("Language and Region Settings")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Configure the System Language and the Region.")
}
// Input Settings // Input Settings
Section { Section {
Toggle(isOn: $config.macroHLE) {
labelWithIcon("Player 1 to Handheld Input", iconName: "formfitting.gamecontroller")
}.tint(.blue)
Toggle(isOn: $config.listinputids) {
labelWithIcon("List Input IDs", iconName: "list.bullet")
}
.tint(.blue)
Toggle(isOn: $ryuDemo) { Toggle(isOn: $ryuDemo) {
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw") labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
@ -363,6 +333,35 @@ struct SettingsView: View {
Text("Configure input devices and on-screen controls for easier navigation and play.") Text("Configure input devices and on-screen controls for easier navigation and play.")
} }
// Language and Region Settings
Section {
Picker(selection: $config.language) {
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Language", iconName: "character.bubble")
}
Picker(selection: $config.regioncode) {
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Region", iconName: "globe")
}
// globe
} header: {
Text("Language and Region Settings")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Configure the System Language and the Region.")
}
// CPU Mode // CPU Mode
Section { Section {
if filteredMemoryModes.isEmpty { if filteredMemoryModes.isEmpty {