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
{
///
/// Represents a table of guest address to a value.
///
/// Type of the value
public unsafe class SparseAddressTable : IAddressTable where TEntry : unmanaged
{
///
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
///
private readonly struct TableSparseBlock : IDisposable
{
public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action 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;
///
public AddressTableType TableType => AddressTableType.Sparse;
///
public ulong Mask { get; }
///
public AddressTableLevel[] Levels { get; }
///
public TEntry Fill
{
get
{
return _fill;
}
set
{
UpdateFill(value);
}
}
///
public nint Base
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return (nint)_table;
}
}
///
/// Constructs a new instance of the class with the specified list of
/// .
///
/// Levels for the address table
/// is null
/// Length of is less than 2
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;
}
///
/// Create an instance for an ARM function table.
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
///
/// True if the guest is A64, false otherwise
/// An for ARM function lookup
public static SparseAddressTable CreateForArm(bool for64Bits)
{
return new SparseAddressTable(AddressTablePresets.GetArmPreset(for64Bits, true));
}
///
/// Update the fill value for the bottom level of the table.
///
/// New fill value
private void UpdateFill(TEntry fillValue)
{
if (_fillBlock != null)
{
Span span = _fillBlock.GetSpan(0, (int)_fillBlock.Size);
MemoryMarshal.Cast(span).Fill(fillValue);
}
_fill = fillValue;
}
///
/// Signal that the given code range exists.
///
///
///
public void SignalCodeRange(ulong address, ulong size)
{
}
///
public bool IsValid(ulong address)
{
return (address & ~Mask) == 0;
}
///
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];
}
///
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
///
/// Pointer to be mapped
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));
}
}
///
/// Initialize a leaf page with the fill value.
///
/// Page to initialize
private void InitLeafPage(Span page)
{
MemoryMarshal.Cast(page).Fill(_fill);
}
///
/// Releases all resources used by the instance.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases all unmanaged and optionally managed resources used by the
/// instance.
///
/// to dispose managed resources also; otherwise just unmanaged resouces
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_block.Dispose();
_fillBlock.Dispose();
_disposed = true;
}
///
/// Frees resources used by the instance.
///
~SparseAddressTable()
{
Dispose(false);
}
}
}