From 2548e8d203975fc2eed89c50fe94970de9507909 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 15 Jun 2024 20:34:50 +0100 Subject: [PATCH 01/13] 2 level inline table (armeilleure) TODOS: - Lightning JIT - Sparse page using signal handler - Fallback page? Would save a few instructions --- src/ARMeilleure/Common/AddressTable.cs | 32 ++++++++++++++- .../Instructions/InstEmitFlowHelper.cs | 41 +++++++++++++++++++ src/ARMeilleure/Translation/PTC/Ptc.cs | 10 +++++ src/ARMeilleure/Translation/Translator.cs | 29 ++++++++++++- 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs index fcab3a202..5b6d48bbc 100644 --- a/src/ARMeilleure/Common/AddressTable.cs +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -56,6 +56,8 @@ namespace ARMeilleure.Common private bool _disposed; private TEntry** _table; private readonly List _pages; + private readonly TEntry* _fallbackTable; + private TEntry _fill; /// /// Gets the bits used by the of the instance. @@ -70,7 +72,18 @@ namespace ARMeilleure.Common /// /// Gets or sets the default fill value of newly created leaf pages. /// - public TEntry Fill { get; set; } + public TEntry Fill + { + get + { + return _fill; + } + set + { + *_fallbackTable = value; + _fill = value; + } + } /// /// Gets the base address of the . @@ -89,6 +102,19 @@ namespace ARMeilleure.Common } } + /// + /// Gets a pointer to a single entry table containing only the leaf fill value. + /// + public IntPtr Fallback + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return (IntPtr)_fallbackTable; + } + } + /// /// Constructs a new instance of the class with the specified list of /// . @@ -113,6 +139,8 @@ namespace ARMeilleure.Common { Mask |= level.Mask; } + + _fallbackTable = (TEntry*)NativeAllocator.Instance.Allocate((ulong)sizeof(TEntry)); } /// @@ -237,6 +265,8 @@ namespace ARMeilleure.Common Marshal.FreeHGlobal(page); } + Marshal.FreeHGlobal((IntPtr)_fallbackTable); + _disposed = true; } } diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index 2009bafda..fbfdcefce 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions Operand hostAddress; + var table = context.FunctionTable; + // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback // onto the dispatch stub. if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) @@ -203,6 +205,45 @@ namespace ARMeilleure.Instructions hostAddress = context.Load(OperandType.I64, hostAddressAddr); } + else if (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]; + + // Currently no bounds check. Maybe conditionally do this for unsafe host mapped. + Operand index = context.ShiftLeft(context.ShiftRightUI(guestAddress, Const(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 clearBits = 64 - (level1.Index + level1.Length); + + Operand index2 = context.ShiftLeft( + context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level1.Index)), + Const(3) + ); + + // TODO: could possibly make a fallback page that level 1 is filled with that contains dispatch stub on all pages + // Would save this load and the comparisons + // 16MB of the same value is a bit wasteful so it could replicate with remapping. + + Operand fallback = !context.HasPtc ? + Const((long)context.FunctionTable.Fallback) : + Const((long)context.FunctionTable.Fallback, Ptc.DispatchFallbackSymbol); + + Operand pageIsZero = context.ICompareEqual(page, Const(0L)); + + // Small trick to keep this branchless - if the page is zero, load a fallback table entry that always contains the dispatch stub. + hostAddress = context.Load(OperandType.I64, context.ConditionalSelect(pageIsZero, fallback, context.Add(page, index2))); + } else { hostAddress = !context.HasPtc ? diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index c2eed7a55..569c28739 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -40,6 +40,8 @@ namespace ARMeilleure.Translation.PTC public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); + public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4); + public static readonly Symbol DispatchFallbackSymbol = new(SymbolType.Special, 5); private const byte FillingByte = 0x00; private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; @@ -705,6 +707,14 @@ namespace ARMeilleure.Translation.PTC { imm = translator.Stubs.DispatchStub; } + else if (symbol == FunctionTableSymbol) + { + imm = translator.FunctionTable.Base; + } + else if (symbol == DispatchFallbackSymbol) + { + imm = translator.FunctionTable.Fallback; + } if (imm == null) { diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 014b12035..c3796cb99 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -22,6 +22,8 @@ namespace ARMeilleure.Translation { public class Translator { + private const bool UseSparseTable = true; + private static readonly AddressTable.Level[] _levels64Bit = new AddressTable.Level[] { @@ -42,6 +44,20 @@ namespace ARMeilleure.Translation new( 1, 6), }; + private static readonly AddressTable.Level[] _levels64BitSparse = + new AddressTable.Level[] + { + new(23, 16), + new( 2, 21), + }; + + private static readonly AddressTable.Level[] _levels32BitSparse = + new AddressTable.Level[] + { + new(22, 10), + new( 1, 21), + }; + private readonly IJitMemoryAllocator _allocator; private readonly ConcurrentQueue> _oldFuncs; @@ -70,9 +86,20 @@ namespace ARMeilleure.Translation JitCache.Initialize(allocator); + AddressTable.Level[] levels; + + if (UseSparseTable) + { + levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; + } + else + { + levels = for64Bits ? _levels64Bit : _levels32Bit; + } + CountTable = new EntryTable(); Functions = new TranslatorCache(); - FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + FunctionTable = new AddressTable(levels); Stubs = new TranslatorStubs(FunctionTable); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; From eb83f0ca317cabc791781d51b271464fc864cbfc Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 15 Jun 2024 21:55:55 +0100 Subject: [PATCH 02/13] Lightning JIT test impl --- .../Arm32/Target/Arm64/InstEmitFlow.cs | 56 +++++++++++++++++++ .../Arm64/Target/Arm64/InstEmitSystem.cs | 56 +++++++++++++++++++ src/Ryujinx.Cpu/LightningJit/Translator.cs | 29 +++++++++- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index 3b1ff5a2a..9d7581580 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -140,6 +140,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 bool isTail = false) { int tempRegister; + int tempGuestAddress = 0; + + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; if (guestAddress.Kind == OperandKind.Constant) { @@ -153,6 +156,13 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 else { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + if (inlineLookup) + { + // Might be overwritten. Move the address to a temp register. + tempGuestAddress = regAlloc.AllocateTempGprRegister(); + asm.Mov(Register(tempGuestAddress), guestAddress); + } } tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; @@ -176,6 +186,47 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 asm.Mov(rn, 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(3); + 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)); + + // Is the page address zero? Make sure to use the fallback if it is. + asm.Tst(rn, rn); + + // Index into the page. + asm.Add(rn, rn, indexReg); + + // Reuse the index register for the fallback + ulong fallback = (ulong)funcTable.Fallback; + asm.Mov(indexReg, fallback); + + asm.Csel(rn, indexReg, rn, ArmCondition.Eq); + + // Load the final branch address + asm.LdrRiUn(rn, rn, 0); + + regAlloc.FreeTempGprRegister(tempGuestAddress); + } else { asm.Mov(rn, (ulong)funcPtr); @@ -252,5 +303,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { return new Operand(register, RegisterType.Integer, type); } + + private static Operand Const(long value, OperandType type = OperandType.I64) + { + return new Operand(type, (ulong)value); + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index 82cb29d73..342f5c698 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -305,6 +305,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 bool isTail = false) { int tempRegister; + int tempGuestAddress = 0; + + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; if (guestAddress.Kind == OperandKind.Constant) { @@ -318,6 +321,13 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 else { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + if (inlineLookup) + { + // Might be overwritten. Move the address to a temp register. + tempGuestAddress = regAlloc.AllocateTempGprRegister(); + asm.Mov(Register(tempGuestAddress), guestAddress); + } } tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; @@ -341,6 +351,47 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 asm.Mov(rn, 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(3); + 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)); + + // Is the page address zero? Make sure to use the fallback if it is. + asm.Tst(rn, rn); + + // Index into the page. + asm.Add(rn, rn, indexReg); + + // Reuse the index register for the fallback + ulong fallback = (ulong)funcTable.Fallback; + asm.Mov(indexReg, fallback); + + asm.Csel(rn, indexReg, rn, ArmCondition.Eq); + + // Load the final branch address + asm.LdrRiUn(rn, rn, 0); + + regAlloc.FreeTempGprRegister(tempGuestAddress); + } else { asm.Mov(rn, (ulong)funcPtr); @@ -613,5 +664,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 { return new Operand(register, RegisterType.Integer, type); } + + private static Operand Const(long value, OperandType type = OperandType.I64) + { + return new Operand(type, (ulong)value); + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index d62410253..8b1b875f4 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -16,6 +16,8 @@ namespace Ryujinx.Cpu.LightningJit { class Translator : IDisposable { + private const bool UseSparseTable = true; + // Should be enabled on platforms that enforce W^X. private static bool IsNoWxPlatform => false; @@ -38,6 +40,20 @@ namespace Ryujinx.Cpu.LightningJit new( 1, 6), }; + private static readonly AddressTable.Level[] _levels64BitSparse = + new AddressTable.Level[] + { + new(23, 16), + new( 2, 21), + }; + + private static readonly AddressTable.Level[] _levels32BitSparse = + new AddressTable.Level[] + { + new(22, 10), + new( 1, 21), + }; + private readonly ConcurrentQueue> _oldFuncs; private readonly NoWxCache _noWxCache; private bool _disposed; @@ -62,8 +78,19 @@ namespace Ryujinx.Cpu.LightningJit JitCache.Initialize(new JitMemoryAllocator(forJit: true)); } + AddressTable.Level[] levels; + + if (UseSparseTable) + { + levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; + } + else + { + levels = for64Bits ? _levels64Bit : _levels32Bit; + } + Functions = new TranslatorCache(); - FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + FunctionTable = new AddressTable(levels); Stubs = new TranslatorStubs(FunctionTable, _noWxCache); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; From 66dc7a93b7fa689b1e321e28d22ea2ec7a450578 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 15 Jun 2024 23:50:59 +0100 Subject: [PATCH 03/13] Avoid a copy if possible --- .../Arm32/Target/Arm64/InstEmitFlow.cs | 31 ++++++++++++++----- .../Arm64/Target/Arm64/InstEmitSystem.cs | 31 ++++++++++++++----- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index 9d7581580..d72e039c8 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -140,7 +140,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 bool isTail = false) { int tempRegister; - int tempGuestAddress = 0; + int tempGuestAddress = -1; bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; @@ -157,15 +157,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); - if (inlineLookup) + if (inlineLookup && guestAddress.Value == 0) { - // Might be overwritten. Move the address to a temp register. + // 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) { @@ -190,8 +190,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { // Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. - Operand indexReg = Register(3); - guestAddress = Register(tempGuestAddress); + 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); @@ -225,7 +229,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 // Load the final branch address asm.LdrRiUn(rn, rn, 0); - regAlloc.FreeTempGprRegister(tempGuestAddress); + if (tempGuestAddress != -1) + { + regAlloc.FreeTempGprRegister(tempGuestAddress); + } } else { @@ -308,5 +315,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { return new Operand(type, (ulong)value); } + + private static int NextFreeRegister(int start, int avoid) + { + if (start == avoid) + { + start++; + } + + return start; + } } } diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index 342f5c698..c157d752e 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -305,7 +305,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 bool isTail = false) { int tempRegister; - int tempGuestAddress = 0; + int tempGuestAddress = -1; bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; @@ -322,15 +322,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 { asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); - if (inlineLookup) + if (inlineLookup && guestAddress.Value == 0) { - // Might be overwritten. Move the address to a temp register. + // 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) { @@ -355,8 +355,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 { // Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. - Operand indexReg = Register(3); - guestAddress = Register(tempGuestAddress); + 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); @@ -390,7 +394,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 // Load the final branch address asm.LdrRiUn(rn, rn, 0); - regAlloc.FreeTempGprRegister(tempGuestAddress); + if (tempGuestAddress != -1) + { + regAlloc.FreeTempGprRegister(tempGuestAddress); + } } else { @@ -669,5 +676,15 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 { return new Operand(type, (ulong)value); } + + private static int NextFreeRegister(int start, int avoid) + { + if (start == avoid) + { + start++; + } + + return start; + } } } From dcfd4af436dc3a9e4ce21b749e5cb84c744a9ceb Mon Sep 17 00:00:00 2001 From: riperiperi Date: Wed, 19 Jun 2024 20:11:48 +0100 Subject: [PATCH 04/13] Ptc update (temp) --- src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 569c28739..58ff9b145 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\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 = 26950; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; From 3763e2cc10b0bef5a3d27e31ff0ba8180bca74c2 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sun, 23 Jun 2024 18:59:45 +0100 Subject: [PATCH 05/13] WIP sparse stuff --- src/ARMeilleure/Common/AddressTable.cs | 205 +++++++++++++++--- .../Instructions/InstEmitFlowHelper.cs | 13 +- src/ARMeilleure/Translation/PTC/Ptc.cs | 7 +- src/ARMeilleure/Translation/Translator.cs | 8 +- .../Arm32/Target/Arm64/InstEmitFlow.cs | 9 - .../Arm64/Target/Arm64/InstEmitSystem.cs | 6 - src/Ryujinx.Cpu/LightningJit/Translator.cs | 8 +- src/Ryujinx.Memory/SparseMemoryBlock.cs | 120 ++++++++++ 8 files changed, 303 insertions(+), 73 deletions(-) create mode 100644 src/Ryujinx.Memory/SparseMemoryBlock.cs diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs index 5b6d48bbc..ebe5dfb01 100644 --- a/src/ARMeilleure/Common/AddressTable.cs +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -1,7 +1,10 @@ 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 { @@ -11,6 +14,12 @@ namespace ARMeilleure.Common /// 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 . /// @@ -53,12 +62,33 @@ namespace ARMeilleure.Common } } + 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 readonly TEntry* _fallbackTable; + 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. /// @@ -80,8 +110,7 @@ namespace ARMeilleure.Common } set { - *_fallbackTable = value; - _fill = value; + UpdateFill(value); } } @@ -102,26 +131,15 @@ namespace ARMeilleure.Common } } - /// - /// Gets a pointer to a single entry table containing only the leaf fill value. - /// - public IntPtr Fallback - { - get - { - ObjectDisposedException.ThrowIf(_disposed, this); - - return (IntPtr)_fallbackTable; - } - } - /// /// 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) + public AddressTable(Level[] levels, bool sparse) { ArgumentNullException.ThrowIfNull(levels); @@ -130,7 +148,7 @@ namespace ARMeilleure.Common throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); } - _pages = new List(capacity: 16); + _pages = new List(capacity: 16); Levels = levels; Mask = 0; @@ -140,7 +158,35 @@ namespace ARMeilleure.Common Mask |= level.Mask; } - _fallbackTable = (TEntry*)NativeAllocator.Instance.Allocate((ulong)sizeof(TEntry)); + _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; } /// @@ -172,7 +218,13 @@ namespace ARMeilleure.Common lock (_pages) { - return ref GetPage(address)[Levels[^1].GetValue(address)]; + TEntry* page = GetPage(address); + + int index = Levels[^1].GetValue(address); + + EnsureMapped((IntPtr)(page + index)); + + return ref page[index]; } } @@ -190,13 +242,18 @@ namespace ARMeilleure.Common ref Level level = ref Levels[i]; ref TEntry* nextPage = ref page[level.GetValue(address)]; - if (nextPage == null) + if (nextPage == null || nextPage == _fillBottomLevelPtr) { 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); + 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; @@ -205,6 +262,46 @@ namespace ARMeilleure.Common 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 . /// @@ -213,12 +310,17 @@ namespace ARMeilleure.Common { if (_table == null) { - _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + _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. /// @@ -230,16 +332,42 @@ namespace ARMeilleure.Common private IntPtr Allocate(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((void*)page, length); - span.Fill(fill); + 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; + return page.Address; } /// @@ -262,10 +390,23 @@ namespace ARMeilleure.Common { foreach (var page in _pages) { - Marshal.FreeHGlobal(page); + if (!page.IsSparse) + { + Marshal.FreeHGlobal(page.Address); + } } - Marshal.FreeHGlobal((IntPtr)_fallbackTable); + if (_sparse) + { + foreach (SparseMemoryBlock block in _sparseReserved) + { + block.Dispose(); + } + + _fillBottomLevel.Dispose(); + _sparseFill.Dispose(); + _sparseLock.Dispose(); + } _disposed = true; } diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index fbfdcefce..2a0355cc3 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -231,18 +231,7 @@ namespace ARMeilleure.Instructions Const(3) ); - // TODO: could possibly make a fallback page that level 1 is filled with that contains dispatch stub on all pages - // Would save this load and the comparisons - // 16MB of the same value is a bit wasteful so it could replicate with remapping. - - Operand fallback = !context.HasPtc ? - Const((long)context.FunctionTable.Fallback) : - Const((long)context.FunctionTable.Fallback, Ptc.DispatchFallbackSymbol); - - Operand pageIsZero = context.ICompareEqual(page, Const(0L)); - - // Small trick to keep this branchless - if the page is zero, load a fallback table entry that always contains the dispatch stub. - hostAddress = context.Load(OperandType.I64, context.ConditionalSelect(pageIsZero, fallback, context.Add(page, index2))); + hostAddress = context.Load(OperandType.I64, context.Add(page, index2)); } else { diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 58ff9b145..59ced5806 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 26950; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 26957; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; @@ -41,7 +41,6 @@ namespace ARMeilleure.Translation.PTC public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4); - public static readonly Symbol DispatchFallbackSymbol = new(SymbolType.Special, 5); private const byte FillingByte = 0x00; private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; @@ -711,10 +710,6 @@ namespace ARMeilleure.Translation.PTC { imm = translator.FunctionTable.Base; } - else if (symbol == DispatchFallbackSymbol) - { - imm = translator.FunctionTable.Fallback; - } if (imm == null) { diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index c3796cb99..9a3d7cec5 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -22,8 +22,6 @@ namespace ARMeilleure.Translation { public class Translator { - private const bool UseSparseTable = true; - private static readonly AddressTable.Level[] _levels64Bit = new AddressTable.Level[] { @@ -88,7 +86,9 @@ namespace ARMeilleure.Translation AddressTable.Level[] levels; - if (UseSparseTable) + bool useSparseTable = AddressTable.UseSparseTable; + + if (useSparseTable) { levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; } @@ -99,7 +99,7 @@ namespace ARMeilleure.Translation CountTable = new EntryTable(); Functions = new TranslatorCache(); - FunctionTable = new AddressTable(levels); + FunctionTable = new AddressTable(levels, useSparseTable); Stubs = new TranslatorStubs(FunctionTable); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index d72e039c8..f0b18fcbf 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -214,18 +214,9 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 asm.Ubfx(indexReg, guestAddress, level1.Index, level1.Length); asm.Lsl(indexReg, indexReg, Const(3)); - // Is the page address zero? Make sure to use the fallback if it is. - asm.Tst(rn, rn); - // Index into the page. asm.Add(rn, rn, indexReg); - // Reuse the index register for the fallback - ulong fallback = (ulong)funcTable.Fallback; - asm.Mov(indexReg, fallback); - - asm.Csel(rn, indexReg, rn, ArmCondition.Eq); - // Load the final branch address asm.LdrRiUn(rn, rn, 0); diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index c157d752e..dc8fc2c14 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -385,12 +385,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 // Index into the page. asm.Add(rn, rn, indexReg); - // Reuse the index register for the fallback - ulong fallback = (ulong)funcTable.Fallback; - asm.Mov(indexReg, fallback); - - asm.Csel(rn, indexReg, rn, ArmCondition.Eq); - // Load the final branch address asm.LdrRiUn(rn, rn, 0); diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index 8b1b875f4..19f883efa 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -16,8 +16,6 @@ namespace Ryujinx.Cpu.LightningJit { class Translator : IDisposable { - private const bool UseSparseTable = true; - // Should be enabled on platforms that enforce W^X. private static bool IsNoWxPlatform => false; @@ -78,9 +76,11 @@ namespace Ryujinx.Cpu.LightningJit JitCache.Initialize(new JitMemoryAllocator(forJit: true)); } + bool useSparseTable = AddressTable.UseSparseTable; + AddressTable.Level[] levels; - if (UseSparseTable) + if (useSparseTable) { levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; } @@ -90,7 +90,7 @@ namespace Ryujinx.Cpu.LightningJit } Functions = new TranslatorCache(); - FunctionTable = new AddressTable(levels); + FunctionTable = new AddressTable(levels, useSparseTable); Stubs = new TranslatorStubs(FunctionTable, _noWxCache); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; diff --git a/src/Ryujinx.Memory/SparseMemoryBlock.cs b/src/Ryujinx.Memory/SparseMemoryBlock.cs new file mode 100644 index 000000000..8c6dbea86 --- /dev/null +++ b/src/Ryujinx.Memory/SparseMemoryBlock.cs @@ -0,0 +1,120 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Memory +{ + public delegate void PageInitDelegate(Span 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 _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(); + _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 | MemoryAllocationFlags.NoMap); + + _mappedBlocks.Add(block); + + _mappedBlockUsage = 0; + } + + _reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize); + _pageInit(_reservedBlock.GetSpan(pageOffset, (int)_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 ((entry & bit) == 0) + { + // Not mapped. + + lock (_lock) + { + // Check the bit while locked to make sure that this only happens once. + + if ((entry & bit) == 0) + { + MapPage(offset & ~(_pageSize - 1)); + + entry |= bit; + } + } + } + } + + public void Dispose() + { + _reservedBlock.Dispose(); + + foreach (MemoryBlock block in _mappedBlocks) + { + block.Dispose(); + } + + GC.SuppressFinalize(this); + } + } +} From 4dbf7c0eb9bc2ad38dfd8a30991797ab1f077b50 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 18:53:18 +0100 Subject: [PATCH 06/13] Implement sparse table --- src/ARMeilleure/Common/AddressTableLevel.cs | 44 ++++ src/ARMeilleure/Common/AddressTablePresets.cs | 51 +++++ src/ARMeilleure/Common/Allocator.cs | 2 +- src/ARMeilleure/Common/IAddressTable.cs | 51 +++++ src/ARMeilleure/Common/NativeAllocator.cs | 2 +- .../Signal/NativeSignalHandlerGenerator.cs | 2 +- .../Translation/ArmEmitterContext.cs | 4 +- src/ARMeilleure/Translation/Translator.cs | 53 +---- .../Translation/TranslatorStubs.cs | 4 +- .../Common => Ryujinx.Cpu}/AddressTable.cs | 196 ++++++++++-------- src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 6 +- .../LightningJit/LightningJitCpuContext.cs | 7 +- src/Ryujinx.Cpu/LightningJit/Translator.cs | 50 +---- src/Ryujinx.Memory/SparseMemoryBlock.cs | 15 +- 14 files changed, 292 insertions(+), 195 deletions(-) create mode 100644 src/ARMeilleure/Common/AddressTableLevel.cs create mode 100644 src/ARMeilleure/Common/AddressTablePresets.cs create mode 100644 src/ARMeilleure/Common/IAddressTable.cs rename src/{ARMeilleure/Common => Ryujinx.Cpu}/AddressTable.cs (72%) diff --git a/src/ARMeilleure/Common/AddressTableLevel.cs b/src/ARMeilleure/Common/AddressTableLevel.cs new file mode 100644 index 000000000..6107726ee --- /dev/null +++ b/src/ARMeilleure/Common/AddressTableLevel.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Common +{ + /// + /// Represents a level in an . + /// + public readonly struct AddressTableLevel + { + /// + /// 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 AddressTableLevel(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); + } + } +} diff --git a/src/ARMeilleure/Common/AddressTablePresets.cs b/src/ARMeilleure/Common/AddressTablePresets.cs new file mode 100644 index 000000000..e7eaf62cd --- /dev/null +++ b/src/ARMeilleure/Common/AddressTablePresets.cs @@ -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; + } + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs index 6905a614f..de6a77ebe 100644 --- a/src/ARMeilleure/Common/Allocator.cs +++ b/src/ARMeilleure/Common/Allocator.cs @@ -2,7 +2,7 @@ using System; namespace ARMeilleure.Common { - unsafe abstract class Allocator : IDisposable + public unsafe abstract class Allocator : IDisposable { public T* Allocate(ulong count = 1) where T : unmanaged { diff --git a/src/ARMeilleure/Common/IAddressTable.cs b/src/ARMeilleure/Common/IAddressTable.cs new file mode 100644 index 000000000..4924b448b --- /dev/null +++ b/src/ARMeilleure/Common/IAddressTable.cs @@ -0,0 +1,51 @@ +using System; + +namespace ARMeilleure.Common +{ + public interface IAddressTable : 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. + /// + static bool UseSparseTable { get; } + + /// + /// Gets the bits used by the of the instance. + /// + ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + AddressTableLevel[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + IntPtr Base { get; } + + /// + /// Determines if the specified is in the range of the + /// . + /// + /// Guest address + /// if is valid; otherwise + bool IsValid(ulong address); + + /// + /// 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 + ref TEntry GetValue(ulong address); + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs index 93c48adda..102e33ebe 100644 --- a/src/ARMeilleure/Common/NativeAllocator.cs +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; namespace ARMeilleure.Common { - unsafe sealed class NativeAllocator : Allocator + public unsafe sealed class NativeAllocator : Allocator { public static NativeAllocator Instance { get; } = new(); diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs index 2ec5bc1b3..896d372d1 100644 --- a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -8,7 +8,7 @@ namespace ARMeilleure.Signal { public static class NativeSignalHandlerGenerator { - public const int MaxTrackedRanges = 8; + public const int MaxTrackedRanges = 16; private const int StructAddressOffset = 0; private const int StructWriteOffset = 4; diff --git a/src/ARMeilleure/Translation/ArmEmitterContext.cs b/src/ARMeilleure/Translation/ArmEmitterContext.cs index e24074739..54cd97d53 100644 --- a/src/ARMeilleure/Translation/ArmEmitterContext.cs +++ b/src/ARMeilleure/Translation/ArmEmitterContext.cs @@ -46,7 +46,7 @@ namespace ARMeilleure.Translation public IMemoryManager Memory { get; } public EntryTable CountTable { get; } - public AddressTable FunctionTable { get; } + public IAddressTable FunctionTable { get; } public TranslatorStubs Stubs { get; } public ulong EntryAddress { get; } @@ -62,7 +62,7 @@ namespace ARMeilleure.Translation public ArmEmitterContext( IMemoryManager memory, EntryTable countTable, - AddressTable funcTable, + IAddressTable funcTable, TranslatorStubs stubs, ulong entryAddress, bool highCq, diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 9a3d7cec5..45758059c 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -22,47 +22,13 @@ namespace ARMeilleure.Translation { public class Translator { - private static readonly AddressTable.Level[] _levels64Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 2, 5), - }; - - private static readonly AddressTable.Level[] _levels32Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 1, 6), - }; - - private static readonly AddressTable.Level[] _levels64BitSparse = - new AddressTable.Level[] - { - new(23, 16), - new( 2, 21), - }; - - private static readonly AddressTable.Level[] _levels32BitSparse = - new AddressTable.Level[] - { - new(22, 10), - new( 1, 21), - }; - private readonly IJitMemoryAllocator _allocator; private readonly ConcurrentQueue> _oldFuncs; private readonly Ptc _ptc; internal TranslatorCache Functions { get; } - internal AddressTable FunctionTable { get; } + internal IAddressTable FunctionTable { get; } internal EntryTable CountTable { get; } internal TranslatorStubs Stubs { get; } internal TranslatorQueue Queue { get; } @@ -71,7 +37,7 @@ namespace ARMeilleure.Translation private Thread[] _backgroundTranslationThreads; private volatile int _threadCount; - public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) + public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable functionTable) { _allocator = allocator; Memory = memory; @@ -84,22 +50,9 @@ namespace ARMeilleure.Translation JitCache.Initialize(allocator); - AddressTable.Level[] levels; - - bool useSparseTable = AddressTable.UseSparseTable; - - if (useSparseTable) - { - levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; - } - else - { - levels = for64Bits ? _levels64Bit : _levels32Bit; - } - CountTable = new EntryTable(); Functions = new TranslatorCache(); - FunctionTable = new AddressTable(levels, useSparseTable); + FunctionTable = functionTable; Stubs = new TranslatorStubs(FunctionTable); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs index d80823a8b..379caa283 100644 --- a/src/ARMeilleure/Translation/TranslatorStubs.cs +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -19,7 +19,7 @@ namespace ARMeilleure.Translation private bool _disposed; - private readonly AddressTable _functionTable; + private readonly IAddressTable _functionTable; private readonly Lazy _dispatchStub; private readonly Lazy _dispatchLoop; private readonly Lazy _contextWrapper; @@ -86,7 +86,7 @@ namespace ARMeilleure.Translation /// /// Function table used to store pointers to the functions that the guest code will call /// is null - public TranslatorStubs(AddressTable functionTable) + public TranslatorStubs(IAddressTable functionTable) { ArgumentNullException.ThrowIfNull(functionTable); diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs similarity index 72% rename from src/ARMeilleure/Common/AddressTable.cs rename to src/Ryujinx.Cpu/AddressTable.cs index ebe5dfb01..e8a0c423b 100644 --- a/src/ARMeilleure/Common/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -1,10 +1,12 @@ -using ARMeilleure.Diagnostics; +using Ryujinx.Common.Logging; +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 { @@ -12,7 +14,7 @@ namespace ARMeilleure.Common /// Represents a table of guest address to a value. /// /// Type of the value - public unsafe class AddressTable : IDisposable where TEntry : unmanaged + public unsafe class AddressTable : IAddressTable where TEntry : unmanaged { /// /// If true, the sparse 2-level table should be used to improve performance. @@ -20,48 +22,6 @@ namespace ARMeilleure.Common /// 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; @@ -74,6 +34,50 @@ namespace ARMeilleure.Common } } + /// + /// 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; + public readonly TrackingEventDelegate TrackingEvent; + + public TableSparseBlock(ulong size, Action ensureMapped, PageInitDelegate pageInit) + { + var block = new SparseMemoryBlock(size, pageInit, null); + + TrackingEvent = (ulong address, ulong size, bool write) => + { + Logger.Error?.PrintMsg(LogClass.Cpu, $"Triggered from exception"); + + 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 _pages; @@ -84,24 +88,19 @@ namespace ARMeilleure.Common private readonly SparseMemoryBlock _fillBottomLevel; private readonly TEntry* _fillBottomLevelPtr; - private readonly List _sparseReserved; + 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; } + /// + public AddressTableLevel[] Levels { get; } - /// - /// Gets or sets the default fill value of newly created leaf pages. - /// + /// public TEntry Fill { get @@ -114,10 +113,7 @@ namespace ARMeilleure.Common } } - /// - /// Gets the base address of the . - /// - /// instance was disposed + /// public IntPtr Base { get @@ -139,7 +135,7 @@ namespace ARMeilleure.Common /// True if the bottom page should be sparsely mapped /// is null /// Length of is less than 2 - public AddressTable(Level[] levels, bool sparse) + public AddressTable(AddressTableLevel[] levels, bool sparse) { ArgumentNullException.ThrowIfNull(levels); @@ -171,13 +167,30 @@ namespace ARMeilleure.Common _fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill); _fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer; - _sparseReserved = new List(); + _sparseReserved = new List(); _sparseLock = new ReaderWriterLockSlim(); _sparseBlockSize = bottomLevelSize << 3; } } + /// + /// Create an instance for an ARM function table. + /// Selects the best table structure for A32/A64, taking into account whether sparse mapping is supported. + /// + /// True if the guest is A64, false otherwise + /// An for ARM function lookup + public static AddressTable CreateForArm(bool for64Bits) + { + bool sparse = UseSparseTable; + + return new AddressTable(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse); + } + + /// + /// Update the fill value for the bottom level of the table. + /// + /// New fill value private void UpdateFill(TEntry fillValue) { if (_sparseFill != null) @@ -189,24 +202,13 @@ namespace ARMeilleure.Common _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); @@ -239,12 +241,12 @@ namespace ARMeilleure.Common for (int i = 0; i < Levels.Length - 1; i++) { - ref Level level = ref Levels[i]; + ref AddressTableLevel level = ref Levels[i]; ref TEntry* nextPage = ref page[level.GetValue(address)]; if (nextPage == null || nextPage == _fillBottomLevelPtr) { - ref Level nextLevel = ref Levels[i + 1]; + ref AddressTableLevel nextLevel = ref Levels[i + 1]; if (i == Levels.Length - 2) { @@ -273,8 +275,10 @@ namespace ARMeilleure.Common try { - foreach (SparseMemoryBlock sparse in _sparseReserved) + 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)); @@ -290,6 +294,11 @@ namespace ARMeilleure.Common } } + /// + /// Get the fill value for a non-leaf level of the table. + /// + /// Level to get the fill value for + /// The fill value private IntPtr GetFillValue(int level) { if (_fillBottomLevel != null && level == Levels.Length - 2) @@ -316,9 +325,28 @@ namespace ARMeilleure.Common return _table; } + private int initedSize = 0; + private int reservedSize = 0; + + /// + /// Initialize a leaf page with the fill value. + /// + /// Page to initialize private void InitLeafPage(Span page) { MemoryMarshal.Cast(page).Fill(_fill); + + initedSize += page.Length; + + Ryujinx.Common.Logging.Logger.Info?.PrintMsg(LogClass.Cpu, $"Using memory {initedSize}/{reservedSize} bytes"); + } + + private void ReserveNewSparseBlock() + { + var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage); + + _sparseReserved.Add(block); + _sparseReservedOffset = 0; } /// @@ -333,6 +361,8 @@ namespace ARMeilleure.Common { var size = sizeof(T) * length; + reservedSize += size; + AddressTablePage page; if (_sparse && leaf) @@ -341,12 +371,10 @@ namespace ARMeilleure.Common if (_sparseReserved.Count == 0 || _sparseReservedOffset == _sparseBlockSize) { - _sparseReserved.Add(new SparseMemoryBlock(_sparseBlockSize, InitLeafPage, _sparseFill)); - - _sparseReservedOffset = 0; + ReserveNewSparseBlock(); } - SparseMemoryBlock block = _sparseReserved.Last(); + SparseMemoryBlock block = _sparseReserved.Last().Block; page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset); @@ -365,7 +393,7 @@ namespace ARMeilleure.Common _pages.Add(page); - TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + //TranslatorEventSource.Log.AddressTableAllocated(size, leaf); return page.Address; } @@ -398,11 +426,13 @@ namespace ARMeilleure.Common if (_sparse) { - foreach (SparseMemoryBlock block in _sparseReserved) + foreach (TableSparseBlock block in _sparseReserved) { block.Dispose(); } + _sparseReserved.Clear(); + _fillBottomLevel.Dispose(); _sparseFill.Dispose(); _sparseLock.Dispose(); diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index 9893c59b2..2b70b0239 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using ARMeilleure.Translation; using Ryujinx.Cpu.Signal; @@ -9,11 +10,14 @@ namespace Ryujinx.Cpu.Jit { private readonly ITickSource _tickSource; private readonly Translator _translator; + private readonly AddressTable _functionTable; public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); + _functionTable = AddressTable.CreateForArm(for64Bit); + + _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable); if (memory.Type.IsHostMappedOrTracked()) { diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs index b63636e39..2d4ca0fca 100644 --- a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.LightningJit.State; @@ -8,11 +9,15 @@ namespace Ryujinx.Cpu.LightningJit { private readonly ITickSource _tickSource; private readonly Translator _translator; + private readonly AddressTable _functionTable; public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _translator = new Translator(memory, for64Bit); + _functionTable = AddressTable.CreateForArm(for64Bit); + + _translator = new Translator(memory, _functionTable); + memory.UnmapEvent += UnmapHandler; } diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs index 19f883efa..bb12ac5fd 100644 --- a/src/Ryujinx.Cpu/LightningJit/Translator.cs +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -19,39 +19,6 @@ namespace Ryujinx.Cpu.LightningJit // Should be enabled on platforms that enforce W^X. private static bool IsNoWxPlatform => false; - private static readonly AddressTable.Level[] _levels64Bit = - new AddressTable.Level[] - { - new(31, 17), - new(23, 8), - new(15, 8), - new( 7, 8), - new( 2, 5), - }; - - private static readonly AddressTable.Level[] _levels32Bit = - new AddressTable.Level[] - { - new(23, 9), - new(15, 8), - new( 7, 8), - new( 1, 6), - }; - - private static readonly AddressTable.Level[] _levels64BitSparse = - new AddressTable.Level[] - { - new(23, 16), - new( 2, 21), - }; - - private static readonly AddressTable.Level[] _levels32BitSparse = - new AddressTable.Level[] - { - new(22, 10), - new( 1, 21), - }; - private readonly ConcurrentQueue> _oldFuncs; private readonly NoWxCache _noWxCache; private bool _disposed; @@ -61,7 +28,7 @@ namespace Ryujinx.Cpu.LightningJit internal TranslatorStubs Stubs { get; } internal IMemoryManager Memory { get; } - public Translator(IMemoryManager memory, bool for64Bits) + public Translator(IMemoryManager memory, AddressTable functionTable) { Memory = memory; @@ -76,21 +43,8 @@ namespace Ryujinx.Cpu.LightningJit JitCache.Initialize(new JitMemoryAllocator(forJit: true)); } - bool useSparseTable = AddressTable.UseSparseTable; - - AddressTable.Level[] levels; - - if (useSparseTable) - { - levels = for64Bits ? _levels64BitSparse : _levels32BitSparse; - } - else - { - levels = for64Bits ? _levels64Bit : _levels32Bit; - } - Functions = new TranslatorCache(); - FunctionTable = new AddressTable(levels, useSparseTable); + FunctionTable = functionTable; Stubs = new TranslatorStubs(FunctionTable, _noWxCache); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; diff --git a/src/Ryujinx.Memory/SparseMemoryBlock.cs b/src/Ryujinx.Memory/SparseMemoryBlock.cs index 8c6dbea86..523685de1 100644 --- a/src/Ryujinx.Memory/SparseMemoryBlock.cs +++ b/src/Ryujinx.Memory/SparseMemoryBlock.cs @@ -2,6 +2,7 @@ using Ryujinx.Common; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Ryujinx.Memory { @@ -66,15 +67,15 @@ namespace Ryujinx.Memory { // Need to map some more memory. - block = new MemoryBlock(MapGranularity, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap); + block = new MemoryBlock(MapGranularity, MemoryAllocationFlags.Mirrorable); _mappedBlocks.Add(block); _mappedBlockUsage = 0; } + _pageInit(block.GetSpan(_mappedBlockUsage, (int)_pageSize)); _reservedBlock.MapView(block, _mappedBlockUsage, pageOffset, _pageSize); - _pageInit(_reservedBlock.GetSpan(pageOffset, (int)_pageSize)); _mappedBlockUsage += _pageSize; } @@ -87,7 +88,7 @@ namespace Ryujinx.Memory ref ulong entry = ref _mappedPageBitmap[bitmapIndex]; ulong bit = 1UL << (pageIndex & 63); - if ((entry & bit) == 0) + if ((Volatile.Read(ref entry) & bit) == 0) { // Not mapped. @@ -95,11 +96,15 @@ namespace Ryujinx.Memory { // Check the bit while locked to make sure that this only happens once. - if ((entry & bit) == 0) + ulong lockedEntry = Volatile.Read(ref entry); + + if ((lockedEntry & bit) == 0) { MapPage(offset & ~(_pageSize - 1)); - entry |= bit; + lockedEntry |= bit; + + Interlocked.Exchange(ref entry, lockedEntry); } } } From 66e6e29b3ba2d773fc4f14bb6df8fdec7331219e Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 19:43:22 +0100 Subject: [PATCH 07/13] Automatically size sparse blocks to minimize tracking ranges --- src/Ryujinx.Cpu/AddressTable.cs | 45 +++++++++++++++---- src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 1 + .../LightningJit/LightningJitCpuContext.cs | 1 + 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs index e8a0c423b..306a6f4d7 100644 --- a/src/Ryujinx.Cpu/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; @@ -89,9 +90,9 @@ namespace ARMeilleure.Common private readonly TEntry* _fillBottomLevelPtr; private readonly List _sparseReserved; - private readonly ulong _sparseBlockSize; private readonly ReaderWriterLockSlim _sparseLock; + private ulong _sparseBlockSize; private ulong _sparseReservedOffset; /// @@ -170,7 +171,7 @@ namespace ARMeilleure.Common _sparseReserved = new List(); _sparseLock = new ReaderWriterLockSlim(); - _sparseBlockSize = bottomLevelSize << 3; + _sparseBlockSize = bottomLevelSize; } } @@ -202,6 +203,23 @@ namespace ARMeilleure.Common _fill = fillValue; } + /// + /// Signal that the given code range exists. + /// + /// + /// + 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)); + } + /// public bool IsValid(ulong address) { @@ -341,12 +359,14 @@ namespace ARMeilleure.Common Ryujinx.Common.Logging.Logger.Info?.PrintMsg(LogClass.Cpu, $"Using memory {initedSize}/{reservedSize} bytes"); } - private void ReserveNewSparseBlock() + private TableSparseBlock ReserveNewSparseBlock() { var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage); _sparseReserved.Add(block); _sparseReservedOffset = 0; + + return block; } /// @@ -369,12 +389,21 @@ namespace ARMeilleure.Common { _sparseLock.EnterWriteLock(); - if (_sparseReserved.Count == 0 || _sparseReservedOffset == _sparseBlockSize) - { - ReserveNewSparseBlock(); - } + SparseMemoryBlock block; - SparseMemoryBlock block = _sparseReserved.Last().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); diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index 2b70b0239..5a9c9dcfe 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -59,6 +59,7 @@ namespace Ryujinx.Cpu.Jit /// public void PrepareCodeRange(ulong address, ulong size) { + _functionTable.SignalCodeRange(address, size); _translator.PrepareCodeRange(address, size); } diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs index 2d4ca0fca..b0b5ad72f 100644 --- a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs @@ -53,6 +53,7 @@ namespace Ryujinx.Cpu.LightningJit /// public void PrepareCodeRange(ulong address, ulong size) { + _functionTable.SignalCodeRange(address, size); } public void Dispose() From 1490f3199bff8aaa84653a505437a8380bc108c1 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 20:37:58 +0100 Subject: [PATCH 08/13] Use sparse memory when memory isn't software. --- src/ARMeilleure/Common/IAddressTable.cs | 6 ------ src/Ryujinx.Cpu/AddressTable.cs | 15 ++++++--------- src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 2 +- .../LightningJit/LightningJitCpuContext.cs | 2 +- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/ARMeilleure/Common/IAddressTable.cs b/src/ARMeilleure/Common/IAddressTable.cs index 4924b448b..5be934163 100644 --- a/src/ARMeilleure/Common/IAddressTable.cs +++ b/src/ARMeilleure/Common/IAddressTable.cs @@ -4,12 +4,6 @@ namespace ARMeilleure.Common { public interface IAddressTable : 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. - /// - static bool UseSparseTable { get; } - /// /// Gets the bits used by the of the instance. /// diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs index 306a6f4d7..d2f03c801 100644 --- a/src/Ryujinx.Cpu/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Memory; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Cpu.Signal; @@ -17,12 +18,6 @@ namespace ARMeilleure.Common /// Type of the value public unsafe class AddressTable : IAddressTable 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; - private readonly struct AddressTablePage { public readonly bool IsSparse; @@ -177,13 +172,15 @@ namespace ARMeilleure.Common /// /// Create an instance for an ARM function table. - /// Selects the best table structure for A32/A64, taking into account whether sparse mapping is supported. + /// Selects the best table structure for A32/A64, taking into account the selected memory manager type. /// /// True if the guest is A64, false otherwise + /// Memory manager type /// An for ARM function lookup - public static AddressTable CreateForArm(bool for64Bits) + public static AddressTable CreateForArm(bool for64Bits, MemoryManagerType type) { - bool sparse = UseSparseTable; + // 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(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse); } diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs index 5a9c9dcfe..bd512a758 100644 --- a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.Jit public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _functionTable = AddressTable.CreateForArm(for64Bit); + _functionTable = AddressTable.CreateForArm(for64Bit, memory.Type); _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable); diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs index b0b5ad72f..c39e0e67e 100644 --- a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.LightningJit public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) { _tickSource = tickSource; - _functionTable = AddressTable.CreateForArm(for64Bit); + _functionTable = AddressTable.CreateForArm(for64Bit, memory.Type); _translator = new Translator(memory, _functionTable); From a3494a75f70accceea72523e0236bf141e9ef15c Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 21:18:01 +0100 Subject: [PATCH 09/13] Some cleanup --- src/ARMeilleure/Common/IAddressTable.cs | 12 +++++-- .../Instructions/InstEmitFlowHelper.cs | 2 +- src/Ryujinx.Cpu/AddressTable.cs | 35 ++++++++++++++----- .../Arm32/Target/Arm64/InstEmitFlow.cs | 2 +- .../Arm64/Target/Arm64/InstEmitSystem.cs | 2 +- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/ARMeilleure/Common/IAddressTable.cs b/src/ARMeilleure/Common/IAddressTable.cs index 5be934163..116ccdaad 100644 --- a/src/ARMeilleure/Common/IAddressTable.cs +++ b/src/ARMeilleure/Common/IAddressTable.cs @@ -5,12 +5,18 @@ namespace ARMeilleure.Common public interface IAddressTable : IDisposable where TEntry : unmanaged { /// - /// Gets the bits used by the of the instance. + /// 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. + /// + bool Sparse { get; } + + /// + /// Gets the bits used by the of the instance. /// ulong Mask { get; } /// - /// Gets the s used by the instance. + /// Gets the s used by the instance. /// AddressTableLevel[] Levels { get; } @@ -27,7 +33,7 @@ namespace ARMeilleure.Common /// /// Determines if the specified is in the range of the - /// . + /// . /// /// Guest address /// if is valid; otherwise diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index 2a0355cc3..12f1dba18 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -205,7 +205,7 @@ namespace ARMeilleure.Instructions hostAddress = context.Load(OperandType.I64, hostAddressAddr); } - else if (table.Levels.Length == 2) + 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. diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs index d2f03c801..ee53e925d 100644 --- a/src/Ryujinx.Cpu/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -18,9 +18,19 @@ namespace ARMeilleure.Common /// Type of the value public unsafe class AddressTable : IAddressTable where TEntry : unmanaged { + /// + /// Represents a page of the address table. + /// private readonly struct AddressTablePage { + /// + /// True if the allocation belongs to a sparse block, false otherwise. + /// public readonly bool IsSparse; + + /// + /// Base address for the page. + /// public readonly IntPtr Address; public AddressTablePage(bool isSparse, IntPtr address) @@ -36,13 +46,13 @@ namespace ARMeilleure.Common private readonly struct TableSparseBlock : IDisposable { public readonly SparseMemoryBlock Block; - public readonly TrackingEventDelegate TrackingEvent; + private readonly TrackingEventDelegate _trackingEvent; public TableSparseBlock(ulong size, Action ensureMapped, PageInitDelegate pageInit) { var block = new SparseMemoryBlock(size, pageInit, null); - TrackingEvent = (ulong address, ulong size, bool write) => + _trackingEvent = (ulong address, ulong size, bool write) => { Logger.Error?.PrintMsg(LogClass.Cpu, $"Triggered from exception"); @@ -56,7 +66,7 @@ namespace ARMeilleure.Common bool added = NativeSignalHandler.AddTrackedRegion( (nuint)block.Block.Pointer, (nuint)(block.Block.Pointer + (IntPtr)block.Block.Size), - Marshal.GetFunctionPointerForDelegate(TrackingEvent)); + Marshal.GetFunctionPointerForDelegate(_trackingEvent)); if (!added) { @@ -79,7 +89,6 @@ namespace ARMeilleure.Common private readonly List _pages; private TEntry _fill; - private readonly bool _sparse; private readonly MemoryBlock _sparseFill; private readonly SparseMemoryBlock _fillBottomLevel; private readonly TEntry* _fillBottomLevelPtr; @@ -90,6 +99,8 @@ namespace ARMeilleure.Common private ulong _sparseBlockSize; private ulong _sparseReservedOffset; + public bool Sparse { get; } + /// public ulong Mask { get; } @@ -150,7 +161,7 @@ namespace ARMeilleure.Common Mask |= level.Mask; } - _sparse = sparse; + Sparse = sparse; if (sparse) { @@ -279,9 +290,13 @@ namespace ARMeilleure.Common return (TEntry*)page; } + /// + /// Ensure the given pointer is mapped in any overlapping sparse reservations. + /// + /// Pointer to be mapped private void EnsureMapped(IntPtr ptr) { - if (_sparse) + 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. @@ -356,6 +371,10 @@ namespace ARMeilleure.Common Ryujinx.Common.Logging.Logger.Info?.PrintMsg(LogClass.Cpu, $"Using memory {initedSize}/{reservedSize} bytes"); } + /// + /// Reserve a new sparse block, and add it to the list. + /// + /// The new sparse block that was added private TableSparseBlock ReserveNewSparseBlock() { var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage); @@ -382,7 +401,7 @@ namespace ARMeilleure.Common AddressTablePage page; - if (_sparse && leaf) + if (Sparse && leaf) { _sparseLock.EnterWriteLock(); @@ -450,7 +469,7 @@ namespace ARMeilleure.Common } } - if (_sparse) + if (Sparse) { foreach (TableSparseBlock block in _sparseReserved) { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs index f0b18fcbf..b2192f3d4 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -142,7 +142,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 int tempRegister; int tempGuestAddress = -1; - bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Sparse && funcTable.Levels.Length == 2; if (guestAddress.Kind == OperandKind.Constant) { diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index dc8fc2c14..8fa95b8a5 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -307,7 +307,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 int tempRegister; int tempGuestAddress = -1; - bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Levels.Length == 2; + bool inlineLookup = guestAddress.Kind != OperandKind.Constant && funcTable != null && funcTable.Sparse && funcTable.Levels.Length == 2; if (guestAddress.Kind == OperandKind.Constant) { From 8454a2243856c7287854d7c8d034d1f1cee67791 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 21:19:11 +0100 Subject: [PATCH 10/13] Remove test logs --- src/Ryujinx.Cpu/AddressTable.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Ryujinx.Cpu/AddressTable.cs b/src/Ryujinx.Cpu/AddressTable.cs index ee53e925d..828e01597 100644 --- a/src/Ryujinx.Cpu/AddressTable.cs +++ b/src/Ryujinx.Cpu/AddressTable.cs @@ -1,6 +1,5 @@ using ARMeilleure.Memory; using Ryujinx.Common; -using Ryujinx.Common.Logging; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using System; @@ -54,8 +53,6 @@ namespace ARMeilleure.Common _trackingEvent = (ulong address, ulong size, bool write) => { - Logger.Error?.PrintMsg(LogClass.Cpu, $"Triggered from exception"); - ulong pointer = (ulong)block.Block.Pointer + address; ensureMapped((IntPtr)pointer); @@ -355,9 +352,6 @@ namespace ARMeilleure.Common return _table; } - private int initedSize = 0; - private int reservedSize = 0; - /// /// Initialize a leaf page with the fill value. /// @@ -365,10 +359,6 @@ namespace ARMeilleure.Common private void InitLeafPage(Span page) { MemoryMarshal.Cast(page).Fill(_fill); - - initedSize += page.Length; - - Ryujinx.Common.Logging.Logger.Info?.PrintMsg(LogClass.Cpu, $"Using memory {initedSize}/{reservedSize} bytes"); } /// @@ -397,8 +387,6 @@ namespace ARMeilleure.Common { var size = sizeof(T) * length; - reservedSize += size; - AddressTablePage page; if (Sparse && leaf) From dad35360bdd4aaaac5114fccdeff2914eb662ac2 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 22:26:07 +0100 Subject: [PATCH 11/13] Add bounds to address table on ARMeilleure --- src/ARMeilleure/Instructions/InstEmitFlowHelper.cs | 12 +++++++----- src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index 12f1dba18..ab8e1e3bd 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -211,9 +211,12 @@ namespace ARMeilleure.Instructions // Deliberately attempts to avoid branches. var level0 = table.Levels[0]; + int clearBits0 = 64 - (level0.Index + level0.Length); - // Currently no bounds check. Maybe conditionally do this for unsafe host mapped. - Operand index = context.ShiftLeft(context.ShiftRightUI(guestAddress, Const(level0.Index)), Const(3)); + Operand index = context.ShiftLeft( + context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits0)), Const(clearBits0 + level0.Index)), + Const(3) + ); Operand tableBase = !context.HasPtc ? Const(table.Base) : @@ -223,11 +226,10 @@ namespace ARMeilleure.Instructions // Second level var level1 = table.Levels[1]; - - int clearBits = 64 - (level1.Index + level1.Length); + int clearBits1 = 64 - (level1.Index + level1.Length); Operand index2 = context.ShiftLeft( - context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level1.Index)), + context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits1)), Const(clearBits1 + level1.Index)), Const(3) ); diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 59ced5806..135ae73be 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 26957; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 26958; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; From f950812b9fecc1d482a93599e45bde6433294131 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 22:56:59 +0100 Subject: [PATCH 12/13] PTC version, remove unnecessary instruction --- src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- .../LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index 135ae73be..559a629c2 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 26958; //! 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 BackupDir = "1"; diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs index 8fa95b8a5..920f61c10 100644 --- a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -379,9 +379,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 asm.Ubfx(indexReg, guestAddress, level1.Index, level1.Length); asm.Lsl(indexReg, indexReg, Const(3)); - // Is the page address zero? Make sure to use the fallback if it is. - asm.Tst(rn, rn); - // Index into the page. asm.Add(rn, rn, indexReg); From fa5de200b3865c16369c52a1fa56278a73fb1234 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 24 Jun 2024 23:11:56 +0100 Subject: [PATCH 13/13] Fix tests --- src/Ryujinx.Tests/Cpu/CpuContext.cs | 3 ++- src/Ryujinx.Tests/Cpu/EnvironmentTests.cs | 6 +++++- src/Ryujinx.Tests/Memory/PartialUnmaps.cs | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Tests/Cpu/CpuContext.cs b/src/Ryujinx.Tests/Cpu/CpuContext.cs index 96b4965a2..81e8ba8c9 100644 --- a/src/Ryujinx.Tests/Cpu/CpuContext.cs +++ b/src/Ryujinx.Tests/Cpu/CpuContext.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Memory; using ARMeilleure.State; using ARMeilleure.Translation; @@ -12,7 +13,7 @@ namespace Ryujinx.Tests.Cpu public CpuContext(IMemoryManager memory, bool for64Bit) { - _translator = new Translator(new JitMemoryAllocator(), memory, for64Bit); + _translator = new Translator(new JitMemoryAllocator(), memory, AddressTable.CreateForArm(for64Bit, memory.Type)); memory.UnmapEvent += UnmapHandler; } diff --git a/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs index 2a4775a31..2a8a98179 100644 --- a/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs +++ b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Translation; using NUnit.Framework; using Ryujinx.Cpu.Jit; @@ -17,7 +18,10 @@ namespace Ryujinx.Tests.Cpu private static void EnsureTranslator() { // 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.CreateForArm(true, ARMeilleure.Memory.MemoryManagerType.SoftwarePageTable)); } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] diff --git a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs index ace68e5c2..6cdfb662c 100644 --- a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs +++ b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Common; using ARMeilleure.Signal; using ARMeilleure.Translation; using NUnit.Framework; @@ -53,7 +54,10 @@ namespace Ryujinx.Tests.Memory private static void EnsureTranslator() { // 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.CreateForArm(true, ARMeilleure.Memory.MemoryManagerType.SoftwarePageTable)); } [Test]