using ARMeilleure.Diagnostics;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace ARMeilleure.Common
{
///
/// Represents a table of guest address to a value.
///
/// Type of the value
public unsafe class AddressTable : IDisposable where TEntry : unmanaged
{
///
/// If true, the sparse 2-level table should be used to improve performance.
/// If false, the platform doesn't properly support it, or will be negatively impacted.
///
public static bool UseSparseTable => true;
///
/// Represents a level in an .
///
public readonly struct Level
{
///
/// Gets the index of the in the guest address.
///
public int Index { get; }
///
/// Gets the length of the in the guest address.
///
public int Length { get; }
///
/// Gets the mask which masks the bits used by the .
///
public ulong Mask => ((1ul << Length) - 1) << Index;
///
/// Initializes a new instance of the structure with the specified
/// and .
///
/// Index of the
/// Length of the
public Level(int index, int length)
{
(Index, Length) = (index, length);
}
///
/// Gets the value of the from the specified guest .
///
/// Guest address
/// Value of the from the specified guest
public int GetValue(ulong address)
{
return (int)((address & Mask) >> Index);
}
}
private readonly struct AddressTablePage
{
public readonly bool IsSparse;
public readonly IntPtr Address;
public AddressTablePage(bool isSparse, IntPtr address)
{
IsSparse = isSparse;
Address = address;
}
}
private bool _disposed;
private TEntry** _table;
private readonly List _pages;
private TEntry _fill;
private readonly bool _sparse;
private readonly MemoryBlock _sparseFill;
private readonly SparseMemoryBlock _fillBottomLevel;
private readonly TEntry* _fillBottomLevelPtr;
private readonly List _sparseReserved;
private readonly ulong _sparseBlockSize;
private readonly ReaderWriterLockSlim _sparseLock;
private ulong _sparseReservedOffset;
///
/// Gets the bits used by the of the instance.
///
public ulong Mask { get; }
///
/// Gets the s used by the instance.
///
public Level[] Levels { get; }
///
/// Gets or sets the default fill value of newly created leaf pages.
///
public TEntry Fill
{
get
{
return _fill;
}
set
{
UpdateFill(value);
}
}
///
/// Gets the base address of the .
///
/// instance was disposed
public IntPtr Base
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
lock (_pages)
{
return (IntPtr)GetRootPage();
}
}
}
///
/// Constructs a new instance of the class with the specified list of
/// .
///
/// Levels for the address table
/// True if the bottom page should be sparsely mapped
/// is null
/// Length of is less than 2
public AddressTable(Level[] levels, bool sparse)
{
ArgumentNullException.ThrowIfNull(levels);
if (levels.Length < 2)
{
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
}
_pages = new List(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(65536, MemoryAllocationFlags.Mirrorable);
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
_sparseReserved = new List();
_sparseLock = new ReaderWriterLockSlim();
_sparseBlockSize = bottomLevelSize << 3;
}
}
private void UpdateFill(TEntry fillValue)
{
if (_sparseFill != null)
{
Span span = _sparseFill.GetSpan(0, (int)_sparseFill.Size);
MemoryMarshal.Cast(span).Fill(fillValue);
}
_fill = fillValue;
}
///
/// Determines if the specified is in the range of the
/// .
///
/// Guest address
/// if is valid; otherwise
public bool IsValid(ulong address)
{
return (address & ~Mask) == 0;
}
///
/// Gets a reference to the value at the specified guest .
///
/// Guest address
/// Reference to the value at the specified guest
/// instance was disposed
/// is not mapped
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];
}
}
///
/// Gets the leaf page for the specified guest .
///
/// Guest address
/// Leaf page for the specified guest
private TEntry* GetPage(ulong address)
{
TEntry** page = GetRootPage();
for (int i = 0; i < Levels.Length - 1; i++)
{
ref Level level = ref Levels[i];
ref TEntry* nextPage = ref page[level.GetValue(address)];
if (nextPage == null || nextPage == _fillBottomLevelPtr)
{
ref Level 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;
}
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 (SparseMemoryBlock sparse in _sparseReserved)
{
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
{
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
break;
}
}
}
finally
{
_sparseLock.ExitReadLock();
}
}
}
private IntPtr GetFillValue(int level)
{
if (_fillBottomLevel != null && level == Levels.Length - 2)
{
return (IntPtr)_fillBottomLevelPtr;
}
else
{
return IntPtr.Zero;
}
}
///
/// Lazily initialize and get the root page of the .
///
/// Root page of the
private TEntry** GetRootPage()
{
if (_table == null)
{
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
}
return _table;
}
private void InitLeafPage(Span page)
{
MemoryMarshal.Cast(page).Fill(_fill);
}
///
/// Allocates a block of memory of the specified type and length.
///
/// Type of elements
/// Number of elements
/// Fill value
/// if leaf; otherwise
/// Allocated block
private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged
{
var size = sizeof(T) * length;
AddressTablePage page;
if (_sparse && leaf)
{
_sparseLock.EnterWriteLock();
if (_sparseReserved.Count == 0 || _sparseReservedOffset == _sparseBlockSize)
{
_sparseReserved.Add(new SparseMemoryBlock(_sparseBlockSize, InitLeafPage, _sparseFill));
_sparseReservedOffset = 0;
}
SparseMemoryBlock block = _sparseReserved.Last();
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((void*)page.Address, length);
span.Fill(fill);
}
_pages.Add(page);
TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
return page.Address;
}
///
/// 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)
{
foreach (var page in _pages)
{
if (!page.IsSparse)
{
Marshal.FreeHGlobal(page.Address);
}
}
if (_sparse)
{
foreach (SparseMemoryBlock block in _sparseReserved)
{
block.Dispose();
}
_fillBottomLevel.Dispose();
_sparseFill.Dispose();
_sparseLock.Dispose();
}
_disposed = true;
}
}
///
/// Frees resources used by the instance.
///
~AddressTable()
{
Dispose(false);
}
}
}