This commit is contained in:
riperiperi 2024-09-18 23:25:45 +01:00 committed by GitHub
commit f363a04c7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 962 additions and 311 deletions

View File

@ -1,252 +0,0 @@
using ARMeilleure.Diagnostics;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
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> : IDisposable where TEntry : unmanaged
{
/// <summary>
/// Represents a level in an <see cref="AddressTable{TEntry}"/>.
/// </summary>
public readonly struct Level
{
/// <summary>
/// Gets the index of the <see cref="Level"/> in the guest address.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets the length of the <see cref="Level"/> in the guest address.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the mask which masks the bits used by the <see cref="Level"/>.
/// </summary>
public ulong Mask => ((1ul << Length) - 1) << Index;
/// <summary>
/// Initializes a new instance of the <see cref="Level"/> structure with the specified
/// <paramref name="index"/> and <paramref name="length"/>.
/// </summary>
/// <param name="index">Index of the <see cref="Level"/></param>
/// <param name="length">Length of the <see cref="Level"/></param>
public Level(int index, int length)
{
(Index, Length) = (index, length);
}
/// <summary>
/// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns>
public int GetValue(ulong address)
{
return (int)((address & Mask) >> Index);
}
}
private bool _disposed;
private TEntry** _table;
private readonly List<IntPtr> _pages;
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public ulong Mask { get; }
/// <summary>
/// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
public Level[] Levels { get; }
/// <summary>
/// Gets or sets the default fill value of newly created leaf pages.
/// </summary>
public TEntry Fill { get; set; }
/// <summary>
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
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>
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
public AddressTable(Level[] levels)
{
ArgumentNullException.ThrowIfNull(levels);
if (levels.Length < 2)
{
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
}
_pages = new List<IntPtr>(capacity: 16);
Levels = levels;
Mask = 0;
foreach (var level in Levels)
{
Mask |= level.Mask;
}
}
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="AddressTable{TEntry}"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
public bool IsValid(ulong address)
{
return (address & ~Mask) == 0;
}
/// <summary>
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
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)
{
return ref GetPage(address)[Levels[^1].GetValue(address)];
}
}
/// <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 Level level = ref Levels[i];
ref TEntry* nextPage = ref page[level.GetValue(address)];
if (nextPage == null)
{
ref Level nextLevel = ref Levels[i + 1];
nextPage = i == Levels.Length - 2 ?
(TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) :
(TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false);
}
page = (TEntry**)nextPage;
}
return (TEntry*)page;
}
/// <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)
{
_table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false);
}
return _table;
}
/// <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;
var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
var span = new Span<T>((void*)page, length);
span.Fill(fill);
_pages.Add(page);
TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
return page;
}
/// <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)
{
Marshal.FreeHGlobal(page);
}
_disposed = true;
}
}
/// <summary>
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
/// </summary>
~AddressTable()
{
Dispose(false);
}
}
}

View File

@ -0,0 +1,44 @@
namespace ARMeilleure.Common
{
/// <summary>
/// Represents a level in an <see cref="IAddressTable{TEntry}"/>.
/// </summary>
public readonly struct AddressTableLevel
{
/// <summary>
/// Gets the index of the <see cref="Level"/> in the guest address.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets the length of the <see cref="AddressTableLevel"/> in the guest address.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the mask which masks the bits used by the <see cref="AddressTableLevel"/>.
/// </summary>
public ulong Mask => ((1ul << Length) - 1) << Index;
/// <summary>
/// Initializes a new instance of the <see cref="AddressTableLevel"/> structure with the specified
/// <paramref name="index"/> and <paramref name="length"/>.
/// </summary>
/// <param name="index">Index of the <see cref="AddressTableLevel"/></param>
/// <param name="length">Length of the <see cref="AddressTableLevel"/></param>
public AddressTableLevel(int index, int length)
{
(Index, Length) = (index, length);
}
/// <summary>
/// Gets the value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/></returns>
public int GetValue(ulong address)
{
return (int)((address & Mask) >> Index);
}
}
}

View File

@ -0,0 +1,51 @@
namespace ARMeilleure.Common
{
public static class AddressTablePresets
{
private static readonly AddressTableLevel[] _levels64Bit =
new AddressTableLevel[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 2, 5),
};
private static readonly AddressTableLevel[] _levels32Bit =
new AddressTableLevel[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 1, 6),
};
private static readonly AddressTableLevel[] _levels64BitSparse =
new AddressTableLevel[]
{
new(23, 16),
new( 2, 21),
};
private static readonly AddressTableLevel[] _levels32BitSparse =
new AddressTableLevel[]
{
new(22, 10),
new( 1, 21),
};
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse)
{
if (sparse)
{
return for64Bits ? _levels64BitSparse : _levels32BitSparse;
}
else
{
return for64Bits ? _levels64Bit : _levels32Bit;
}
}
}
}

View File

@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.Common namespace ARMeilleure.Common
{ {
unsafe abstract class Allocator : IDisposable public unsafe abstract class Allocator : IDisposable
{ {
public T* Allocate<T>(ulong count = 1) where T : unmanaged public T* Allocate<T>(ulong count = 1) where T : unmanaged
{ {

View File

@ -0,0 +1,51 @@
using System;
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.
/// </summary>
bool Sparse { get; }
/// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
ulong Mask { get; }
/// <summary>
/// Gets the <see cref="AddressTableLevel"/>s used by the <see cref="IAddressTable{TEntry}"/> instance.
/// </summary>
AddressTableLevel[] Levels { get; }
/// <summary>
/// Gets or sets the default fill value of newly created leaf pages.
/// </summary>
TEntry Fill { get; set; }
/// <summary>
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
IntPtr Base { get; }
/// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="IAddressTable{TEntry}"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
bool IsValid(ulong address);
/// <summary>
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
/// </summary>
/// <param name="address">Guest address</param>
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
ref TEntry GetValue(ulong address);
}
}

View File

@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
namespace ARMeilleure.Common namespace ARMeilleure.Common
{ {
unsafe sealed class NativeAllocator : Allocator public unsafe sealed class NativeAllocator : Allocator
{ {
public static NativeAllocator Instance { get; } = new(); public static NativeAllocator Instance { get; } = new();

View File

@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions
Operand hostAddress; Operand hostAddress;
var table = context.FunctionTable;
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
// onto the dispatch stub. // onto the dispatch stub.
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
@ -203,6 +205,36 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, hostAddressAddr); hostAddress = context.Load(OperandType.I64, hostAddressAddr);
} }
else if (table.Sparse && table.Levels.Length == 2)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
// Deliberately attempts to avoid branches.
var level0 = table.Levels[0];
int clearBits0 = 64 - (level0.Index + level0.Length);
Operand index = context.ShiftLeft(
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits0)), Const(clearBits0 + level0.Index)),
Const(3)
);
Operand tableBase = !context.HasPtc ?
Const(table.Base) :
Const(table.Base, Ptc.FunctionTableSymbol);
Operand page = context.Load(OperandType.I64, context.Add(tableBase, index));
// Second level
var level1 = table.Levels[1];
int clearBits1 = 64 - (level1.Index + level1.Length);
Operand index2 = context.ShiftLeft(
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits1)), Const(clearBits1 + level1.Index)),
Const(3)
);
hostAddress = context.Load(OperandType.I64, context.Add(page, index2));
}
else else
{ {
hostAddress = !context.HasPtc ? hostAddress = !context.HasPtc ?

View File

@ -8,7 +8,7 @@ namespace ARMeilleure.Signal
{ {
public static class NativeSignalHandlerGenerator public static class NativeSignalHandlerGenerator
{ {
public const int MaxTrackedRanges = 8; public const int MaxTrackedRanges = 16;
private const int StructAddressOffset = 0; private const int StructAddressOffset = 0;
private const int StructWriteOffset = 4; private const int StructWriteOffset = 4;

View File

@ -46,7 +46,7 @@ namespace ARMeilleure.Translation
public IMemoryManager Memory { get; } public IMemoryManager Memory { get; }
public EntryTable<uint> CountTable { get; } public EntryTable<uint> CountTable { get; }
public AddressTable<ulong> FunctionTable { get; } public IAddressTable<ulong> FunctionTable { get; }
public TranslatorStubs Stubs { get; } public TranslatorStubs Stubs { get; }
public ulong EntryAddress { get; } public ulong EntryAddress { get; }
@ -62,7 +62,7 @@ namespace ARMeilleure.Translation
public ArmEmitterContext( public ArmEmitterContext(
IMemoryManager memory, IMemoryManager memory,
EntryTable<uint> countTable, EntryTable<uint> countTable,
AddressTable<ulong> funcTable, IAddressTable<ulong> funcTable,
TranslatorStubs stubs, TranslatorStubs stubs,
ulong entryAddress, ulong entryAddress,
bool highCq, bool highCq,

View File

@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project. private const uint InternalVersion = 6978; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0"; private const string ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";
@ -40,6 +40,7 @@ namespace ARMeilleure.Translation.PTC
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4);
private const byte FillingByte = 0x00; private const byte FillingByte = 0x00;
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
@ -705,6 +706,10 @@ namespace ARMeilleure.Translation.PTC
{ {
imm = translator.Stubs.DispatchStub; imm = translator.Stubs.DispatchStub;
} }
else if (symbol == FunctionTableSymbol)
{
imm = translator.FunctionTable.Base;
}
if (imm == null) if (imm == null)
{ {

View File

@ -22,33 +22,13 @@ namespace ARMeilleure.Translation
{ {
public class Translator public class Translator
{ {
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
new AddressTable<ulong>.Level[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 2, 5),
};
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
new AddressTable<ulong>.Level[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 1, 6),
};
private readonly IJitMemoryAllocator _allocator; private readonly IJitMemoryAllocator _allocator;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
private readonly Ptc _ptc; private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; } internal TranslatorCache<TranslatedFunction> Functions { get; }
internal AddressTable<ulong> FunctionTable { get; } internal IAddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; } internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; } internal TranslatorStubs Stubs { get; }
internal TranslatorQueue Queue { get; } internal TranslatorQueue Queue { get; }
@ -57,7 +37,7 @@ namespace ARMeilleure.Translation
private Thread[] _backgroundTranslationThreads; private Thread[] _backgroundTranslationThreads;
private volatile int _threadCount; private volatile int _threadCount;
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable<ulong> functionTable)
{ {
_allocator = allocator; _allocator = allocator;
Memory = memory; Memory = memory;
@ -72,7 +52,7 @@ namespace ARMeilleure.Translation
CountTable = new EntryTable<uint>(); CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>(); Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit); FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable); Stubs = new TranslatorStubs(FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;

View File

@ -19,7 +19,7 @@ namespace ARMeilleure.Translation
private bool _disposed; private bool _disposed;
private readonly AddressTable<ulong> _functionTable; private readonly IAddressTable<ulong> _functionTable;
private readonly Lazy<IntPtr> _dispatchStub; private readonly Lazy<IntPtr> _dispatchStub;
private readonly Lazy<DispatcherFunction> _dispatchLoop; private readonly Lazy<DispatcherFunction> _dispatchLoop;
private readonly Lazy<WrapperFunction> _contextWrapper; private readonly Lazy<WrapperFunction> _contextWrapper;
@ -86,7 +86,7 @@ namespace ARMeilleure.Translation
/// </summary> /// </summary>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param> /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(AddressTable<ulong> functionTable) public TranslatorStubs(IAddressTable<ulong> functionTable)
{ {
ArgumentNullException.ThrowIfNull(functionTable); ArgumentNullException.ThrowIfNull(functionTable);

View File

@ -0,0 +1,486 @@
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);
if (levels.Length < 2)
{
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(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(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<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)
{
_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);
}
}
}

View File

@ -1,3 +1,4 @@
using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using Ryujinx.Cpu.Signal; using Ryujinx.Cpu.Signal;
@ -9,11 +10,14 @@ namespace Ryujinx.Cpu.Jit
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;
private readonly Translator _translator; private readonly Translator _translator;
private readonly AddressTable<ulong> _functionTable;
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
{ {
_tickSource = tickSource; _tickSource = tickSource;
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); _functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
if (memory.Type.IsHostMappedOrTracked()) if (memory.Type.IsHostMappedOrTracked())
{ {
@ -55,6 +59,7 @@ namespace Ryujinx.Cpu.Jit
/// <inheritdoc/> /// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size) public void PrepareCodeRange(ulong address, ulong size)
{ {
_functionTable.SignalCodeRange(address, size);
_translator.PrepareCodeRange(address, size); _translator.PrepareCodeRange(address, size);
} }

View File

@ -140,6 +140,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
bool isTail = false) bool isTail = false)
{ {
int tempRegister; int tempRegister;
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Sparse && funcTable.Levels.Length == 2;
if (guestAddress.Kind == OperandKind.Constant) if (guestAddress.Kind == OperandKind.Constant)
{ {
@ -153,9 +156,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
else else
{ {
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
if (inlineLookup && guestAddress.Value == 0)
{
// X0 will be overwritten. Move the address to a temp register.
tempGuestAddress = regAlloc.AllocateTempGprRegister();
asm.Mov(Register(tempGuestAddress), guestAddress);
}
} }
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; tempRegister = NextFreeRegister(1, tempGuestAddress);
if (!isTail) if (!isTail)
{ {
@ -176,6 +186,45 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
asm.Mov(rn, funcPtrLoc & ~0xfffUL); asm.Mov(rn, funcPtrLoc & ~0xfffUL);
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
} }
else if (inlineLookup)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
if (tempGuestAddress != -1)
{
guestAddress = Register(tempGuestAddress);
}
var level0 = funcTable.Levels[0];
asm.Ubfx(indexReg, guestAddress, level0.Index, level0.Length);
asm.Lsl(indexReg, indexReg, Const(3));
ulong tableBase = (ulong)funcTable.Base;
// Index into the table.
asm.Mov(rn, tableBase);
asm.Add(rn, rn, indexReg);
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
var level1 = funcTable.Levels[1];
asm.Ubfx(indexReg, guestAddress, level1.Index, level1.Length);
asm.Lsl(indexReg, indexReg, Const(3));
// Index into the page.
asm.Add(rn, rn, indexReg);
// Load the final branch address
asm.LdrRiUn(rn, rn, 0);
if (tempGuestAddress != -1)
{
regAlloc.FreeTempGprRegister(tempGuestAddress);
}
}
else else
{ {
asm.Mov(rn, (ulong)funcPtr); asm.Mov(rn, (ulong)funcPtr);
@ -252,5 +301,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{ {
return new Operand(register, RegisterType.Integer, type); return new Operand(register, RegisterType.Integer, type);
} }
private static Operand Const(long value, OperandType type = OperandType.I64)
{
return new Operand(type, (ulong)value);
}
private static int NextFreeRegister(int start, int avoid)
{
if (start == avoid)
{
start++;
}
return start;
}
} }
} }

View File

@ -305,6 +305,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
bool isTail = false) bool isTail = false)
{ {
int tempRegister; int tempRegister;
int tempGuestAddress = -1;
bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Sparse && funcTable.Levels.Length == 2;
if (guestAddress.Kind == OperandKind.Constant) if (guestAddress.Kind == OperandKind.Constant)
{ {
@ -318,9 +321,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
else else
{ {
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
if (inlineLookup && guestAddress.Value == 0)
{
// X0 will be overwritten. Move the address to a temp register.
tempGuestAddress = regAlloc.AllocateTempGprRegister();
asm.Mov(Register(tempGuestAddress), guestAddress);
}
} }
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; tempRegister = NextFreeRegister(1, tempGuestAddress);
if (!isTail) if (!isTail)
{ {
@ -341,6 +351,45 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
asm.Mov(rn, funcPtrLoc & ~0xfffUL); asm.Mov(rn, funcPtrLoc & ~0xfffUL);
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
} }
else if (inlineLookup)
{
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
if (tempGuestAddress != -1)
{
guestAddress = Register(tempGuestAddress);
}
var level0 = funcTable.Levels[0];
asm.Ubfx(indexReg, guestAddress, level0.Index, level0.Length);
asm.Lsl(indexReg, indexReg, Const(3));
ulong tableBase = (ulong)funcTable.Base;
// Index into the table.
asm.Mov(rn, tableBase);
asm.Add(rn, rn, indexReg);
// Load the page address.
asm.LdrRiUn(rn, rn, 0);
var level1 = funcTable.Levels[1];
asm.Ubfx(indexReg, guestAddress, level1.Index, level1.Length);
asm.Lsl(indexReg, indexReg, Const(3));
// Index into the page.
asm.Add(rn, rn, indexReg);
// Load the final branch address
asm.LdrRiUn(rn, rn, 0);
if (tempGuestAddress != -1)
{
regAlloc.FreeTempGprRegister(tempGuestAddress);
}
}
else else
{ {
asm.Mov(rn, (ulong)funcPtr); asm.Mov(rn, (ulong)funcPtr);
@ -613,5 +662,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{ {
return new Operand(register, RegisterType.Integer, type); return new Operand(register, RegisterType.Integer, type);
} }
private static Operand Const(long value, OperandType type = OperandType.I64)
{
return new Operand(type, (ulong)value);
}
private static int NextFreeRegister(int start, int avoid)
{
if (start == avoid)
{
start++;
}
return start;
}
} }
} }

View File

@ -1,3 +1,4 @@
using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.Jit;
using Ryujinx.Cpu.LightningJit.State; using Ryujinx.Cpu.LightningJit.State;
@ -8,11 +9,15 @@ namespace Ryujinx.Cpu.LightningJit
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;
private readonly Translator _translator; private readonly Translator _translator;
private readonly AddressTable<ulong> _functionTable;
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
{ {
_tickSource = tickSource; _tickSource = tickSource;
_translator = new Translator(memory, for64Bit); _functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
_translator = new Translator(memory, _functionTable);
memory.UnmapEvent += UnmapHandler; memory.UnmapEvent += UnmapHandler;
} }
@ -48,6 +53,7 @@ namespace Ryujinx.Cpu.LightningJit
/// <inheritdoc/> /// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size) public void PrepareCodeRange(ulong address, ulong size)
{ {
_functionTable.SignalCodeRange(address, size);
} }
public void Dispose() public void Dispose()

View File

@ -19,25 +19,6 @@ namespace Ryujinx.Cpu.LightningJit
// Should be enabled on platforms that enforce W^X. // Should be enabled on platforms that enforce W^X.
private static bool IsNoWxPlatform => false; private static bool IsNoWxPlatform => false;
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
new AddressTable<ulong>.Level[]
{
new(31, 17),
new(23, 8),
new(15, 8),
new( 7, 8),
new( 2, 5),
};
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
new AddressTable<ulong>.Level[]
{
new(23, 9),
new(15, 8),
new( 7, 8),
new( 1, 6),
};
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
private readonly NoWxCache _noWxCache; private readonly NoWxCache _noWxCache;
private bool _disposed; private bool _disposed;
@ -47,7 +28,7 @@ namespace Ryujinx.Cpu.LightningJit
internal TranslatorStubs Stubs { get; } internal TranslatorStubs Stubs { get; }
internal IMemoryManager Memory { get; } internal IMemoryManager Memory { get; }
public Translator(IMemoryManager memory, bool for64Bits) public Translator(IMemoryManager memory, AddressTable<ulong> functionTable)
{ {
Memory = memory; Memory = memory;
@ -63,7 +44,7 @@ namespace Ryujinx.Cpu.LightningJit
} }
Functions = new TranslatorCache<TranslatedFunction>(); Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit); FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable, _noWxCache); Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;

View File

@ -0,0 +1,125 @@
using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Ryujinx.Memory
{
public delegate void PageInitDelegate(Span<byte> page);
public class SparseMemoryBlock : IDisposable
{
private const ulong MapGranularity = 1UL << 17;
private readonly PageInitDelegate _pageInit;
private readonly object _lock = new object();
private readonly ulong _pageSize;
private readonly MemoryBlock _reservedBlock;
private readonly List<MemoryBlock> _mappedBlocks;
private ulong _mappedBlockUsage;
private readonly ulong[] _mappedPageBitmap;
public MemoryBlock Block => _reservedBlock;
public SparseMemoryBlock(ulong size, PageInitDelegate pageInit, MemoryBlock fill)
{
_pageSize = MemoryBlock.GetPageSize();
_reservedBlock = new MemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible);
_mappedBlocks = new List<MemoryBlock>();
_pageInit = pageInit;
int pages = (int)BitUtils.DivRoundUp(size, _pageSize);
int bitmapEntries = BitUtils.DivRoundUp(pages, 64);
_mappedPageBitmap = new ulong[bitmapEntries];
if (fill != null)
{
// Fill the block with mappings from the fill block.
if (fill.Size % _pageSize != 0)
{
throw new ArgumentException("Fill memory block should be page aligned.", nameof(fill));
}
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;
}
}
// If a fill block isn't provided, the pages that aren't EnsureMapped are unmapped.
// The caller can rely on signal handler to fill empty pages instead.
}
private void MapPage(ulong pageOffset)
{
// Take a page from the latest mapped block.
MemoryBlock block = _mappedBlocks.LastOrDefault();
if (block == null || _mappedBlockUsage == MapGranularity)
{
// Need to map some more memory.
block = new MemoryBlock(MapGranularity, MemoryAllocationFlags.Mirrorable);
_mappedBlocks.Add(block);
_mappedBlockUsage = 0;
}
_pageInit(block.GetSpan(_mappedBlockUsage, (int)_pageSize));
_reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize);
_mappedBlockUsage += _pageSize;
}
public void EnsureMapped(ulong offset)
{
int pageIndex = (int)(offset / _pageSize);
int bitmapIndex = pageIndex >> 6;
ref ulong entry = ref _mappedPageBitmap[bitmapIndex];
ulong bit = 1UL << (pageIndex & 63);
if ((Volatile.Read(ref entry) & bit) == 0)
{
// Not mapped.
lock (_lock)
{
// Check the bit while locked to make sure that this only happens once.
ulong lockedEntry = Volatile.Read(ref entry);
if ((lockedEntry & bit) == 0)
{
MapPage(offset & ~(_pageSize - 1));
lockedEntry |= bit;
Interlocked.Exchange(ref entry, lockedEntry);
}
}
}
}
public void Dispose()
{
_reservedBlock.Dispose();
foreach (MemoryBlock block in _mappedBlocks)
{
block.Dispose();
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,3 +1,4 @@
using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation; using ARMeilleure.Translation;
@ -12,7 +13,7 @@ namespace Ryujinx.Tests.Cpu
public CpuContext(IMemoryManager memory, bool for64Bit) public CpuContext(IMemoryManager memory, bool for64Bit)
{ {
_translator = new Translator(new JitMemoryAllocator(), memory, for64Bit); _translator = new Translator(new JitMemoryAllocator(), memory, AddressTable<ulong>.CreateForArm(for64Bit, memory.Type));
memory.UnmapEvent += UnmapHandler; memory.UnmapEvent += UnmapHandler;
} }

View File

@ -1,3 +1,4 @@
using ARMeilleure.Common;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using NUnit.Framework; using NUnit.Framework;
using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.Jit;
@ -17,7 +18,10 @@ namespace Ryujinx.Tests.Cpu
private static void EnsureTranslator() private static void EnsureTranslator()
{ {
// Create a translator, as one is needed to register the signal handler or emit methods. // Create a translator, as one is needed to register the signal handler or emit methods.
_translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); _translator ??= new Translator(
new JitMemoryAllocator(),
new MockMemoryManager(),
AddressTable<ulong>.CreateForArm(true, ARMeilleure.Memory.MemoryManagerType.SoftwarePageTable));
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]

View File

@ -1,3 +1,4 @@
using ARMeilleure.Common;
using ARMeilleure.Signal; using ARMeilleure.Signal;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using NUnit.Framework; using NUnit.Framework;
@ -53,7 +54,10 @@ namespace Ryujinx.Tests.Memory
private static void EnsureTranslator() private static void EnsureTranslator()
{ {
// Create a translator, as one is needed to register the signal handler or emit methods. // Create a translator, as one is needed to register the signal handler or emit methods.
_translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); _translator ??= new Translator(
new JitMemoryAllocator(),
new MockMemoryManager(),
AddressTable<ulong>.CreateForArm(true, ARMeilleure.Memory.MemoryManagerType.SoftwarePageTable));
} }
[Test] [Test]