Compare commits

...
4 Commits
Author SHA1 Message Date
KeatonTheBotandLotP 3f62488b26 SDL: Update game controller database on launch (#177)
Expands upon existing custom SDL mappings functionality: b82e789d4f

Downloads an updated game controller database (gamecontrollerdb.txt) on launch, which only updates when a new file is available. This keeps the database updated between SDL releases.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/177
2026-07-30 21:18:39 +00:00
MabelandLotP e3940abe01 Threaded Renderer Crash, Threaded Renderer Index Desync, Logger Disposal (#174)
These 3 are all fixes made by LotP, who said they just didn't feel like making the PR atm and that someone else could do it if they wanted to (https://discord.com/channels/1294443224030511104/1295891559056674816/1531034369232211968)

Addresses these 3 specific issues:
1. Toggling the hidden console tries to assign a new console logger, which is also done on setting initial state, but at that point a logger already exists so it just returns, except previously it did not dispose of the new logger.
2. The threaded renderer would sometimes just crash when emulation is stopped, the fix being to make sure the backend render thread is joined before touching common objects
3. The disposing workflow of other threads than the main GPU adds commands to the command queue, causing a problem where the index can get desynced because of race conditions

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/174
2026-07-27 20:03:45 +00:00
LotPandLotP1 a82350bb77 mono-jit-v2 (#173)
second attempt at single layer address tables (first attempt https://git.ryujinx.app/projects/Ryubing/pulls/167).
should work with all games now.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/173
2026-07-17 12:57:07 +00:00
LotPandLotP1 deb5dca420 Revert "mono-jit (#167)" (#172)
This reverts the mono jit PR.
It had several issues that i haven't had time to fix yet.
Expect mono jit v2 in the future with said fixes.

JitCache alignement fixes are still included/fixed.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/172
2026-07-14 10:23:16 +00:00
29 changed files with 437 additions and 382 deletions
@@ -20,21 +20,21 @@ namespace ARMeilleure.Common
new( 1, 6)
];
private static readonly AddressTableLevel[] _monoSparse64Bit =
private static readonly AddressTableLevel[] _levels64BitMono =
[
new( 2, 37)
new( 2, 37)
];
private static readonly AddressTableLevel[] _monoSparse32Bit =
private static readonly AddressTableLevel[] _levels32BitMono =
[
new( 1, 31)
new( 1, 31)
];
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse)
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool mono)
{
if (sparse)
if (mono)
{
return for64Bits ? _monoSparse64Bit : _monoSparse32Bit;
return for64Bits ? _levels64BitMono : _levels32BitMono;
}
else
{
@@ -0,0 +1,8 @@
namespace ARMeilleure.Common
{
public enum AddressTableType
{
Default,
Sparse
}
}
+9
View File
@@ -53,5 +53,14 @@ namespace ARMeilleure.Common
{
return (bits >> shift) | (bits << (size - shift));
}
public static T AlignUp<T>(T value, T size) where T : IBinaryInteger<T>
=> (value + (size - T.One)) & -size;
public static T AlignDown<T>(T value, T size) where T : IBinaryInteger<T>
=> value & -size;
public static T DivRoundUp<T>(T value, T dividend) where T : IBinaryInteger<T>
=> (value + (dividend - T.One)) / dividend;
}
}
+9 -3
View File
@@ -5,10 +5,9 @@ namespace ARMeilleure.Common
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
{
/// <summary>
/// True if the address table's bottom level is sparsely mapped.
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
/// Gets the <see cref="AddressTableType"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
bool Sparse { get; }
AddressTableType TableType { get; }
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
@@ -31,6 +30,13 @@ namespace ARMeilleure.Common
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
nint Base { get; }
/// <summary>
/// Signal that the given code range exists.
/// </summary>
/// <param name="address">Guest code range address</param>
/// <param name="size">Guest code range size</param>
void SignalCodeRange(ulong address, ulong size);
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="IAddressTable{TEntry}"/>.
@@ -236,7 +236,7 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
}
else if (table.Sparse)
else if (table.TableType == AddressTableType.Sparse)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
// Deliberately attempts to avoid branches.
@@ -260,8 +260,8 @@ namespace ARMeilleure.Instructions
else
{
hostAddress = !context.HasPtc ?
Const((long)context.Stubs.DispatchStub) :
Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol);
Const(context.Stubs.DispatchStub) :
Const(context.Stubs.DispatchStub, Ptc.DispatchStubSymbol);
}
if (isJump)
+1 -1
View File
@@ -34,7 +34,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 7018; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 7020; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
+3 -1
View File
@@ -28,7 +28,7 @@ namespace ARMeilleure.Translation
private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal IAddressTable<ulong> FunctionTable { get; }
public IAddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; }
internal TranslatorQueue Queue { get; }
@@ -53,7 +53,9 @@ namespace ARMeilleure.Translation
CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable;
Stubs = new TranslatorStubs(JitCache, FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
@@ -161,7 +161,7 @@ namespace ARMeilleure.Translation
context.BranchIfTrue(lblFallback, masked);
Operand index = default;
Operand page = Const(_functionTable.Base);
Operand page = Const((long)_functionTable.Base);
for (int i = 0; i < _functionTable.Levels.Length; i++)
{
+1
View File
@@ -162,6 +162,7 @@ namespace Ryujinx.Common.Logging
{
if (_logTargets.Any(t => t.Name == target.Name))
{
target.Dispose();
return;
}
+38 -319
View File
@@ -1,15 +1,9 @@
using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Cpu.Signal;
using Ryujinx.Memory;
using ARMeilleure.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Cpu.MemoryEhMeilleure;
namespace ARMeilleure.Common
namespace Ryujinx.Cpu
{
/// <summary>
/// Represents a table of guest address to a value.
@@ -17,85 +11,12 @@ namespace ARMeilleure.Common
/// <typeparam name="TEntry">Type of the value</typeparam>
public unsafe class AddressTable<TEntry> : IAddressTable<TEntry> where TEntry : unmanaged
{
/// <summary>
/// Represents a page of the address table.
/// </summary>
private readonly struct AddressTablePage
{
/// <summary>
/// True if the allocation belongs to a sparse block, false otherwise.
/// </summary>
public readonly bool IsSparse;
/// <summary>
/// Base address for the page.
/// </summary>
public readonly nint Address;
public AddressTablePage(bool isSparse, nint address)
{
IsSparse = isSparse;
Address = address;
}
}
/// <summary>
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
/// </summary>
private readonly struct TableSparseBlock : IDisposable
{
public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit)
{
SparseMemoryBlock block = new(size, pageInit, null);
_trackingEvent = (address, size, write) =>
{
ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((nint)pointer);
return pointer;
};
bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (nint)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)
{
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
}
Block = block;
}
public void Dispose()
{
NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer);
Block.Dispose();
}
}
private bool _disposed;
private TEntry** _table;
private TEntry* _sparseTable;
private readonly List<AddressTablePage> _pages;
private TEntry _fill;
private readonly List<nint> _pages;
private MemoryBlock _sparseFill;
private SparseMemoryBlock _fillBottomLevel;
private TEntry* _fillBottomLevelPtr;
private readonly List<TableSparseBlock> _sparseReserved;
private readonly ReaderWriterLockSlim _sparseLock;
private ulong _sparseBlockSize;
private ulong _sparseReservedOffset;
public bool Sparse { get; }
/// <inheritdoc/>
public AddressTableType TableType => AddressTableType.Default;
/// <inheritdoc/>
public ulong Mask { get; }
@@ -104,17 +25,7 @@ namespace ARMeilleure.Common
public AddressTableLevel[] Levels { get; }
/// <inheritdoc/>
public TEntry Fill
{
get
{
return _fill;
}
set
{
UpdateFill(value);
}
}
public TEntry Fill { get; set; }
/// <inheritdoc/>
public nint Base
@@ -123,16 +34,9 @@ namespace ARMeilleure.Common
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (Sparse)
lock (_pages)
{
return (nint)_sparseTable;
}
else
{
lock (_pages)
{
return (nint)GetRootPage();
}
return (nint)GetRootPage();
}
}
}
@@ -142,14 +46,13 @@ namespace ARMeilleure.Common
/// <see cref="AddressTableLevel"/>.
/// </summary>
/// <param name="levels">Levels for the address table</param>
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public AddressTable(AddressTableLevel[] levels, bool sparse)
public AddressTable(AddressTableLevel[] levels)
{
ArgumentNullException.ThrowIfNull(levels);
_pages = new List<AddressTablePage>(capacity: 16);
_pages = new List<nint>(capacity: 16);
Levels = levels;
Mask = 0;
@@ -158,15 +61,6 @@ namespace ARMeilleure.Common
{
Mask |= level.Mask;
}
Sparse = sparse;
if (sparse)
{
// If the address table is sparse, allocate a fill block
_sparseReserved = [];
_sparseLock = new ReaderWriterLockSlim();
}
}
/// <summary>
@@ -174,29 +68,10 @@ namespace ARMeilleure.Common
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
/// </summary>
/// <param name="for64Bits">True if the guest is A64, false otherwise</param>
/// <param name="type">Memory manager type</param>
/// <returns>An <see cref="AddressTable{TEntry}"/> for ARM function lookup</returns>
public static AddressTable<TEntry> CreateForArm(bool for64Bits, MemoryManagerType type)
public static AddressTable<TEntry> CreateForArm(bool for64Bits)
{
// Assume software memory means that we don't want to use any signal handlers.
bool sparse = type is not MemoryManagerType.SoftwareMmu and not MemoryManagerType.SoftwarePageTable;
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse);
}
/// <summary>
/// Update the fill value for the bottom level of the table.
/// </summary>
/// <param name="fillValue">New fill value</param>
private void UpdateFill(TEntry fillValue)
{
if (_sparseFill != null)
{
Span<byte> span = _sparseFill.GetSpan(0, (int)_sparseFill.Size);
MemoryMarshal.Cast<byte, TEntry>(span).Fill(fillValue);
}
_fill = fillValue;
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, false));
}
/// <summary>
@@ -204,28 +79,7 @@ namespace ARMeilleure.Common
/// </summary>
/// <param name="address"></param>
/// <param name="size"></param>
public void SignalCodeRange(ulong address, ulong size)
{
AddressTableLevel bottom = Levels.Last();
ulong entries = size >> bottom.Index;
if (Sparse)
{
ulong bottomLevelSize = (ulong)BitUtils.Pow2RoundUp((int)entries) * (ulong)sizeof(TEntry);
_sparseFill = new MemoryBlock(bottomLevelSize, MemoryAllocationFlags.Mirrorable);
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
_sparseBlockSize = bottomLevelSize;
_sparseTable = (TEntry*)Allocate((int)entries, Fill, leaf: true);
_sparseTable -= Levels.Last().GetValue(address);
}
}
public void SignalCodeRange(ulong address, ulong size) { }
/// <inheritdoc/>
public bool IsValid(ulong address)
@@ -243,26 +97,13 @@ namespace ARMeilleure.Common
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
}
if (Sparse)
lock (_pages)
{
long index = Levels.Last().GetValue(address);
TEntry* page = GetPage(address);
EnsureMapped((nint)(_sparseTable + index));
long index = Levels[^1].GetValue(address);
return ref _sparseTable[index];
}
else
{
lock (_pages)
{
TEntry* page = GetPage(address);
long index = Levels.Last().GetValue(address);
EnsureMapped((nint)(page + index));
return ref page[index];
}
return ref page[index];
}
}
@@ -278,19 +119,19 @@ namespace ARMeilleure.Common
for (int i = 0; i < Levels.Length - 1; i++)
{
ref AddressTableLevel level = ref Levels[i];
ref TEntry* nextPage = ref page[level.GetValue(address)];
ref TEntry* nextPage = ref page![level.GetValue(address)];
if (nextPage == null || nextPage == _fillBottomLevelPtr)
if (nextPage == null)
{
ref AddressTableLevel nextLevel = ref Levels[i + 1];
if (i == Levels.Length - 2)
{
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true);
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill);
}
else
{
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false);
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, nint.Zero);
}
}
@@ -300,57 +141,6 @@ namespace ARMeilleure.Common
return (TEntry*)page;
}
/// <summary>
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary>
/// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(nint ptr)
{
if (Sparse)
{
// Check sparse allocations to see if the pointer is in any of them.
// Ensure the page is committed if there's a match.
_sparseLock.EnterReadLock();
try
{
foreach (TableSparseBlock reserved in _sparseReserved)
{
SparseMemoryBlock sparse = reserved.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
{
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
break;
}
}
}
finally
{
_sparseLock.ExitReadLock();
}
}
}
/// <summary>
/// Get the fill value for a non-leaf level of the table.
/// </summary>
/// <param name="level">Level to get the fill value for</param>
/// <returns>The fill value</returns>
private nint GetFillValue(int level)
{
if (_fillBottomLevel != null && level == Levels.Length - 2)
{
return (nint)_fillBottomLevelPtr;
}
else
{
return nint.Zero;
}
}
/// <summary>
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
/// </summary>
@@ -360,91 +150,35 @@ namespace ARMeilleure.Common
if (_table == null)
{
if (Levels.Length == 1)
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true);
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill);
else
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
_table = (TEntry**)Allocate(1 << Levels[0].Length, nint.Zero);
}
return _table;
}
/// <summary>
/// Initialize a leaf page with the fill value.
/// </summary>
/// <param name="page">Page to initialize</param>
private void InitLeafPage(Span<byte> page)
{
MemoryMarshal.Cast<byte, TEntry>(page).Fill(_fill);
}
/// <summary>
/// Reserve a new sparse block, and add it to the list.
/// </summary>
/// <returns>The new sparse block that was added</returns>
private TableSparseBlock ReserveNewSparseBlock()
{
TableSparseBlock block = new(_sparseBlockSize, EnsureMapped, InitLeafPage);
_sparseReserved.Add(block);
_sparseReservedOffset = 0;
return block;
}
/// <summary>
/// Allocates a block of memory of the specified type and length.
/// </summary>
/// <typeparam name="T">Type of elements</typeparam>
/// <param name="length">Number of elements</param>
/// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns>
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
private nint Allocate<T>(int length, T fill) where T : unmanaged
{
int size = sizeof(T) * length;
AddressTablePage page;
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
if (Sparse && leaf)
{
_sparseLock.EnterWriteLock();
Span<T> span = new((void*)address, length);
span.Fill(fill);
SparseMemoryBlock block;
if (_sparseReserved.Count == 0)
{
block = ReserveNewSparseBlock().Block;
}
else
{
block = _sparseReserved.Last().Block;
if (_sparseReservedOffset == block.Block.Size)
{
block = ReserveNewSparseBlock().Block;
}
}
page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset);
_sparseReservedOffset += (ulong)size;
_sparseLock.ExitWriteLock();
}
else
{
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
page = new AddressTablePage(false, address);
Span<T> span = new((void*)page.Address, length);
span.Fill(fill);
}
_pages.Add(page);
_pages.Add(address);
//TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
return page.Address;
return address;
}
/// <summary>
@@ -463,32 +197,17 @@ namespace ARMeilleure.Common
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
if (_disposed)
{
foreach (AddressTablePage page in _pages)
{
if (!page.IsSparse)
{
Marshal.FreeHGlobal(page.Address);
}
}
if (Sparse)
{
foreach (TableSparseBlock block in _sparseReserved)
{
block.Dispose();
}
_sparseReserved.Clear();
_fillBottomLevel.Dispose();
_sparseFill.Dispose();
_sparseLock.Dispose();
}
_disposed = true;
return;
}
foreach (nint page in _pages)
{
Marshal.FreeHGlobal(page);
}
_disposed = true;
}
/// <summary>
+6 -4
View File
@@ -9,13 +9,15 @@ namespace Ryujinx.Cpu.Jit
{
private readonly ITickSource _tickSource;
private readonly Translator _translator;
private readonly AddressTable<ulong> _functionTable;
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
{
_tickSource = tickSource;
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
bool sparse = memory.Type is not MemoryManagerType.SoftwareMmu and not MemoryManagerType.SoftwarePageTable;
IAddressTable<ulong> functionTable = sparse ? SparseAddressTable<ulong>.CreateForArm(for64Bit) : AddressTable<ulong>.CreateForArm(for64Bit);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, functionTable);
if (memory.Type.IsHostMappedOrTracked)
{
@@ -57,7 +59,7 @@ namespace Ryujinx.Cpu.Jit
/// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size)
{
_functionTable.SignalCodeRange(address, size);
_translator.FunctionTable.SignalCodeRange(address, size);
_translator.PrepareCodeRange(address, size);
}
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.LightningJit
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
ExecutionMode executionMode,
Architecture targetArch)
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
bool isThumb,
Architecture targetArch)
@@ -22,7 +22,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
public readonly RegisterAllocator RegisterAllocator;
public readonly MemoryManagerType MemoryManagerType;
public readonly TailMerger TailMerger;
public readonly AddressTable<ulong> FuncTable;
public readonly IAddressTable<ulong> FuncTable;
public readonly nint DispatchStubPointer;
private readonly RegisterSaveRestore _registerSaveRestore;
@@ -33,7 +33,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
RegisterAllocator registerAllocator,
MemoryManagerType mmType,
TailMerger tailMerger,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
RegisterSaveRestore registerSaveRestore,
nint dispatchStubPointer,
nint pageTablePointer)
@@ -225,7 +225,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
}
}
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
{
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
@@ -133,7 +133,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint funcPtr,
int spillBaseOffset,
uint nextAddress,
@@ -144,7 +144,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
funcTable is { Sparse: true };
funcTable.TableType == AddressTableType.Sparse;
if (guestAddress.Kind == OperandKind.Constant)
{
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
Architecture targetArch)
{
@@ -18,7 +18,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
public readonly CodeWriter Writer;
public readonly RegisterAllocator RegisterAllocator;
public readonly TailMerger TailMerger;
public readonly AddressTable<ulong> FuncTable;
public readonly IAddressTable<ulong> FuncTable;
public readonly nint DispatchStubPointer;
private readonly MultiBlock _multiBlock;
@@ -31,7 +31,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
TailMerger tailMerger,
RegisterSaveRestore registerSaveRestore,
MultiBlock multiBlock,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPointer,
nint pageTablePointer)
{
@@ -303,7 +303,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
}
}
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, nint dispatchStubPtr)
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr)
{
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address);
@@ -215,7 +215,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint dispatchStubPtr,
InstName name,
ulong pc,
@@ -299,7 +299,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
RegisterAllocator regAlloc,
TailMerger tailMerger,
Action writeEpilogue,
AddressTable<ulong> funcTable,
IAddressTable<ulong> funcTable,
nint funcPtr,
int spillBaseOffset,
ulong pc,
@@ -310,7 +310,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
funcTable is { Sparse: true };
funcTable.TableType == AddressTableType.Sparse;
if (guestAddress.Kind == OperandKind.Constant)
{
@@ -106,7 +106,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{
int endOffs = offset + size;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
@@ -115,7 +117,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{
int endOffs = offset + size;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
@@ -9,15 +9,15 @@ namespace Ryujinx.Cpu.LightningJit
{
private readonly ITickSource _tickSource;
private readonly Translator _translator;
private readonly AddressTable<ulong> _functionTable;
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
{
_tickSource = tickSource;
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
bool sparse = memory.Type is not MemoryManagerType.SoftwareMmu and not MemoryManagerType.SoftwarePageTable;
IAddressTable<ulong> functionTable = sparse ? SparseAddressTable<ulong>.CreateForArm(for64Bit) : AddressTable<ulong>.CreateForArm(for64Bit);
_translator = new Translator(memory, _functionTable);
_translator = new Translator(memory, functionTable);
memory.UnmapEvent += UnmapHandler;
}
@@ -54,7 +54,7 @@ namespace Ryujinx.Cpu.LightningJit
/// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size)
{
_functionTable.SignalCodeRange(address, size);
_translator.FunctionTable.SignalCodeRange(address, size);
}
public void Dispose()
+3 -2
View File
@@ -24,11 +24,11 @@ namespace Ryujinx.Cpu.LightningJit
private bool _disposed;
internal TranslatorCache<TranslatedFunction> Functions { get; }
internal AddressTable<ulong> FunctionTable { get; }
internal IAddressTable<ulong> FunctionTable { get; }
internal TranslatorStubs Stubs { get; }
internal IMemoryManager Memory { get; }
public Translator(IMemoryManager memory, AddressTable<ulong> functionTable)
public Translator(IMemoryManager memory, IAddressTable<ulong> functionTable)
{
Memory = memory;
@@ -45,6 +45,7 @@ namespace Ryujinx.Cpu.LightningJit
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable;
Stubs = new TranslatorStubs(_jitCache, FunctionTable, _noWxCache);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
+247
View File
@@ -0,0 +1,247 @@
using ARMeilleure.Common;
using Ryujinx.Common;
using Ryujinx.Cpu.Signal;
using Ryujinx.Memory;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using static Ryujinx.Cpu.MemoryEhMeilleure;
namespace Ryujinx.Cpu
{
/// <summary>
/// Represents a table of guest address to a value.
/// </summary>
/// <typeparam name="TEntry">Type of the value</typeparam>
public unsafe class SparseAddressTable<TEntry> : IAddressTable<TEntry> where TEntry : unmanaged
{
/// <summary>
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
/// </summary>
private readonly struct TableSparseBlock : IDisposable
{
public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit, MemoryBlock fill)
{
SparseMemoryBlock block = new(size, pageInit, fill);
_trackingEvent = (address, _, _) =>
{
ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((nint)pointer);
return pointer;
};
bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (nint)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)
{
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
}
Block = block;
}
public void Dispose()
{
NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer);
Block.Dispose();
}
}
private bool _disposed;
private TEntry* _table;
private TEntry _fill;
private readonly TableSparseBlock _block;
private readonly MemoryBlock _fillBlock;
/// <inheritdoc/>
public AddressTableType TableType => AddressTableType.Sparse;
/// <inheritdoc/>
public ulong Mask { get; }
/// <inheritdoc/>
public AddressTableLevel[] Levels { get; }
/// <inheritdoc/>
public TEntry Fill
{
get
{
return _fill;
}
set
{
UpdateFill(value);
}
}
/// <inheritdoc/>
public nint Base
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return (nint)_table;
}
}
/// <summary>
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
/// <see cref="AddressTableLevel"/>.
/// </summary>
/// <param name="levels">Levels for the address table</param>
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public SparseAddressTable(AddressTableLevel[] levels)
{
ArgumentNullException.ThrowIfNull(levels);
Levels = levels;
Mask = 0;
foreach (AddressTableLevel level in Levels)
{
Mask |= level.Mask;
}
_fillBlock = new MemoryBlock(MemoryBlock.GetPageSize() << 10, MemoryAllocationFlags.Mirrorable);
// We need to use the full size, as some games dynamically expand the code range (e.g. SSBU)
// Limiting the size to only the requested code range will cause crashes
// This should not be an issue tho, as the SparseBlock dynamically allocates memory as needed, and
// Falls back to the fill block in case guest code tries to call an unmapped function.
ulong bottomLevelSize = (1ul << Levels.Last().Length) * (ulong)sizeof(TEntry);
_block = new TableSparseBlock(bottomLevelSize, EnsureMapped, InitLeafPage, _fillBlock);
_table = (TEntry*)_block.Block.Block.Pointer;
}
/// <summary>
/// Create an <see cref="AddressTable{TEntry}"/> instance for an ARM function table.
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
/// </summary>
/// <param name="for64Bits">True if the guest is A64, false otherwise</param>
/// <returns>An <see cref="AddressTable{TEntry}"/> for ARM function lookup</returns>
public static SparseAddressTable<TEntry> CreateForArm(bool for64Bits)
{
return new SparseAddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, true));
}
/// <summary>
/// Update the fill value for the bottom level of the table.
/// </summary>
/// <param name="fillValue">New fill value</param>
private void UpdateFill(TEntry fillValue)
{
if (_fillBlock != null)
{
Span<byte> span = _fillBlock.GetSpan(0, (int)_fillBlock.Size);
MemoryMarshal.Cast<byte, TEntry>(span).Fill(fillValue);
}
_fill = fillValue;
}
/// <summary>
/// Signal that the given code range exists.
/// </summary>
/// <param name="address"></param>
/// <param name="size"></param>
public void SignalCodeRange(ulong address, ulong size)
{
}
/// <inheritdoc/>
public bool IsValid(ulong address)
{
return (address & ~Mask) == 0;
}
/// <inheritdoc/>
public ref TEntry GetValue(ulong address)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!IsValid(address))
{
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
}
long index = Levels.Last().GetValue(address);
EnsureMapped((nint)(_table + index));
return ref _table[index];
}
/// <summary>
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary>
/// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(nint ptr)
{
SparseMemoryBlock sparse = _block.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
{
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
}
}
/// <summary>
/// Initialize a leaf page with the fill value.
/// </summary>
/// <param name="page">Page to initialize</param>
private void InitLeafPage(Span<byte> page)
{
MemoryMarshal.Cast<byte, TEntry>(page).Fill(_fill);
}
/// <summary>
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
/// instance.
/// </summary>
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_block.Dispose();
_fillBlock.Dispose();
_disposed = true;
}
/// <summary>
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
~SparseAddressTable()
{
Dispose(false);
}
}
}
@@ -195,11 +195,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
{
// The reference table is sized so that it will never overflow, so long as the references are taken after the command is allocated.
int index = _refProducerPtr;
// make sure increment is thread safe
int index = Interlocked.Increment(ref _refProducerPtr) - 1;
index %= _refQueue.Length;
_refQueue[index] = obj;
_refProducerPtr = (_refProducerPtr + 1) % _refQueue.Length;
_refProducerPtr %= _refQueue.Length;
return index;
}
@@ -531,11 +534,16 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_running = false;
_galWorkAvailable.Set();
if (_gpuThread != null && _gpuThread.IsAlive)
if (_gpuThread is { IsAlive: true })
{
_gpuThread.Join();
}
if (_backendThread is { IsAlive: true })
{
_backendThread.Join();
}
// Dispose the renderer.
_baseRenderer.Dispose();
+16 -8
View File
@@ -21,6 +21,7 @@ namespace Ryujinx.Memory
private ulong _mappedBlockUsage;
private readonly ulong[] _mappedPageBitmap;
private readonly MemoryBlock _fill;
public MemoryBlock Block => _reservedBlock;
@@ -34,23 +35,24 @@ namespace Ryujinx.Memory
int pages = (int)BitUtils.DivRoundUp(size, _pageSize);
int bitmapEntries = BitUtils.DivRoundUp(pages, 64);
_mappedPageBitmap = new ulong[bitmapEntries];
_fill = fill;
if (fill != null)
if (_fill is not null)
{
// Fill the block with mappings from the fill block.
if (fill.Size % _pageSize != 0)
if (_fill.Size % _pageSize !=0)
{
throw new ArgumentException("Fill memory block should be page aligned.", nameof(fill));
throw new ArgumentException("Fill memory block should be page sized.", nameof(_fill));
}
int repeats = (int)BitUtils.DivRoundUp(size, fill.Size);
// Fill the block with mappings from the fill block.
int repeats = (int)BitUtils.DivRoundUp(size, _fill.Size);
ulong offset = 0;
for (int i = 0; i < repeats; i++)
{
_reservedBlock.MapView(fill, 0, offset, Math.Min(fill.Size, size - offset));
offset += fill.Size;
_reservedBlock.MapView(_fill, 0, offset, Math.Min(_fill.Size, size - offset));
offset += _fill.Size;
}
}
@@ -75,6 +77,12 @@ namespace Ryujinx.Memory
}
_pageInit(block.GetSpan(_mappedBlockUsage, (int)_pageSize));
if (_fill is not null)
{
_reservedBlock.UnmapView(_fill, pageOffset, _pageSize);
}
_reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize);
_mappedBlockUsage += _pageSize;
+38 -1
View File
@@ -4,6 +4,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using SDL;
using static SDL.SDL3;
@@ -43,6 +44,9 @@ namespace Ryujinx.SDL3.Common
private SDL3Driver() { }
private static readonly HttpClient _httpClient = new();
private const string GamepadDbUrl = "https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt";
public void Initialize()
{
lock (_lock)
@@ -96,7 +100,9 @@ namespace Ryujinx.SDL3.Common
SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_GAMEPAD_SENSOR_UPDATE, false);
string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt");
string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "gamecontrollerdb.txt");
UpdateGamepadDb(gamepadDbPath);
if (File.Exists(gamepadDbPath))
{
@@ -110,6 +116,37 @@ namespace Ryujinx.SDL3.Common
}
}
private static void UpdateGamepadDb(string gamepadDbPath)
{
try
{
byte[] remoteBytes = _httpClient.GetByteArrayAsync(GamepadDbUrl).GetAwaiter().GetResult();
bool shouldWrite = true;
if (File.Exists(gamepadDbPath))
{
byte[] localBytes = File.ReadAllBytes(gamepadDbPath);
if (localBytes.AsSpan().SequenceEqual(remoteBytes))
{
shouldWrite = false;
}
}
if (shouldWrite)
{
File.WriteAllBytes(gamepadDbPath ?? "", remoteBytes);
Logger.Info?.Print(LogClass.Application, "Updated gamepad database.");
}
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, $"Failed to check/download gamepad database, using existing local copy if present: {ex.Message}");
}
}
public bool RegisterWindow(SDL_WindowID windowId, Action<SDL_Event> windowEventHandler)
{
return _registeredWindowHandlers.TryAdd(windowId, windowEventHandler);
+4 -1
View File
@@ -13,7 +13,10 @@ namespace Ryujinx.Tests.Cpu
public CpuContext(IMemoryManager memory, bool for64Bit)
{
_translator = new Translator(new JitMemoryAllocator(), memory, AddressTable<ulong>.CreateForArm(for64Bit, memory.Type));
bool sparse = memory.Type is not MemoryManagerType.SoftwareMmu and not MemoryManagerType.SoftwarePageTable;
IAddressTable<ulong> functionTable = sparse ? SparseAddressTable<ulong>.CreateForArm(for64Bit) : AddressTable<ulong>.CreateForArm(for64Bit);
_translator = new Translator(new JitMemoryAllocator(), memory, functionTable);
memory.UnmapEvent += UnmapHandler;
}
+2 -2
View File
@@ -1,7 +1,7 @@
using ARMeilleure.Common;
using ARMeilleure.Memory;
using ARMeilleure.Translation;
using NUnit.Framework;
using Ryujinx.Cpu;
using Ryujinx.Cpu.Jit;
using Ryujinx.Tests.Memory;
using System;
@@ -20,7 +20,7 @@ namespace Ryujinx.Tests.Cpu
_translator ??= new Translator(
new JitMemoryAllocator(),
new MockMemoryManager(),
AddressTable<ulong>.CreateForArm(true, MemoryManagerType.SoftwarePageTable));
AddressTable<ulong>.CreateForArm(true));
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+1 -1
View File
@@ -60,7 +60,7 @@ namespace Ryujinx.Tests.Memory
_translator ??= new Translator(
new JitMemoryAllocator(),
new MockMemoryManager(),
AddressTable<ulong>.CreateForArm(true, MemoryManagerType.SoftwarePageTable));
AddressTable<ulong>.CreateForArm(true));
NativeSignalHandler.InitializeSignalHandler();
}