More up to date build of the JIT Sparse PR for continued development. JIT Sparse Function Table was originally developed by riperiperi for the original Ryujinx project, and decreased the amount of layers in the Function Table structure, to decrease lookup times at the cost of slightly higher RAM usage. This PR rebalances the JIT Sparse Function Table to be a bit more RAM intensive, but faster in workloads where the JIT Function Table is a bottleneck. Faster RAM will see a bigger impact and slower RAM (DDR3 and potentially slow DDR4) will see a slight performance decrease. This PR also implements a base for a PPTC profile system that could allow for PPTC with ExeFS mods enabled in the future. This PR also potentially fixes a strange issue where Avalonia would time out in some rare instances, e.g. when running ExeFS mods with TotK and a strange controller configuration. --------- Co-authored-by: Evan Husted <gr33m11@gmail.com>
483 lines
16 KiB
C#
483 lines
16 KiB
C#
using ARMeilleure.Memory;
|
|
using Ryujinx.Common;
|
|
using Ryujinx.Cpu.Signal;
|
|
using Ryujinx.Memory;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using static Ryujinx.Cpu.MemoryEhMeilleure;
|
|
|
|
namespace ARMeilleure.Common
|
|
{
|
|
/// <summary>
|
|
/// Represents a table of guest address to a value.
|
|
/// </summary>
|
|
/// <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 IntPtr Address;
|
|
|
|
public AddressTablePage(bool isSparse, IntPtr 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<IntPtr> ensureMapped, PageInitDelegate pageInit)
|
|
{
|
|
var block = new SparseMemoryBlock(size, pageInit, null);
|
|
|
|
_trackingEvent = (ulong address, ulong size, bool write) =>
|
|
{
|
|
ulong pointer = (ulong)block.Block.Pointer + address;
|
|
ensureMapped((IntPtr)pointer);
|
|
return pointer;
|
|
};
|
|
|
|
bool added = NativeSignalHandler.AddTrackedRegion(
|
|
(nuint)block.Block.Pointer,
|
|
(nuint)(block.Block.Pointer + (IntPtr)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 readonly List<AddressTablePage> _pages;
|
|
private TEntry _fill;
|
|
|
|
private readonly MemoryBlock _sparseFill;
|
|
private readonly SparseMemoryBlock _fillBottomLevel;
|
|
private readonly TEntry* _fillBottomLevelPtr;
|
|
|
|
private readonly List<TableSparseBlock> _sparseReserved;
|
|
private readonly ReaderWriterLockSlim _sparseLock;
|
|
|
|
private ulong _sparseBlockSize;
|
|
private ulong _sparseReservedOffset;
|
|
|
|
public bool Sparse { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public ulong Mask { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public AddressTableLevel[] Levels { get; }
|
|
|
|
/// <inheritdoc/>
|
|
public TEntry Fill
|
|
{
|
|
get
|
|
{
|
|
return _fill;
|
|
}
|
|
set
|
|
{
|
|
UpdateFill(value);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IntPtr Base
|
|
{
|
|
get
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
|
|
lock (_pages)
|
|
{
|
|
return (IntPtr)GetRootPage();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
|
/// <see cref="Level"/>.
|
|
/// </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)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(levels);
|
|
|
|
_pages = new List<AddressTablePage>(capacity: 16);
|
|
|
|
Levels = levels;
|
|
Mask = 0;
|
|
|
|
foreach (var level in Levels)
|
|
{
|
|
Mask |= level.Mask;
|
|
}
|
|
|
|
Sparse = sparse;
|
|
|
|
if (sparse)
|
|
{
|
|
// If the address table is sparse, allocate a fill block
|
|
|
|
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
|
|
|
|
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
|
|
|
|
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
|
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
|
|
|
_sparseReserved = new List<TableSparseBlock>();
|
|
_sparseLock = new ReaderWriterLockSlim();
|
|
|
|
_sparseBlockSize = bottomLevelSize;
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
/// <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)
|
|
{
|
|
// Assume software memory means that we don't want to use any signal handlers.
|
|
bool sparse = type != MemoryManagerType.SoftwareMmu && type != 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signal that the given code range exists.
|
|
/// </summary>
|
|
/// <param name="address"></param>
|
|
/// <param name="size"></param>
|
|
public void SignalCodeRange(ulong address, ulong size)
|
|
{
|
|
AddressTableLevel bottom = Levels.Last();
|
|
ulong bottomLevelEntries = 1ul << bottom.Length;
|
|
|
|
ulong entryIndex = address >> bottom.Index;
|
|
ulong entries = size >> bottom.Index;
|
|
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
|
|
|
|
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
|
|
}
|
|
|
|
/// <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));
|
|
}
|
|
|
|
lock (_pages)
|
|
{
|
|
TEntry* page = GetPage(address);
|
|
|
|
int index = Levels[^1].GetValue(address);
|
|
|
|
EnsureMapped((IntPtr)(page + index));
|
|
|
|
return ref page[index];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the leaf page for the specified guest <paramref name="address"/>.
|
|
/// </summary>
|
|
/// <param name="address">Guest address</param>
|
|
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
|
|
private TEntry* GetPage(ulong address)
|
|
{
|
|
TEntry** page = GetRootPage();
|
|
|
|
for (int i = 0; i < Levels.Length - 1; i++)
|
|
{
|
|
ref AddressTableLevel level = ref Levels[i];
|
|
ref TEntry* nextPage = ref page[level.GetValue(address)];
|
|
|
|
if (nextPage == null || nextPage == _fillBottomLevelPtr)
|
|
{
|
|
ref AddressTableLevel nextLevel = ref Levels[i + 1];
|
|
|
|
if (i == Levels.Length - 2)
|
|
{
|
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true);
|
|
}
|
|
else
|
|
{
|
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false);
|
|
}
|
|
}
|
|
|
|
page = (TEntry**)nextPage;
|
|
}
|
|
|
|
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(IntPtr 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 + (IntPtr)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 IntPtr GetFillValue(int level)
|
|
{
|
|
if (_fillBottomLevel != null && level == Levels.Length - 2)
|
|
{
|
|
return (IntPtr)_fillBottomLevelPtr;
|
|
}
|
|
else
|
|
{
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
|
|
/// </summary>
|
|
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
|
|
private TEntry** GetRootPage()
|
|
{
|
|
if (_table == null)
|
|
{
|
|
if (Levels.Length == 1)
|
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true);
|
|
else
|
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
|
|
}
|
|
|
|
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()
|
|
{
|
|
var block = new TableSparseBlock(_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 IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
|
{
|
|
var size = sizeof(T) * length;
|
|
|
|
AddressTablePage page;
|
|
|
|
if (Sparse && leaf)
|
|
{
|
|
_sparseLock.EnterWriteLock();
|
|
|
|
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 + (IntPtr)_sparseReservedOffset);
|
|
|
|
_sparseReservedOffset += (ulong)size;
|
|
|
|
_sparseLock.ExitWriteLock();
|
|
}
|
|
else
|
|
{
|
|
var address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
|
|
page = new AddressTablePage(false, address);
|
|
|
|
var span = new Span<T>((void*)page.Address, length);
|
|
span.Fill(fill);
|
|
}
|
|
|
|
_pages.Add(page);
|
|
|
|
//TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
|
|
|
|
return page.Address;
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
foreach (var 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
/// </summary>
|
|
~AddressTable()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
}
|
|
}
|