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); } } }