From 9d7627af6484e090ebbc3209bc7301f0bdf47d24 Mon Sep 17 00:00:00 2001 From: FICTURE7 Date: Sun, 30 May 2021 01:06:28 +0400 Subject: [PATCH] Add multi-level function table (#2228) * Add AddressTable * Use AddressTable for dispatch * Remove JumpTable & co. * Add fallback for out of range addresses * Add PPTC support * Add documentation to `AddressTable` * Make AddressTable configurable * Fix table walk * Fix IsMapped check * Remove CountTableCapacity * Add PPTC support for fast path * Rename IsMapped to IsValid * Remove stale comment * Change format of address in exception message * Add TranslatorStubs * Split DispatchStub Avoids recompilation of stubs during tests. * Add hint for 64bit or 32bit * Add documentation to `Symbol` * Add documentation to `TranslatorStubs` Make `TranslatorStubs` disposable as well. * Add documentation to `SymbolType` * Add `AddressTableEventSource` to monitor function table size Add an EventSource which measures the amount of unmanaged bytes allocated by AddressTable instances. dotnet-counters monitor -n Ryujinx --counters ARMeilleure * Add `AllowLcqInFunctionTable` optimization toggle This is to reduce the impact this change has on the test duration. Before everytime a test was ran, the FunctionTable would be initialized and populated so that the newly compiled test would get registered to it. * Implement unmanaged dispatcher Uses the DispatchStub to dispatch into the next translation, which allows execution to stay in unmanaged for longer and skips a ConcurrentDictionary look up when the target translation has been registered to the FunctionTable. * Remove redundant null check * Tune levels of FunctionTable Uses 5 levels instead of 4 and change unit of AddressTableEventSource from KB to MB. * Use 64-bit function table Improves codegen for direct branches: mov qword [rax+0x408],0x10603560 - mov rcx,sub_10603560_OFFSET - mov ecx,[rcx] - mov ecx,ecx - mov rdx,JIT_CACHE_BASE - add rdx,rcx + mov rcx,sub_10603560 + mov rdx,[rcx] mov rcx,rax Improves codegen for dispatch stub: and rax,byte +0x1f - mov eax,[rcx+rax*4] - mov eax,eax - mov rcx,JIT_CACHE_BASE - lea rax,[rcx+rax] + mov rax,[rcx+rax*8] mov rcx,rbx * Remove `JitCacheSymbol` & `JitCache.Offset` * Turn `Translator.Translate` into an instance method We do not have to add more parameter to this method and related ones as new structures are added & needed for translation. * Add symbol only when PTC is enabled Address LDj3SNuD's feedback * Change `NativeContext.Running` to a 32-bit integer * Fix PageTable symbol for host mapped --- ARMeilleure/CodeGen/X86/Assembler.cs | 6 +- ARMeilleure/Common/AddressTable.cs | 261 +++++++++++++ ARMeilleure/Common/EntryTable.cs | 2 +- .../EventSources/AddressTableEventSource.cs | 51 +++ .../Instructions/InstEmitFlowHelper.cs | 250 +++---------- .../Instructions/InstEmitMemoryHelper.cs | 8 +- ARMeilleure/Instructions/NativeInterface.cs | 17 - .../IntermediateRepresentation/Operand.cs | 22 +- .../OperandHelper.cs | 14 +- ARMeilleure/Optimizations.cs | 3 + ARMeilleure/State/ExecutionContext.cs | 8 +- ARMeilleure/State/NativeContext.cs | 15 +- ARMeilleure/Translation/ArmEmitterContext.cs | 39 +- ARMeilleure/Translation/Cache/JitCache.cs | 2 + ARMeilleure/Translation/Cache/JumpTable.cs | 279 -------------- .../Cache/JumpTableEntryAllocator.cs | 72 ---- ARMeilleure/Translation/Delegates.cs | 1 - ARMeilleure/Translation/DirectCallStubs.cs | 125 ------- ARMeilleure/Translation/DispatcherFunction.cs | 6 + ARMeilleure/Translation/EmitterContext.cs | 27 +- ARMeilleure/Translation/PTC/Ptc.cs | 152 +++----- ARMeilleure/Translation/PTC/PtcInfo.cs | 3 +- ARMeilleure/Translation/PTC/PtcJumpTable.cs | 350 ------------------ ARMeilleure/Translation/PTC/RelocEntry.cs | 10 +- ARMeilleure/Translation/PTC/Symbol.cs | 100 +++++ ARMeilleure/Translation/PTC/SymbolType.cs | 28 ++ ARMeilleure/Translation/Translator.cs | 146 +++++--- ARMeilleure/Translation/TranslatorStubs.cs | 248 +++++++++++++ Ryujinx.Cpu/CpuContext.cs | 4 +- Ryujinx.HLE/HOS/ArmProcessContext.cs | 5 +- Ryujinx.HLE/HOS/ArmProcessContextFactory.cs | 6 +- .../Kernel/Process/IProcessContextFactory.cs | 5 +- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 4 +- .../Kernel/Process/ProcessContextFactory.cs | 5 +- Ryujinx.Tests/Cpu/CpuTest.cs | 8 +- Ryujinx.Tests/Cpu/CpuTest32.cs | 10 +- 36 files changed, 1020 insertions(+), 1272 deletions(-) create mode 100644 ARMeilleure/Common/AddressTable.cs create mode 100644 ARMeilleure/Diagnostics/EventSources/AddressTableEventSource.cs delete mode 100644 ARMeilleure/Translation/Cache/JumpTable.cs delete mode 100644 ARMeilleure/Translation/Cache/JumpTableEntryAllocator.cs delete mode 100644 ARMeilleure/Translation/DirectCallStubs.cs create mode 100644 ARMeilleure/Translation/DispatcherFunction.cs delete mode 100644 ARMeilleure/Translation/PTC/PtcJumpTable.cs create mode 100644 ARMeilleure/Translation/PTC/Symbol.cs create mode 100644 ARMeilleure/Translation/PTC/SymbolType.cs create mode 100644 ARMeilleure/Translation/TranslatorStubs.cs diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs index bab4c4530b..39aeb7c9df 100644 --- a/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -963,8 +963,6 @@ namespace ARMeilleure.CodeGen.X86 } else if (dest?.Kind == OperandKind.Register && info.OpRImm64 != BadOp) { - int? index = source.PtcIndex; - int rexPrefix = GetRexPrefix(dest, source, type, rrm: false); if (rexPrefix != 0) @@ -974,9 +972,9 @@ namespace ARMeilleure.CodeGen.X86 WriteByte((byte)(info.OpRImm64 + (dest.GetRegister().Index & 0b111))); - if (_ptcInfo != null && index != null) + if (_ptcInfo != null && source.Relocatable) { - _ptcInfo.WriteRelocEntry(new RelocEntry((int)_stream.Position, (int)index)); + _ptcInfo.WriteRelocEntry(new RelocEntry((int)_stream.Position, source.Symbol)); } WriteUInt64(imm); diff --git a/ARMeilleure/Common/AddressTable.cs b/ARMeilleure/Common/AddressTable.cs new file mode 100644 index 0000000000..4af1dc3a0a --- /dev/null +++ b/ARMeilleure/Common/AddressTable.cs @@ -0,0 +1,261 @@ +using ARMeilleure.Diagnostics.EventSources; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + /// + /// Represents a table of guest address to a value. + /// + /// Type of the value + unsafe class AddressTable : IDisposable where TEntry : unmanaged + { + /// + /// 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 bool _disposed; + private TEntry** _table; + private readonly List _pages; + + /// + /// Gets the bits used by the of the instance. + /// + public ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + public Level[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + public TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + public IntPtr Base + { + get + { + if (_disposed) + { + throw new ObjectDisposedException(null); + } + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// + /// Constructs a new instance of the class with the specified list of + /// . + /// + /// is null + /// Length of is less than 2 + public AddressTable(Level[] levels) + { + if (levels == null) + { + throw new ArgumentNullException(nameof(levels)); + } + + if (levels.Length < 2) + { + throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); + } + + _pages = new List(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + } + + /// + /// 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) + { + if (_disposed) + { + throw new ObjectDisposedException(null); + } + + 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)]; + } + } + + /// + /// Gets the leaf page for the specified guest . + /// + /// Guest address + /// Leaf page for the specified guest + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref Level level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null) + { + 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; + } + + /// + /// Lazily initialize and get the root page of the . + /// + /// Root page of the + private TEntry** GetRootPage() + { + if (_table == null) + { + _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + } + + return _table; + } + + /// + /// Allocates a block of memory of the specified type and length. + /// + /// Type of elements + /// Number of elements + /// Fill value + /// if leaf; otherwise + /// Allocated block + private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + var page = Marshal.AllocHGlobal(size); + var span = new Span((void*)page, length); + + span.Fill(fill); + + _pages.Add(page); + + AddressTableEventSource.Log.Allocated(size, leaf); + + return page; + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + Marshal.FreeHGlobal(page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/ARMeilleure/Common/EntryTable.cs b/ARMeilleure/Common/EntryTable.cs index a0ed7c8e53..b61af8f81d 100644 --- a/ARMeilleure/Common/EntryTable.cs +++ b/ARMeilleure/Common/EntryTable.cs @@ -168,7 +168,7 @@ namespace ARMeilleure.Common } /// - /// Releases all unmanaged and optionally managed resources used by the + /// Releases all unmanaged and optionally managed resources used by the /// instance. /// /// to dispose managed resources also; otherwise just unmanaged resouces diff --git a/ARMeilleure/Diagnostics/EventSources/AddressTableEventSource.cs b/ARMeilleure/Diagnostics/EventSources/AddressTableEventSource.cs new file mode 100644 index 0000000000..201a25623c --- /dev/null +++ b/ARMeilleure/Diagnostics/EventSources/AddressTableEventSource.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.Tracing; + +namespace ARMeilleure.Diagnostics.EventSources +{ + [EventSource(Name = "ARMeilleure")] + class AddressTableEventSource : EventSource + { + public static readonly AddressTableEventSource Log = new(); + + private ulong _size; + private ulong _leafSize; + private PollingCounter _sizeCounter; + private PollingCounter _leafSizeCounter; + + public AddressTableEventSource() + { + _sizeCounter = new PollingCounter("addr-tab-alloc", this, () => _size / 1024d / 1024d) + { + DisplayName = "AddressTable Total Bytes Allocated", + DisplayUnits = "MB" + }; + + _leafSizeCounter = new PollingCounter("addr-tab-leaf-alloc", this, () => _leafSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Leaf Bytes Allocated", + DisplayUnits = "MB" + }; + } + + public void Allocated(int bytes, bool leaf) + { + _size += (uint)bytes; + + if (leaf) + { + _leafSize += (uint)bytes; + } + } + + protected override void Dispose(bool disposing) + { + _leafSizeCounter.Dispose(); + _leafSizeCounter = null; + + _sizeCounter.Dispose(); + _sizeCounter = null; + + base.Dispose(disposing); + } + } +} diff --git a/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/ARMeilleure/Instructions/InstEmitFlowHelper.cs index e1309a4e36..808d15c805 100644 --- a/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -150,21 +150,70 @@ namespace ARMeilleure.Instructions } else { - EmitJumpTableBranch(context, Const(immediate), isJump: false); + EmitTableBranch(context, Const(immediate), isJump: false); } } - private static void EmitNativeCall(ArmEmitterContext context, Operand nativeContextPtr, Operand funcAddr, bool isJump) + public static void EmitVirtualCall(ArmEmitterContext context, Operand target) { + EmitTableBranch(context, target, isJump: false); + } + + public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) + { + if (isReturn) + { + context.Return(target); + } + else + { + EmitTableBranch(context, target, isJump: true); + } + } + + private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump) + { + context.StoreToContext(); + + if (guestAddress.Type == OperandType.I32) + { + guestAddress = context.ZeroExtend32(OperandType.I64, guestAddress); + } + + // Store the target guest address into the native context. The stubs uses this address to dispatch into the + // next translation. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + context.Store(dispAddressAddr, guestAddress); + + Operand hostAddress; + + // 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)) + { + Operand hostAddressAddr = !context.HasPtc ? + Const(ref context.FunctionTable.GetValue(guestAddress.Value)) : + Const(ref context.FunctionTable.GetValue(guestAddress.Value), new Symbol(SymbolType.FunctionTable, guestAddress.Value)); + + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + } + else + { + hostAddress = !context.HasPtc ? + Const((long)context.Stubs.DispatchStub) : + Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol); + } + if (isJump) { - context.Tailcall(funcAddr, nativeContextPtr); + context.Tailcall(hostAddress, nativeContext); } else { OpCode op = context.CurrOp; - Operand returnAddress = context.Call(funcAddr, OperandType.I64, nativeContextPtr); + Operand returnAddress = context.Call(hostAddress, OperandType.I64, nativeContext); context.LoadFromContext(); @@ -177,203 +226,10 @@ namespace ARMeilleure.Instructions // If the return address isn't to our next instruction, we need to return so the JIT can figure out // what to do. Operand lblContinue = context.GetLabel(nextAddr.Value); - - // We need to clear out the call flag for the return address before comparing it. context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); context.Return(returnAddress); } } - - private static void EmitNativeCall(ArmEmitterContext context, Operand funcAddr, bool isJump) - { - EmitNativeCall(context, context.LoadArgument(OperandType.I64, 0), funcAddr, isJump); - } - - public static void EmitVirtualCall(ArmEmitterContext context, Operand target) - { - EmitJumpTableBranch(context, target, isJump: false); - } - - public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) - { - if (isReturn) - { - context.Return(target); - } - else - { - EmitJumpTableBranch(context, target, isJump: true); - } - } - - public static void EmitTailContinue(ArmEmitterContext context, Operand address) - { - // Left option here as it may be useful if we need to return to managed rather than tail call in future. - // (eg. for debug) - bool useTailContinue = true; - - if (useTailContinue) - { - if (context.HighCq) - { - // If we're doing a tail continue in HighCq, reserve a space in the jump table to avoid calling back - // to the translator. This will always try to get a HighCq version of our continue target as well. - EmitJumpTableBranch(context, address, isJump: true); - } - else - { - context.StoreToContext(); - - Operand fallbackAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), address); - - EmitNativeCall(context, fallbackAddr, isJump: true); - } - } - else - { - context.Return(address); - } - } - - private static void EmitNativeCallWithGuestAddress(ArmEmitterContext context, Operand funcAddr, Operand guestAddress, bool isJump) - { - Operand nativeContextPtr = context.LoadArgument(OperandType.I64, 0); - context.Store(context.Add(nativeContextPtr, Const((long)NativeContext.GetCallAddressOffset())), guestAddress); - - EmitNativeCall(context, nativeContextPtr, funcAddr, isJump); - } - - private static void EmitBranchFallback(ArmEmitterContext context, Operand address, bool isJump) - { - Operand fallbackAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), address); - - EmitNativeCall(context, fallbackAddr, isJump); - } - - private static void EmitDynamicTableCall(ArmEmitterContext context, Operand tableAddress, Operand address, bool isJump) - { - // Loop over elements of the dynamic table. Unrolled loop. - - Operand endLabel = Label(); - Operand fallbackLabel = Label(); - - void EmitTableEntry(Operand entrySkipLabel) - { - // Try to take this entry in the table if its guest address equals 0. - Operand gotResult = context.CompareAndSwap(tableAddress, Const(0L), address); - - // Is the address ours? (either taken via CompareAndSwap (0), or what was already here) - context.BranchIfFalse(entrySkipLabel, - context.BitwiseOr( - context.ICompareEqual(gotResult, address), - context.ICompareEqual(gotResult, Const(0L))) - ); - - // It's ours, so what function is it pointing to? - Operand targetFunctionPtr = context.Add(tableAddress, Const(8L)); - Operand targetFunction = context.Load(OperandType.I64, targetFunctionPtr); - - // Call the function. - // We pass in the entry address as the guest address, as the entry may need to be updated by the - // indirect call stub. - EmitNativeCallWithGuestAddress(context, targetFunction, tableAddress, isJump); - - context.Branch(endLabel); - } - - // Currently this uses a size of 1, as higher values inflate code size for no real benefit. - for (int i = 0; i < JumpTable.DynamicTableElems; i++) - { - if (i == JumpTable.DynamicTableElems - 1) - { - // If this is the last entry, avoid emitting the additional label and add. - EmitTableEntry(fallbackLabel); - } - else - { - Operand nextLabel = Label(); - - EmitTableEntry(nextLabel); - - context.MarkLabel(nextLabel); - - // Move to the next table entry. - tableAddress = context.Add(tableAddress, Const((long)JumpTable.JumpTableStride)); - } - } - - context.MarkLabel(fallbackLabel); - - EmitBranchFallback(context, address, isJump); - - context.MarkLabel(endLabel); - } - - private static void EmitJumpTableBranch(ArmEmitterContext context, Operand address, bool isJump) - { - if (address.Type == OperandType.I32) - { - address = context.ZeroExtend32(OperandType.I64, address); - } - - context.StoreToContext(); - - // TODO: Constant folding. Indirect calls are slower in the best case and emit more code so we want to - // avoid them when possible. - bool isConst = address.Kind == OperandKind.Constant; - ulong constAddr = address.Value; - - if (!context.HighCq) - { - // Don't emit indirect calls or jumps if we're compiling in lowCq mode. This avoids wasting space on the - // jump and indirect tables. Just ask the translator for the function address. - EmitBranchFallback(context, address, isJump); - } - else if (!isConst) - { - // Virtual branch/call - store first used addresses on a small table for fast lookup. - int entry = context.JumpTable.ReserveDynamicEntry(context.EntryAddress, isJump); - - int jumpOffset = entry * JumpTable.JumpTableStride * JumpTable.DynamicTableElems; - - Operand dynTablePtr; - - if (Ptc.State == PtcState.Disabled) - { - dynTablePtr = Const(context.JumpTable.DynamicPointer.ToInt64() + jumpOffset); - } - else - { - dynTablePtr = Const(context.JumpTable.DynamicPointer.ToInt64(), true, Ptc.DynamicPointerIndex); - dynTablePtr = context.Add(dynTablePtr, Const((long)jumpOffset)); - } - - EmitDynamicTableCall(context, dynTablePtr, address, isJump); - } - else - { - int entry = context.JumpTable.ReserveTableEntry(context.EntryAddress, constAddr, isJump); - - int jumpOffset = entry * JumpTable.JumpTableStride + 8; // Offset directly to the host address. - - Operand tableEntryPtr; - - if (Ptc.State == PtcState.Disabled) - { - tableEntryPtr = Const(context.JumpTable.JumpPointer.ToInt64() + jumpOffset); - } - else - { - tableEntryPtr = Const(context.JumpTable.JumpPointer.ToInt64(), true, Ptc.JumpPointerIndex); - tableEntryPtr = context.Add(tableEntryPtr, Const((long)jumpOffset)); - } - - Operand funcAddr = context.Load(OperandType.I64, tableEntryPtr); - - // Call the function directly. If it's not present yet, this will call the direct call stub. - EmitNativeCallWithGuestAddress(context, funcAddr, address, isJump); - } - } } } diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs index 2de1230462..3bac685591 100644 --- a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs +++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -327,9 +327,9 @@ namespace ARMeilleure.Instructions Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address; Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size)); - Operand pte = Ptc.State == PtcState.Disabled + Operand pte = !context.HasPtc ? Const(context.Memory.PageTablePointer.ToInt64()) - : Const(context.Memory.PageTablePointer.ToInt64(), true, Ptc.PageTablePointerIndex); + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask)); @@ -411,9 +411,9 @@ namespace ARMeilleure.Instructions address = context.BitwiseAnd(address, mask); } - Operand baseAddr = Ptc.State == PtcState.Disabled + Operand baseAddr = !context.HasPtc ? Const(context.Memory.PageTablePointer.ToInt64()) - : Const(context.Memory.PageTablePointer.ToInt64(), true, Ptc.PageTablePointerIndex); + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); return context.Add(baseAddr, address); } diff --git a/ARMeilleure/Instructions/NativeInterface.cs b/ARMeilleure/Instructions/NativeInterface.cs index fa17d33499..02a22fa63a 100644 --- a/ARMeilleure/Instructions/NativeInterface.cs +++ b/ARMeilleure/Instructions/NativeInterface.cs @@ -242,23 +242,6 @@ namespace ARMeilleure.Instructions return (ulong)function.FuncPtr.ToInt64(); } - public static ulong GetIndirectFunctionAddress(ulong address, ulong entryAddress) - { - TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); - - ulong ptr = (ulong)function.FuncPtr.ToInt64(); - - if (function.HighCq) - { - Debug.Assert(Context.Translator.JumpTable.CheckEntryFromAddressDynamicTable((IntPtr)entryAddress)); - - // Rewrite the host function address in the table to point to the highCq function. - Marshal.WriteInt64((IntPtr)entryAddress, 8, (long)ptr); - } - - return ptr; - } - public static bool CheckSynchronization() { Statistics.PauseTimer(); diff --git a/ARMeilleure/IntermediateRepresentation/Operand.cs b/ARMeilleure/IntermediateRepresentation/Operand.cs index ec02393921..64df416f54 100644 --- a/ARMeilleure/IntermediateRepresentation/Operand.cs +++ b/ARMeilleure/IntermediateRepresentation/Operand.cs @@ -1,3 +1,4 @@ +using ARMeilleure.Translation.PTC; using System; using System.Collections.Generic; using System.Diagnostics; @@ -12,12 +13,12 @@ namespace ARMeilleure.IntermediateRepresentation public ulong Value { get; private set; } - public bool Relocatable { get; private set; } - public int? PtcIndex { get; private set; } - public List Assignments { get; } public List Uses { get; } + public Symbol Symbol { get; private set; } + public bool Relocatable => Symbol.Type != SymbolType.None; + public Operand() { Assignments = new List(); @@ -34,16 +35,14 @@ namespace ARMeilleure.IntermediateRepresentation OperandKind kind, OperandType type = OperandType.None, ulong value = 0, - bool relocatable = false, - int? index = null) + Symbol symbol = default) { Kind = kind; Type = type; Value = value; - Relocatable = relocatable; - PtcIndex = index; + Symbol = symbol; Assignments.Clear(); Uses.Clear(); @@ -61,9 +60,14 @@ namespace ARMeilleure.IntermediateRepresentation return With(OperandKind.Constant, OperandType.I32, value); } - public Operand With(long value, bool relocatable = false, int? index = null) + public Operand With(long value) { - return With(OperandKind.Constant, OperandType.I64, (ulong)value, relocatable, index); + return With(OperandKind.Constant, OperandType.I64, (ulong)value); + } + + public Operand With(long value, Symbol symbol) + { + return With(OperandKind.Constant, OperandType.I64, (ulong)value, symbol); } public Operand With(ulong value) diff --git a/ARMeilleure/IntermediateRepresentation/OperandHelper.cs b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs index 1b748f6a27..420555a785 100644 --- a/ARMeilleure/IntermediateRepresentation/OperandHelper.cs +++ b/ARMeilleure/IntermediateRepresentation/OperandHelper.cs @@ -1,4 +1,5 @@ using ARMeilleure.Common; +using ARMeilleure.Translation.PTC; using System.Runtime.CompilerServices; namespace ARMeilleure.IntermediateRepresentation @@ -25,9 +26,14 @@ namespace ARMeilleure.IntermediateRepresentation return Operand().With(value); } - public static Operand Const(long value, bool relocatable = false, int? index = null) + public static Operand Const(long value) { - return Operand().With(value, relocatable, index); + return Operand().With(value); + } + + public static Operand Const(long value, Symbol symbol) + { + return Operand().With(value, symbol); } public static Operand Const(ulong value) @@ -35,9 +41,9 @@ namespace ARMeilleure.IntermediateRepresentation return Operand().With(value); } - public static unsafe Operand Const(ref T reference, int? index = null) + public static unsafe Operand Const(ref T reference, Symbol symbol = default) { - return Operand().With((long)Unsafe.AsPointer(ref reference), index != null, index); + return Operand().With((long)Unsafe.AsPointer(ref reference), symbol); } public static Operand ConstF(float value) diff --git a/ARMeilleure/Optimizations.cs b/ARMeilleure/Optimizations.cs index 0fd67c8223..986b0c9ecb 100644 --- a/ARMeilleure/Optimizations.cs +++ b/ARMeilleure/Optimizations.cs @@ -6,6 +6,9 @@ namespace ARMeilleure { public static bool FastFP { get; set; } = true; + public static bool AllowLcqInFunctionTable { get; set; } = true; + public static bool UseUnmanagedDispatchLoop { get; set; } = true; + public static bool UseSseIfAvailable { get; set; } = true; public static bool UseSse2IfAvailable { get; set; } = true; public static bool UseSse3IfAvailable { get; set; } = true; diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs index a964f6baa8..9a2215695e 100644 --- a/ARMeilleure/State/ExecutionContext.cs +++ b/ARMeilleure/State/ExecutionContext.cs @@ -66,7 +66,11 @@ namespace ARMeilleure.State } } - public bool Running { get; private set; } + public bool Running + { + get => _nativeContext.GetRunning(); + private set => _nativeContext.SetRunning(value); + } public event EventHandler Interrupt; public event EventHandler Break; @@ -78,7 +82,6 @@ namespace ARMeilleure.State _hostTickFreq = 1.0 / Stopwatch.Frequency; _tickCounter = new Stopwatch(); - _tickCounter.Start(); } @@ -138,6 +141,7 @@ namespace ARMeilleure.State public void StopRunning() { Running = false; + _nativeContext.SetCounter(0); } diff --git a/ARMeilleure/State/NativeContext.cs b/ARMeilleure/State/NativeContext.cs index 09ec6cdede..962783f5e2 100644 --- a/ARMeilleure/State/NativeContext.cs +++ b/ARMeilleure/State/NativeContext.cs @@ -14,10 +14,11 @@ namespace ARMeilleure.State public fixed uint Flags[RegisterConsts.FlagsCount]; public fixed uint FpFlags[RegisterConsts.FpFlagsCount]; public int Counter; - public ulong CallAddress; + public ulong DispatchAddress; public ulong ExclusiveAddress; public ulong ExclusiveValueLow; public ulong ExclusiveValueHigh; + public int Running; } private static NativeCtxStorage _dummyStorage = new NativeCtxStorage(); @@ -117,6 +118,9 @@ namespace ARMeilleure.State public int GetCounter() => GetStorage().Counter; public void SetCounter(int value) => GetStorage().Counter = value; + public bool GetRunning() => GetStorage().Running != 0; + public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; + public unsafe static int GetRegisterOffset(Register reg) { if (reg.Type == RegisterType.Integer) @@ -162,9 +166,9 @@ namespace ARMeilleure.State return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter); } - public static int GetCallAddressOffset() + public static int GetDispatchAddressOffset() { - return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallAddress); + return StorageOffset(ref _dummyStorage, ref _dummyStorage.DispatchAddress); } public static int GetExclusiveAddressOffset() @@ -177,6 +181,11 @@ namespace ARMeilleure.State return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveValueLow); } + public static int GetRunningOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); + } + private static int StorageOffset(ref NativeCtxStorage storage, ref T target) { return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); diff --git a/ARMeilleure/Translation/ArmEmitterContext.cs b/ARMeilleure/Translation/ArmEmitterContext.cs index ad44b0cfb8..7a82b27b14 100644 --- a/ARMeilleure/Translation/ArmEmitterContext.cs +++ b/ARMeilleure/Translation/ArmEmitterContext.cs @@ -1,12 +1,14 @@ using ARMeilleure.Common; using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; using ARMeilleure.Instructions; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.Memory; using ARMeilleure.State; -using ARMeilleure.Translation.Cache; +using ARMeilleure.Translation.PTC; +using System; using System.Collections.Generic; - +using System.Reflection; using static ARMeilleure.IntermediateRepresentation.OperandHelper; namespace ARMeilleure.Translation @@ -41,8 +43,11 @@ namespace ARMeilleure.Translation public IMemoryManager Memory { get; } - public JumpTable JumpTable { get; } + public bool HasPtc { get; } + public EntryTable CountTable { get; } + public AddressTable FunctionTable { get; } + public TranslatorStubs Stubs { get; } public ulong EntryAddress { get; } public bool HighCq { get; } @@ -50,15 +55,18 @@ namespace ARMeilleure.Translation public ArmEmitterContext( IMemoryManager memory, - JumpTable jumpTable, EntryTable countTable, + AddressTable funcTable, + TranslatorStubs stubs, ulong entryAddress, bool highCq, Aarch32Mode mode) { + HasPtc = Ptc.State != PtcState.Disabled; Memory = memory; - JumpTable = jumpTable; CountTable = countTable; + FunctionTable = funcTable; + Stubs = stubs; EntryAddress = entryAddress; HighCq = highCq; Mode = mode; @@ -66,6 +74,27 @@ namespace ARMeilleure.Translation _labels = new Dictionary(); } + public override Operand Call(MethodInfo info, params Operand[] callArgs) + { + if (!HasPtc) + { + return base.Call(info, callArgs); + } + else + { + int index = Delegates.GetDelegateIndex(info); + IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbol symbol = new Symbol(SymbolType.DelegateTable, (ulong)index); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64(), symbol), returnType, callArgs); + } + } + public Operand GetLabel(ulong address) { if (!_labels.TryGetValue(address, out Operand label)) diff --git a/ARMeilleure/Translation/Cache/JitCache.cs b/ARMeilleure/Translation/Cache/JitCache.cs index db45c608ba..c0d0a25dee 100644 --- a/ARMeilleure/Translation/Cache/JitCache.cs +++ b/ARMeilleure/Translation/Cache/JitCache.cs @@ -25,6 +25,8 @@ namespace ARMeilleure.Translation.Cache private static readonly object _lock = new object(); private static bool _initialized; + public static IntPtr Base => _jitRegion.Pointer; + public static void Initialize(IJitMemoryAllocator allocator) { if (_initialized) return; diff --git a/ARMeilleure/Translation/Cache/JumpTable.cs b/ARMeilleure/Translation/Cache/JumpTable.cs deleted file mode 100644 index aa3b6caf30..0000000000 --- a/ARMeilleure/Translation/Cache/JumpTable.cs +++ /dev/null @@ -1,279 +0,0 @@ -using ARMeilleure.Diagnostics; -using ARMeilleure.Memory; -using ARMeilleure.Translation.PTC; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace ARMeilleure.Translation.Cache -{ - class JumpTable : IDisposable - { - // The jump table is a block of (guestAddress, hostAddress) function mappings. - // Each entry corresponds to one branch in a JIT compiled function. The entries are - // reserved specifically for each call. - // The Dependants dictionary can be used to update the hostAddress for any functions that change. - - public const int JumpTableStride = 16; // 8 byte guest address, 8 byte host address. - - private const int JumpTableSize = 1048576; - private const int JumpTableByteSize = JumpTableSize * JumpTableStride; - - // The dynamic table is also a block of (guestAddress, hostAddress) function mappings. - // The main difference is that indirect calls and jumps reserve _multiple_ entries on the table. - // These start out as all 0. When an indirect call is made, it tries to find the guest address on the table. - - // If we get to an empty address, the guestAddress is set to the call that we want. - - // If we get to a guestAddress that matches our own (or we just claimed it), the hostAddress is read. - // If it is non-zero, we immediately branch or call the host function. - // If it is 0, NativeInterface is called to find the rejited address of the call. - // If none is found, the hostAddress entry stays at 0. Otherwise, the new address is placed in the entry. - - // If the table size is exhausted and we didn't find our desired address, we fall back to requesting - // the function from the JIT. - - public const int DynamicTableElems = 1; - - public const int DynamicTableStride = DynamicTableElems * JumpTableStride; - - private const int DynamicTableSize = 1048576; - private const int DynamicTableByteSize = DynamicTableSize * DynamicTableStride; - - public const int DynamicEntryTag = 1 << 31; - - private readonly ReservedRegion _jumpRegion; - private readonly ReservedRegion _dynamicRegion; - - public IntPtr JumpPointer => _jumpRegion.Pointer; - public IntPtr DynamicPointer => _dynamicRegion.Pointer; - - public JumpTableEntryAllocator Table { get; } - public JumpTableEntryAllocator DynTable { get; } - - public ConcurrentDictionary Targets { get; } - public ConcurrentDictionary> Dependants { get; } // TODO: Attach to TranslatedFunction or a wrapper class. - public ConcurrentDictionary> Owners { get; } - - public JumpTable(IJitMemoryAllocator allocator) - { - _jumpRegion = new ReservedRegion(allocator, JumpTableByteSize); - _dynamicRegion = new ReservedRegion(allocator, DynamicTableByteSize); - - Table = new JumpTableEntryAllocator(); - DynTable = new JumpTableEntryAllocator(); - - Targets = new ConcurrentDictionary(); - Dependants = new ConcurrentDictionary>(); - Owners = new ConcurrentDictionary>(); - - Symbols.Add((ulong)_jumpRegion.Pointer.ToInt64(), JumpTableByteSize, JumpTableStride, "JMP_TABLE"); - Symbols.Add((ulong)_dynamicRegion.Pointer.ToInt64(), DynamicTableByteSize, DynamicTableStride, "DYN_TABLE"); - } - - public void Initialize(PtcJumpTable ptcJumpTable, ConcurrentDictionary funcs) - { - foreach (ulong guestAddress in ptcJumpTable.Targets) - { - if (funcs.TryGetValue(guestAddress, out TranslatedFunction func)) - { - Targets.TryAdd(guestAddress, func); - } - else - { - throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{guestAddress:X16})"); - } - } - - foreach (var kv in ptcJumpTable.Dependants) - { - Dependants.TryAdd(kv.Key, new List(kv.Value)); - } - - foreach (var kv in ptcJumpTable.Owners) - { - Owners.TryAdd(kv.Key, new List(kv.Value)); - } - } - - public void RegisterFunction(ulong address, TranslatedFunction func) - { - Targets.AddOrUpdate(address, func, (key, oldFunc) => func); - long funcPtr = func.FuncPtr.ToInt64(); - - // Update all jump table entries that target this address. - if (Dependants.TryGetValue(address, out List myDependants)) - { - lock (myDependants) - { - foreach (int entry in myDependants) - { - IntPtr addr = GetEntryAddressJumpTable(entry); - - Marshal.WriteInt64(addr, 8, funcPtr); - } - } - } - } - - public int ReserveTableEntry(ulong ownerGuestAddress, ulong address, bool isJump) - { - int entry = Table.AllocateEntry(); - - ExpandIfNeededJumpTable(entry); - - // Is the address we have already registered? If so, put the function address in the jump table. - // If not, it will point to the direct call stub. - long value = DirectCallStubs.DirectCallStub(isJump).ToInt64(); - if (Targets.TryGetValue(address, out TranslatedFunction func)) - { - value = func.FuncPtr.ToInt64(); - } - - // Make sure changes to the function at the target address update this jump table entry. - List targetDependants = Dependants.GetOrAdd(address, (addr) => new List()); - lock (targetDependants) - { - targetDependants.Add(entry); - } - - // Keep track of ownership for jump table entries. - List ownerEntries = Owners.GetOrAdd(ownerGuestAddress, (addr) => new List()); - lock (ownerEntries) - { - ownerEntries.Add(entry); - } - - IntPtr addr = GetEntryAddressJumpTable(entry); - - Marshal.WriteInt64(addr, 0, (long)address); - Marshal.WriteInt64(addr, 8, value); - - return entry; - } - - public int ReserveDynamicEntry(ulong ownerGuestAddress, bool isJump) - { - int entry = DynTable.AllocateEntry(); - - ExpandIfNeededDynamicTable(entry); - - // Keep track of ownership for jump table entries. - List ownerEntries = Owners.GetOrAdd(ownerGuestAddress, (addr) => new List()); - lock (ownerEntries) - { - ownerEntries.Add(entry | DynamicEntryTag); - } - - // Initialize all host function pointers to the indirect call stub. - IntPtr addr = GetEntryAddressDynamicTable(entry); - long stubPtr = DirectCallStubs.IndirectCallStub(isJump).ToInt64(); - - for (int i = 0; i < DynamicTableElems; i++) - { - Marshal.WriteInt64(addr, i * JumpTableStride + 8, stubPtr); - } - - return entry; - } - - // For future use. - public void RemoveFunctionEntries(ulong guestAddress) - { - Targets.TryRemove(guestAddress, out _); - Dependants.TryRemove(guestAddress, out _); - - if (Owners.TryRemove(guestAddress, out List entries)) - { - foreach (int entry in entries) - { - if ((entry & DynamicEntryTag) == 0) - { - IntPtr addr = GetEntryAddressJumpTable(entry); - - Marshal.WriteInt64(addr, 0, 0L); - Marshal.WriteInt64(addr, 8, 0L); - - Table.FreeEntry(entry); - } - else - { - IntPtr addr = GetEntryAddressDynamicTable(entry & ~DynamicEntryTag); - - for (int j = 0; j < DynamicTableElems; j++) - { - Marshal.WriteInt64(addr + j * JumpTableStride, 0, 0L); - Marshal.WriteInt64(addr + j * JumpTableStride, 8, 0L); - } - - DynTable.FreeEntry(entry & ~DynamicEntryTag); - } - } - } - } - - public void ExpandIfNeededJumpTable(int entry) - { - Debug.Assert(entry >= 0); - - if (entry < JumpTableSize) - { - _jumpRegion.ExpandIfNeeded((ulong)((entry + 1) * JumpTableStride)); - } - else - { - throw new OutOfMemoryException("JIT Direct Jump Table exhausted."); - } - } - - public void ExpandIfNeededDynamicTable(int entry) - { - Debug.Assert(entry >= 0); - - if (entry < DynamicTableSize) - { - _dynamicRegion.ExpandIfNeeded((ulong)((entry + 1) * DynamicTableStride)); - } - else - { - throw new OutOfMemoryException("JIT Dynamic Jump Table exhausted."); - } - } - - public IntPtr GetEntryAddressJumpTable(int entry) - { - Debug.Assert(Table.EntryIsValid(entry)); - - return _jumpRegion.Pointer + entry * JumpTableStride; - } - - public IntPtr GetEntryAddressDynamicTable(int entry) - { - Debug.Assert(DynTable.EntryIsValid(entry)); - - return _dynamicRegion.Pointer + entry * DynamicTableStride; - } - - public bool CheckEntryFromAddressJumpTable(IntPtr entryAddress) - { - int entry = Math.DivRem((int)((ulong)entryAddress - (ulong)_jumpRegion.Pointer), JumpTableStride, out int rem); - - return rem == 0 && Table.EntryIsValid(entry); - } - - public bool CheckEntryFromAddressDynamicTable(IntPtr entryAddress) - { - int entry = Math.DivRem((int)((ulong)entryAddress - (ulong)_dynamicRegion.Pointer), DynamicTableStride, out int rem); - - return rem == 0 && DynTable.EntryIsValid(entry); - } - - public void Dispose() - { - _jumpRegion.Dispose(); - _dynamicRegion.Dispose(); - } - } -} diff --git a/ARMeilleure/Translation/Cache/JumpTableEntryAllocator.cs b/ARMeilleure/Translation/Cache/JumpTableEntryAllocator.cs deleted file mode 100644 index ae2c075e14..0000000000 --- a/ARMeilleure/Translation/Cache/JumpTableEntryAllocator.cs +++ /dev/null @@ -1,72 +0,0 @@ -using ARMeilleure.Common; -using System.Collections.Generic; -using System.Diagnostics; - -namespace ARMeilleure.Translation.Cache -{ - class JumpTableEntryAllocator - { - private readonly BitMap _bitmap; - private int _freeHint; - - public JumpTableEntryAllocator() - { - _bitmap = new BitMap(); - } - - public bool EntryIsValid(int entryIndex) - { - lock (_bitmap) - { - return _bitmap.IsSet(entryIndex); - } - } - - public void SetEntry(int entryIndex) - { - lock (_bitmap) - { - _bitmap.Set(entryIndex); - } - } - - public int AllocateEntry() - { - lock (_bitmap) - { - int entryIndex; - - if (!_bitmap.IsSet(_freeHint)) - { - entryIndex = _freeHint; - } - else - { - entryIndex = _bitmap.FindFirstUnset(); - } - - _freeHint = entryIndex + 1; - - bool wasSet = _bitmap.Set(entryIndex); - Debug.Assert(wasSet); - - return entryIndex; - } - } - - public void FreeEntry(int entryIndex) - { - lock (_bitmap) - { - _bitmap.Clear(entryIndex); - - _freeHint = entryIndex; - } - } - - public IEnumerable GetEntries() - { - return _bitmap; - } - } -} diff --git a/ARMeilleure/Translation/Delegates.cs b/ARMeilleure/Translation/Delegates.cs index a561d26535..f189eaf710 100644 --- a/ARMeilleure/Translation/Delegates.cs +++ b/ARMeilleure/Translation/Delegates.cs @@ -114,7 +114,6 @@ namespace ARMeilleure.Translation SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only. SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress))); - SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetIndirectFunctionAddress))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr))); SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only. SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0))); diff --git a/ARMeilleure/Translation/DirectCallStubs.cs b/ARMeilleure/Translation/DirectCallStubs.cs deleted file mode 100644 index 85af690187..0000000000 --- a/ARMeilleure/Translation/DirectCallStubs.cs +++ /dev/null @@ -1,125 +0,0 @@ -using ARMeilleure.Instructions; -using ARMeilleure.IntermediateRepresentation; -using ARMeilleure.State; -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -using static ARMeilleure.IntermediateRepresentation.OperandHelper; - -namespace ARMeilleure.Translation -{ - static class DirectCallStubs - { - private delegate long GuestFunction(IntPtr nativeContextPtr); - - private static IntPtr _directCallStubPtr; - private static IntPtr _directTailCallStubPtr; - private static IntPtr _indirectCallStubPtr; - private static IntPtr _indirectTailCallStubPtr; - - private static readonly object _lock = new object(); - private static bool _initialized; - - public static void InitializeStubs() - { - if (_initialized) return; - - lock (_lock) - { - if (_initialized) return; - - Translator.PreparePool(); - - _directCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateDirectCallStub(false)); - _directTailCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateDirectCallStub(true)); - _indirectCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateIndirectCallStub(false)); - _indirectTailCallStubPtr = Marshal.GetFunctionPointerForDelegate(GenerateIndirectCallStub(true)); - - Translator.ResetPool(); - - Translator.DisposePools(); - - _initialized = true; - } - } - - public static IntPtr DirectCallStub(bool tailCall) - { - Debug.Assert(_initialized); - - return tailCall ? _directTailCallStubPtr : _directCallStubPtr; - } - - public static IntPtr IndirectCallStub(bool tailCall) - { - Debug.Assert(_initialized); - - return tailCall ? _indirectTailCallStubPtr : _indirectCallStubPtr; - } - - private static void EmitCall(EmitterContext context, Operand address, bool tailCall) - { - if (tailCall) - { - context.Tailcall(address, context.LoadArgument(OperandType.I64, 0)); - } - else - { - context.Return(context.Call(address, OperandType.I64, context.LoadArgument(OperandType.I64, 0))); - } - } - - /// - /// Generates a stub that is used to find function addresses. Used for direct calls when their jump table does not have the host address yet. - /// Takes a NativeContext like a translated guest function, and extracts the target address from the NativeContext. - /// When the target function is compiled in highCq, all table entries are updated to point to that function instead of this stub by the translator. - /// - private static GuestFunction GenerateDirectCallStub(bool tailCall) - { - EmitterContext context = new EmitterContext(); - - Operand nativeContextPtr = context.LoadArgument(OperandType.I64, 0); - - Operand address = context.Load(OperandType.I64, context.Add(nativeContextPtr, Const((long)NativeContext.GetCallAddressOffset()))); - - Operand functionAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), address); - EmitCall(context, functionAddr, tailCall); - - ControlFlowGraph cfg = context.GetControlFlowGraph(); - - OperandType[] argTypes = new OperandType[] { OperandType.I64 }; - - return Compiler.Compile(cfg, argTypes, OperandType.I64, CompilerOptions.HighCq); - } - - /// - /// Generates a stub that is used to find function addresses and add them to an indirect table. - /// Used for indirect calls entries (already claimed) when their jump table does not have the host address yet. - /// Takes a NativeContext like a translated guest function, and extracts the target indirect table entry from the NativeContext. - /// If the function we find is highCq, the entry in the table is updated to point to that function rather than this stub. - /// - private static GuestFunction GenerateIndirectCallStub(bool tailCall) - { - EmitterContext context = new EmitterContext(); - - Operand nativeContextPtr = context.LoadArgument(OperandType.I64, 0); - - Operand entryAddress = context.Load(OperandType.I64, context.Add(nativeContextPtr, Const((long)NativeContext.GetCallAddressOffset()))); - Operand address = context.Load(OperandType.I64, entryAddress); - - // We need to find the missing function. If the function is HighCq, then it replaces this stub in the indirect table. - // Either way, we call it afterwards. - Operand functionAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetIndirectFunctionAddress)), address, entryAddress); - - // Call and save the function. - EmitCall(context, functionAddr, tailCall); - - ControlFlowGraph cfg = context.GetControlFlowGraph(); - - OperandType[] argTypes = new OperandType[] { OperandType.I64 }; - - return Compiler.Compile(cfg, argTypes, OperandType.I64, CompilerOptions.HighCq); - } - } -} diff --git a/ARMeilleure/Translation/DispatcherFunction.cs b/ARMeilleure/Translation/DispatcherFunction.cs new file mode 100644 index 0000000000..e3ea21f67b --- /dev/null +++ b/ARMeilleure/Translation/DispatcherFunction.cs @@ -0,0 +1,6 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); +} diff --git a/ARMeilleure/Translation/EmitterContext.cs b/ARMeilleure/Translation/EmitterContext.cs index cc2205cecb..fbd9e69137 100644 --- a/ARMeilleure/Translation/EmitterContext.cs +++ b/ARMeilleure/Translation/EmitterContext.cs @@ -97,33 +97,18 @@ namespace ARMeilleure.Translation return Add(Instruction.ByteSwap, Local(op1.Type), op1); } - public Operand Call(MethodInfo info, params Operand[] callArgs) + public virtual Operand Call(MethodInfo info, params Operand[] callArgs) { - if (Ptc.State == PtcState.Disabled) - { - IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info); + IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info); - OperandType returnType = GetOperandType(info.ReturnType); + OperandType returnType = GetOperandType(info.ReturnType); - Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); - return Call(Const(funcPtr.ToInt64()), returnType, callArgs); - } - else - { - int index = Delegates.GetDelegateIndex(info); - - IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index); - - OperandType returnType = GetOperandType(info.ReturnType); - - Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); - - return Call(Const(funcPtr.ToInt64(), true, index), returnType, callArgs); - } + return Call(Const(funcPtr.ToInt64()), returnType, callArgs); } - private static OperandType GetOperandType(Type type) + protected static OperandType GetOperandType(Type type) { if (type == typeof(bool) || type == typeof(byte) || type == typeof(char) || type == typeof(short) || diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs index ed4a003db1..9f07ca011d 100644 --- a/ARMeilleure/Translation/PTC/Ptc.cs +++ b/ARMeilleure/Translation/PTC/Ptc.cs @@ -28,7 +28,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 2289; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 2228; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; @@ -36,10 +36,9 @@ namespace ARMeilleure.Translation.PTC private const string TitleIdTextDefault = "0000000000000000"; private const string DisplayVersionDefault = "0"; - internal const int PageTablePointerIndex = -1; // Must be a negative value. - internal const int JumpPointerIndex = -2; // Must be a negative value. - internal const int DynamicPointerIndex = -3; // Must be a negative value. - internal const int CountTableIndex = -4; // Must be a negative value. + internal static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); + internal static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); + internal static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); private const byte FillingByte = 0x00; private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; @@ -59,8 +58,6 @@ namespace ARMeilleure.Translation.PTC private static bool _disposed; - internal static PtcJumpTable PtcJumpTable { get; private set; } - internal static string TitleIdText { get; private set; } internal static string DisplayVersion { get; private set; } @@ -89,8 +86,6 @@ namespace ARMeilleure.Translation.PTC _disposed = false; - PtcJumpTable = new PtcJumpTable(); - TitleIdText = TitleIdTextDefault; DisplayVersion = DisplayVersionDefault; @@ -348,20 +343,8 @@ namespace ARMeilleure.Translation.PTC return false; } - ReadOnlySpan ptcJumpTableBytes = new(stream.PositionPointer, innerHeader.PtcJumpTableLength); - stream.Seek(innerHeader.PtcJumpTableLength, SeekOrigin.Current); - Debug.Assert(stream.Position == stream.Length); - Hash128 ptcJumpTableHash = XXHash128.ComputeHash(ptcJumpTableBytes); - - if (innerHeader.PtcJumpTableHash != ptcJumpTableHash) - { - InvalidateCompressedStream(compressedStream); - - return false; - } - stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); _infosStream.Write(infosBytes); @@ -375,8 +358,6 @@ namespace ARMeilleure.Translation.PTC _unwindInfosStream.Write(unwindInfosBytes); stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); - PtcJumpTable = PtcJumpTable.Deserialize(stream); - Debug.Assert(stream.Position == stream.Length); } } @@ -422,7 +403,6 @@ namespace ARMeilleure.Translation.PTC finally { ResetCarriersIfNeeded(); - PtcJumpTable.ClearIfNeeded(); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; } @@ -442,7 +422,6 @@ namespace ARMeilleure.Translation.PTC innerHeader.CodesLength = _codesList.Length(); innerHeader.RelocsLength = (int)_relocsStream.Length; innerHeader.UnwindInfosLength = (int)_unwindInfosStream.Length; - innerHeader.PtcJumpTableLength = PtcJumpTable.GetSerializeSize(PtcJumpTable); OuterHeader outerHeader = new OuterHeader(); @@ -459,8 +438,7 @@ namespace ARMeilleure.Translation.PTC innerHeader.InfosLength + innerHeader.CodesLength + innerHeader.RelocsLength + - innerHeader.UnwindInfosLength + - innerHeader.PtcJumpTableLength; + innerHeader.UnwindInfosLength; outerHeader.SetHeaderHash(); @@ -486,16 +464,12 @@ namespace ARMeilleure.Translation.PTC ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); _unwindInfosStream.WriteTo(stream); - ReadOnlySpan ptcJumpTableBytes = new(stream.PositionPointer, innerHeader.PtcJumpTableLength); - PtcJumpTable.Serialize(stream, PtcJumpTable); - Debug.Assert(stream.Position == stream.Length); innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes); innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes); innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes); innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); - innerHeader.PtcJumpTableHash = XXHash128.ComputeHash(ptcJumpTableBytes); innerHeader.SetHeaderHash(); @@ -505,7 +479,6 @@ namespace ARMeilleure.Translation.PTC translatedFuncsCount = GetEntriesCount(); ResetCarriersIfNeeded(); - PtcJumpTable.ClearIfNeeded(); using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate)) using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true)) @@ -545,11 +518,7 @@ namespace ARMeilleure.Translation.PTC } } - internal static void LoadTranslations( - ConcurrentDictionary funcs, - IMemoryManager memory, - JumpTable jumpTable, - EntryTable countTable) + internal static void LoadTranslations(Translator translator) { if (AreCarriersEmpty()) { @@ -580,7 +549,7 @@ namespace ARMeilleure.Translation.PTC continue; } - bool isEntryChanged = infoEntry.Hash != ComputeHash(memory, infoEntry.Address, infoEntry.GuestSize); + bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize); if (isEntryChanged || (!infoEntry.HighCq && PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq)) { @@ -594,8 +563,6 @@ namespace ARMeilleure.Translation.PTC if (isEntryChanged) { - PtcJumpTable.Clean(infoEntry.Address); - Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})"); } @@ -610,14 +577,16 @@ namespace ARMeilleure.Translation.PTC { RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount); - PatchCode(code, relocEntries, memory.PageTablePointer, jumpTable, countTable, out callCounter); + PatchCode(translator, code, relocEntries, out callCounter); } UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader); TranslatedFunction func = FastTranslate(code, callCounter, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq); - bool isAddressUnique = funcs.TryAdd(infoEntry.Address, func); + translator.RegisterFunction(infoEntry.Address, func); + + bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, func); Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique."); } @@ -630,12 +599,7 @@ namespace ARMeilleure.Translation.PTC throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end."); } - jumpTable.Initialize(PtcJumpTable, funcs); - - PtcJumpTable.WriteJumpTable(jumpTable, funcs); - PtcJumpTable.WriteDynamicTable(jumpTable); - - Logger.Info?.Print(LogClass.Ptc, $"{funcs.Count} translated functions loaded"); + Logger.Info?.Print(LogClass.Ptc, $"{translator.Functions.Count} translated functions loaded"); } private static int GetEntriesCount() @@ -676,56 +640,63 @@ namespace ARMeilleure.Translation.PTC for (int i = 0; i < relocEntriesCount; i++) { int position = relocsReader.ReadInt32(); - int index = relocsReader.ReadInt32(); + SymbolType type = (SymbolType)relocsReader.ReadByte(); + ulong value = relocsReader.ReadUInt64(); - relocEntries[i] = new RelocEntry(position, index); + relocEntries[i] = new RelocEntry(position, new Symbol(type, value)); } return relocEntries; } - private static void PatchCode( - Span code, - RelocEntry[] relocEntries, - IntPtr pageTablePointer, - JumpTable jumpTable, - EntryTable countTable, - out Counter callCounter) + private static void PatchCode(Translator translator, Span code, RelocEntry[] relocEntries, out Counter callCounter) { callCounter = null; foreach (RelocEntry relocEntry in relocEntries) { - ulong imm; + IntPtr? imm = null; + Symbol symbol = relocEntry.Symbol; - if (relocEntry.Index == PageTablePointerIndex) + if (symbol.Type == SymbolType.FunctionTable) { - imm = (ulong)pageTablePointer.ToInt64(); - } - else if (relocEntry.Index == JumpPointerIndex) - { - imm = (ulong)jumpTable.JumpPointer.ToInt64(); - } - else if (relocEntry.Index == DynamicPointerIndex) - { - imm = (ulong)jumpTable.DynamicPointer.ToInt64(); - } - else if (relocEntry.Index == CountTableIndex) - { - callCounter = new Counter(countTable); + ulong guestAddress = symbol.Value; - unsafe { imm = (ulong)Unsafe.AsPointer(ref callCounter.Value); } + if (translator.FunctionTable.IsValid(guestAddress)) + { + unsafe { imm = (IntPtr)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress)); } + } } - else if (Delegates.TryGetDelegateFuncPtrByIndex(relocEntry.Index, out IntPtr funcPtr)) + else if (symbol.Type == SymbolType.DelegateTable) { - imm = (ulong)funcPtr.ToInt64(); + int index = (int)symbol.Value; + + if (Delegates.TryGetDelegateFuncPtrByIndex(index, out IntPtr funcPtr)) + { + imm = funcPtr; + } } - else + else if (symbol == PageTableSymbol) + { + imm = translator.Memory.PageTablePointer; + } + else if (symbol == CountTableSymbol) + { + callCounter = new Counter(translator.CountTable); + + unsafe { imm = (IntPtr)Unsafe.AsPointer(ref callCounter.Value); } + } + else if (symbol == DispatchStubSymbol) + { + imm = translator.Stubs.DispatchStub; + } + + if (imm == null) { throw new Exception($"Unexpected reloc entry {relocEntry}."); } - BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), imm); + BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), (ulong)imm.Value); } } @@ -798,13 +769,9 @@ namespace ARMeilleure.Translation.PTC } } - internal static void MakeAndSaveTranslations( - ConcurrentDictionary funcs, - IMemoryManager memory, - JumpTable jumpTable, - EntryTable countTable) + internal static void MakeAndSaveTranslations(Translator translator) { - var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(funcs); + var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(translator.Functions); _translateCount = 0; _translateTotalCount = profiledFuncsToTranslate.Count; @@ -814,7 +781,6 @@ namespace ARMeilleure.Translation.PTC if (_translateTotalCount == 0 || degreeOfParallelism == 0) { ResetCarriersIfNeeded(); - PtcJumpTable.ClearIfNeeded(); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; @@ -844,19 +810,16 @@ namespace ARMeilleure.Translation.PTC Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address)); - TranslatedFunction func = Translator.Translate(memory, jumpTable, countTable, address, item.funcProfile.Mode, item.funcProfile.HighCq); + TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq); - bool isAddressUnique = funcs.TryAdd(address, func); + bool isAddressUnique = translator.Functions.TryAdd(address, func); Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique."); - if (func.HighCq) - { - jumpTable.RegisterFunction(address, func); - } - Interlocked.Increment(ref _translateCount); + translator.RegisterFunction(address, func); + if (State != PtcState.Enabled) { break; @@ -888,11 +851,6 @@ namespace ARMeilleure.Translation.PTC Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}"); - PtcJumpTable.Initialize(jumpTable); - - PtcJumpTable.ReadJumpTable(jumpTable); - PtcJumpTable.ReadDynamicTable(jumpTable); - Thread preSaveThread = new Thread(PreSave); preSaveThread.IsBackground = true; preSaveThread.Start(); @@ -1022,13 +980,11 @@ namespace ARMeilleure.Translation.PTC public long CodesLength; public int RelocsLength; public int UnwindInfosLength; - public int PtcJumpTableLength; public Hash128 InfosHash; public Hash128 CodesHash; public Hash128 RelocsHash; public Hash128 UnwindInfosHash; - public Hash128 PtcJumpTableHash; public Hash128 HeaderHash; diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs index 920469c73c..b28b2dc1f5 100644 --- a/ARMeilleure/Translation/PTC/PtcInfo.cs +++ b/ARMeilleure/Translation/PTC/PtcInfo.cs @@ -30,7 +30,8 @@ namespace ARMeilleure.Translation.PTC public void WriteRelocEntry(RelocEntry relocEntry) { _relocWriter.Write((int)relocEntry.Position); - _relocWriter.Write((int)relocEntry.Index); + _relocWriter.Write((byte)relocEntry.Symbol.Type); + _relocWriter.Write((ulong)relocEntry.Symbol.Value); RelocEntriesCount++; } diff --git a/ARMeilleure/Translation/PTC/PtcJumpTable.cs b/ARMeilleure/Translation/PTC/PtcJumpTable.cs deleted file mode 100644 index 67719623e4..0000000000 --- a/ARMeilleure/Translation/PTC/PtcJumpTable.cs +++ /dev/null @@ -1,350 +0,0 @@ -using ARMeilleure.Translation.Cache; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -using static ARMeilleure.Translation.PTC.PtcFormatter; - -namespace ARMeilleure.Translation.PTC -{ - class PtcJumpTable - { - [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 16*/)] - public struct TableEntry - { - public int EntryIndex; - public long GuestAddress; - public TAddress HostAddress; - - public TableEntry(int entryIndex, long guestAddress, TAddress hostAddress) - { - EntryIndex = entryIndex; - GuestAddress = guestAddress; - HostAddress = hostAddress; - } - } - - public enum DirectHostAddress : int - { - CallStub = 0, - TailCallStub = 1, - Host = 2 - } - - public enum IndirectHostAddress : int - { - CallStub = 0, - TailCallStub = 1 - } - - private readonly List> _jumpTable; - private readonly List> _dynamicTable; - - public List Targets { get; } - public Dictionary> Dependants { get; } - public Dictionary> Owners { get; } - - public PtcJumpTable() - { - _jumpTable = new List>(); - _dynamicTable = new List>(); - - Targets = new List(); - Dependants = new Dictionary>(); - Owners = new Dictionary>(); - } - - public PtcJumpTable( - List> jumpTable, List> dynamicTable, - List targets, Dictionary> dependants, Dictionary> owners) - { - _jumpTable = jumpTable; - _dynamicTable = dynamicTable; - - Targets = targets; - Dependants = dependants; - Owners = owners; - } - - public static PtcJumpTable Deserialize(Stream stream) - { - var jumpTable = DeserializeList>(stream); - var dynamicTable = DeserializeList>(stream); - - var targets = DeserializeList(stream); - var dependants = DeserializeDictionary>(stream, (stream) => DeserializeList(stream)); - var owners = DeserializeDictionary>(stream, (stream) => DeserializeList(stream)); - - return new PtcJumpTable(jumpTable, dynamicTable, targets, dependants, owners); - } - - public static int GetSerializeSize(PtcJumpTable ptcJumpTable) - { - int size = 0; - - size += GetSerializeSizeList(ptcJumpTable._jumpTable); - size += GetSerializeSizeList(ptcJumpTable._dynamicTable); - - size += GetSerializeSizeList(ptcJumpTable.Targets); - size += GetSerializeSizeDictionary(ptcJumpTable.Dependants, (list) => GetSerializeSizeList(list)); - size += GetSerializeSizeDictionary(ptcJumpTable.Owners, (list) => GetSerializeSizeList(list)); - - return size; - } - - public static void Serialize(Stream stream, PtcJumpTable ptcJumpTable) - { - SerializeList(stream, ptcJumpTable._jumpTable); - SerializeList(stream, ptcJumpTable._dynamicTable); - - SerializeList(stream, ptcJumpTable.Targets); - SerializeDictionary(stream, ptcJumpTable.Dependants, (stream, list) => SerializeList(stream, list)); - SerializeDictionary(stream, ptcJumpTable.Owners, (stream, list) => SerializeList(stream, list)); - } - - public void Initialize(JumpTable jumpTable) - { - Targets.Clear(); - - foreach (ulong guestAddress in jumpTable.Targets.Keys) - { - Targets.Add(guestAddress); - } - - Dependants.Clear(); - - foreach (var kv in jumpTable.Dependants) - { - Dependants.Add(kv.Key, new List(kv.Value)); - } - - Owners.Clear(); - - foreach (var kv in jumpTable.Owners) - { - Owners.Add(kv.Key, new List(kv.Value)); - } - } - - public void Clean(ulong guestAddress) - { - if (Owners.TryGetValue(guestAddress, out List entries)) - { - foreach (int entry in entries) - { - if ((entry & JumpTable.DynamicEntryTag) == 0) - { - int removed = _jumpTable.RemoveAll(tableEntry => tableEntry.EntryIndex == entry); - - Debug.Assert(removed == 1); - } - else - { - if (JumpTable.DynamicTableElems > 1) - { - throw new NotSupportedException(); - } - - int removed = _dynamicTable.RemoveAll(tableEntry => tableEntry.EntryIndex == (entry & ~JumpTable.DynamicEntryTag)); - - Debug.Assert(removed == 1); - } - } - } - - Targets.Remove(guestAddress); - Dependants.Remove(guestAddress); - Owners.Remove(guestAddress); - } - - public void ClearIfNeeded() - { - if (_jumpTable.Count == 0 && _dynamicTable.Count == 0 && - Targets.Count == 0 && Dependants.Count == 0 && Owners.Count == 0) - { - return; - } - - _jumpTable.Clear(); - _jumpTable.TrimExcess(); - _dynamicTable.Clear(); - _dynamicTable.TrimExcess(); - - Targets.Clear(); - Targets.TrimExcess(); - Dependants.Clear(); - Dependants.TrimExcess(); - Owners.Clear(); - Owners.TrimExcess(); - } - - public void WriteJumpTable(JumpTable jumpTable, ConcurrentDictionary funcs) - { - // Writes internal state to jump table in-memory, after PtcJumpTable was deserialized. - - foreach (var tableEntry in _jumpTable) - { - long guestAddress = tableEntry.GuestAddress; - DirectHostAddress directHostAddress = tableEntry.HostAddress; - - long hostAddress; - - if (directHostAddress == DirectHostAddress.CallStub) - { - hostAddress = DirectCallStubs.DirectCallStub(false).ToInt64(); - } - else if (directHostAddress == DirectHostAddress.TailCallStub) - { - hostAddress = DirectCallStubs.DirectCallStub(true).ToInt64(); - } - else if (directHostAddress == DirectHostAddress.Host) - { - if (funcs.TryGetValue((ulong)guestAddress, out TranslatedFunction func)) - { - hostAddress = func.FuncPtr.ToInt64(); - } - else - { - if (!PtcProfiler.ProfiledFuncs.TryGetValue((ulong)guestAddress, out var value) || !value.HighCq) - { - throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})"); - } - - hostAddress = 0L; - } - } - else - { - throw new InvalidOperationException(nameof(directHostAddress)); - } - - int entry = tableEntry.EntryIndex; - - jumpTable.Table.SetEntry(entry); - jumpTable.ExpandIfNeededJumpTable(entry); - - IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry); - - Marshal.WriteInt64(addr, 0, guestAddress); - Marshal.WriteInt64(addr, 8, hostAddress); - } - } - - public void WriteDynamicTable(JumpTable jumpTable) - { - // Writes internal state to jump table in-memory, after PtcJumpTable was deserialized. - - if (JumpTable.DynamicTableElems > 1) - { - throw new NotSupportedException(); - } - - foreach (var tableEntry in _dynamicTable) - { - long guestAddress = tableEntry.GuestAddress; - IndirectHostAddress indirectHostAddress = tableEntry.HostAddress; - - long hostAddress; - - if (indirectHostAddress == IndirectHostAddress.CallStub) - { - hostAddress = DirectCallStubs.IndirectCallStub(false).ToInt64(); - } - else if (indirectHostAddress == IndirectHostAddress.TailCallStub) - { - hostAddress = DirectCallStubs.IndirectCallStub(true).ToInt64(); - } - else - { - throw new InvalidOperationException(nameof(indirectHostAddress)); - } - - int entry = tableEntry.EntryIndex; - - jumpTable.DynTable.SetEntry(entry); - jumpTable.ExpandIfNeededDynamicTable(entry); - - IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry); - - Marshal.WriteInt64(addr, 0, guestAddress); - Marshal.WriteInt64(addr, 8, hostAddress); - } - } - - public void ReadJumpTable(JumpTable jumpTable) - { - // Reads in-memory jump table state and store internally for PtcJumpTable serialization. - - _jumpTable.Clear(); - - IEnumerable entries = jumpTable.Table.GetEntries(); - - foreach (int entry in entries) - { - IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry); - - long guestAddress = Marshal.ReadInt64(addr, 0); - long hostAddress = Marshal.ReadInt64(addr, 8); - - DirectHostAddress directHostAddress; - - if (hostAddress == DirectCallStubs.DirectCallStub(false).ToInt64()) - { - directHostAddress = DirectHostAddress.CallStub; - } - else if (hostAddress == DirectCallStubs.DirectCallStub(true).ToInt64()) - { - directHostAddress = DirectHostAddress.TailCallStub; - } - else - { - directHostAddress = DirectHostAddress.Host; - } - - _jumpTable.Add(new TableEntry(entry, guestAddress, directHostAddress)); - } - } - - public void ReadDynamicTable(JumpTable jumpTable) - { - // Reads in-memory jump table state and store internally for PtcJumpTable serialization. - - if (JumpTable.DynamicTableElems > 1) - { - throw new NotSupportedException(); - } - - _dynamicTable.Clear(); - - IEnumerable entries = jumpTable.DynTable.GetEntries(); - - foreach (int entry in entries) - { - IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry); - - long guestAddress = Marshal.ReadInt64(addr, 0); - long hostAddress = Marshal.ReadInt64(addr, 8); - - IndirectHostAddress indirectHostAddress; - - if (hostAddress == DirectCallStubs.IndirectCallStub(false).ToInt64()) - { - indirectHostAddress = IndirectHostAddress.CallStub; - } - else if (hostAddress == DirectCallStubs.IndirectCallStub(true).ToInt64()) - { - indirectHostAddress = IndirectHostAddress.TailCallStub; - } - else - { - throw new InvalidOperationException($"({nameof(hostAddress)} = 0x{hostAddress:X16})"); - } - - _dynamicTable.Add(new TableEntry(entry, guestAddress, indirectHostAddress)); - } - } - } -} \ No newline at end of file diff --git a/ARMeilleure/Translation/PTC/RelocEntry.cs b/ARMeilleure/Translation/PTC/RelocEntry.cs index 52d73db8b4..545612d019 100644 --- a/ARMeilleure/Translation/PTC/RelocEntry.cs +++ b/ARMeilleure/Translation/PTC/RelocEntry.cs @@ -2,20 +2,20 @@ namespace ARMeilleure.Translation.PTC { struct RelocEntry { - public const int Stride = 8; // Bytes. + public const int Stride = 13; // Bytes. public int Position; - public int Index; + public Symbol Symbol; - public RelocEntry(int position, int index) + public RelocEntry(int position, Symbol symbol) { Position = position; - Index = index; + Symbol = symbol; } public override string ToString() { - return $"({nameof(Position)} = {Position}, {nameof(Index)} = {Index})"; + return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})"; } } } \ No newline at end of file diff --git a/ARMeilleure/Translation/PTC/Symbol.cs b/ARMeilleure/Translation/PTC/Symbol.cs new file mode 100644 index 0000000000..f9d67742bc --- /dev/null +++ b/ARMeilleure/Translation/PTC/Symbol.cs @@ -0,0 +1,100 @@ +using System; + +namespace ARMeilleure.Translation.PTC +{ + /// + /// Represents a symbol. + /// + struct Symbol + { + private readonly ulong _value; + + /// + /// Gets the of the . + /// + public SymbolType Type { get; } + + /// + /// Gets the value of the . + /// + /// is + public ulong Value + { + get + { + if (Type == SymbolType.None) + { + ThrowSymbolNone(); + } + + return _value; + } + } + + /// + /// Initializes a new instance of the structure with the specified and value. + /// + /// Type of symbol + /// Value of symbol + public Symbol(SymbolType type, ulong value) + { + (Type, _value) = (type, value); + } + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// if equal; otherwise + public static bool operator ==(Symbol a, Symbol b) + { + return a.Equals(b); + } + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// if not equal; otherwise + /// + public static bool operator !=(Symbol a, Symbol b) + { + return !(a == b); + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// if equal; otherwise + public bool Equals(Symbol other) + { + return other.Type == Type && other._value == _value; + } + + /// + public override bool Equals(object obj) + { + return obj is Symbol sym && Equals(sym); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, _value); + } + + /// + public override string ToString() + { + return $"{Type}:{_value}"; + } + + private static void ThrowSymbolNone() + { + throw new InvalidOperationException("Symbol refers to nothing."); + } + } +} diff --git a/ARMeilleure/Translation/PTC/SymbolType.cs b/ARMeilleure/Translation/PTC/SymbolType.cs new file mode 100644 index 0000000000..cd7b6c1c6a --- /dev/null +++ b/ARMeilleure/Translation/PTC/SymbolType.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Translation.PTC +{ + /// + /// Types of . + /// + enum SymbolType : byte + { + /// + /// Refers to nothing, i.e no symbol. + /// + None, + + /// + /// Refers to an entry in . + /// + DelegateTable, + + /// + /// Refers to an entry in . + /// + FunctionTable, + + /// + /// Refers to a special symbol which is handled by . + /// + Special + } +} diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs index eeeb517f64..2110a4e35f 100644 --- a/ARMeilleure/Translation/Translator.cs +++ b/ARMeilleure/Translation/Translator.cs @@ -24,12 +24,27 @@ namespace ARMeilleure.Translation { public class Translator { - private const int CountTableCapacity = 4 * 1024 * 1024; + 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 readonly IJitMemoryAllocator _allocator; - private readonly IMemoryManager _memory; - - private readonly ConcurrentDictionary _funcs; private readonly ConcurrentQueue> _oldFuncs; private readonly ConcurrentDictionary _backgroundSet; @@ -37,21 +52,22 @@ namespace ARMeilleure.Translation private readonly AutoResetEvent _backgroundTranslatorEvent; private readonly ReaderWriterLock _backgroundTranslatorLock; - private JumpTable _jumpTable; - internal JumpTable JumpTable => _jumpTable; + internal ConcurrentDictionary Functions { get; } + internal AddressTable FunctionTable { get; } internal EntryTable CountTable { get; } + internal TranslatorStubs Stubs { get; } + internal IMemoryManager Memory { get; } private volatile int _threadCount; // FIXME: Remove this once the init logic of the emulator will be redone. public static readonly ManualResetEvent IsReadyForTranslation = new(false); - public Translator(IJitMemoryAllocator allocator, IMemoryManager memory) + public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) { _allocator = allocator; - _memory = memory; + Memory = memory; - _funcs = new ConcurrentDictionary(); _oldFuncs = new ConcurrentQueue>(); _backgroundSet = new ConcurrentDictionary(); @@ -59,11 +75,14 @@ namespace ARMeilleure.Translation _backgroundTranslatorEvent = new AutoResetEvent(false); _backgroundTranslatorLock = new ReaderWriterLock(); - CountTable = new EntryTable(); - JitCache.Initialize(allocator); - DirectCallStubs.InitializeStubs(); + CountTable = new EntryTable(); + Functions = new ConcurrentDictionary(); + FunctionTable = new AddressTable(for64Bits ? Levels64Bit : Levels32Bit); + Stubs = new TranslatorStubs(this); + + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; if (memory.Type.IsHostMapped()) { @@ -80,27 +99,21 @@ namespace ARMeilleure.Translation if (_backgroundStack.TryPop(out RejitRequest request) && _backgroundSet.TryRemove(request.Address, out _)) { - TranslatedFunction func = Translate( - _memory, - _jumpTable, - CountTable, - request.Address, - request.Mode, - highCq: true); + TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true); - _funcs.AddOrUpdate(request.Address, func, (key, oldFunc) => + Functions.AddOrUpdate(request.Address, func, (key, oldFunc) => { EnqueueForDeletion(key, oldFunc); return func; }); - _jumpTable.RegisterFunction(request.Address, func); - if (PtcProfiler.Enabled) { PtcProfiler.UpdateEntry(request.Address, request.Mode, highCq: true); } + RegisterFunction(request.Address, func); + _backgroundTranslatorLock.ReleaseReaderLock(); } else @@ -120,14 +133,11 @@ namespace ARMeilleure.Translation { IsReadyForTranslation.WaitOne(); - Debug.Assert(_jumpTable == null); - _jumpTable = new JumpTable(_allocator); - if (Ptc.State == PtcState.Enabled) { - Debug.Assert(_funcs.Count == 0); - Ptc.LoadTranslations(_funcs, _memory, _jumpTable, CountTable); - Ptc.MakeAndSaveTranslations(_funcs, _memory, _jumpTable, CountTable); + Debug.Assert(Functions.Count == 0); + Ptc.LoadTranslations(this); + Ptc.MakeAndSaveTranslations(this); } PtcProfiler.Start(); @@ -160,13 +170,20 @@ namespace ARMeilleure.Translation Statistics.InitializeTimer(); - NativeInterface.RegisterThread(context, _memory, this); + NativeInterface.RegisterThread(context, Memory, this); - do + if (Optimizations.UseUnmanagedDispatchLoop) { - address = ExecuteSingle(context, address); + Stubs.DispatchLoop(context.NativeContextPtr, address); + } + else + { + do + { + address = ExecuteSingle(context, address); + } + while (context.Running && address != 0); } - while (context.Running && address != 0); NativeInterface.UnregisterThread(); @@ -178,9 +195,8 @@ namespace ARMeilleure.Translation DisposePools(); - _jumpTable.Dispose(); - _jumpTable = null; - + Stubs.Dispose(); + FunctionTable.Dispose(); CountTable.Dispose(); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; @@ -202,40 +218,51 @@ namespace ARMeilleure.Translation internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) { - if (!_funcs.TryGetValue(address, out TranslatedFunction func)) + if (!Functions.TryGetValue(address, out TranslatedFunction func)) { - func = Translate(_memory, _jumpTable, CountTable, address, mode, highCq: false); + func = Translate(address, mode, highCq: false); - TranslatedFunction getFunc = _funcs.GetOrAdd(address, func); + TranslatedFunction oldFunc = Functions.GetOrAdd(address, func); - if (getFunc != func) + if (oldFunc != func) { JitCache.Unmap(func.FuncPtr); - func = getFunc; + func = oldFunc; } if (PtcProfiler.Enabled) { PtcProfiler.AddEntry(address, mode, highCq: false); } + + RegisterFunction(address, func); } return func; } - internal static TranslatedFunction Translate( - IMemoryManager memory, - JumpTable jumpTable, - EntryTable countTable, - ulong address, - ExecutionMode mode, - bool highCq) + internal void RegisterFunction(ulong guestAddress, TranslatedFunction func) { - var context = new ArmEmitterContext(memory, jumpTable, countTable, address, highCq, Aarch32Mode.User); + if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq)) + { + Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPtr); + } + } + + internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq) + { + var context = new ArmEmitterContext( + Memory, + CountTable, + FunctionTable, + Stubs, + address, + highCq, + mode: Aarch32Mode.User); Logger.StartPass(PassName.Decoding); - Block[] blocks = Decoder.Decode(memory, address, mode, highCq, singleBlock: false); + Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleBlock: false); Logger.EndPass(PassName.Decoding); @@ -268,7 +295,7 @@ namespace ARMeilleure.Translation GuestFunction func; - if (Ptc.State == PtcState.Disabled) + if (!context.HasPtc) { func = Compiler.Compile(cfg, argTypes, OperandType.I64, options); @@ -282,7 +309,7 @@ namespace ARMeilleure.Translation ResetPool(highCq ? 1 : 0); - Hash128 hash = Ptc.ComputeHash(memory, address, funcSize); + Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize); Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, hash, highCq, ptcInfo); } @@ -360,7 +387,11 @@ namespace ARMeilleure.Translation if (block.Exit) { - InstEmitFlowHelper.EmitTailContinue(context, Const(block.Address)); + // Left option here as it may be useful if we need to return to managed rather than tail call in + // future. (eg. for debug) + bool useReturns = false; + + InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); } else { @@ -416,7 +447,10 @@ namespace ARMeilleure.Translation Operand lblEnd = Label(); - Operand address = Const(ref counter.Value, Ptc.CountTableIndex); + Operand address = !context.HasPtc ? + Const(ref counter.Value) : + Const(ref counter.Value, Ptc.CountTableSymbol); + Operand curCount = context.Load(OperandType.I32, address); Operand count = context.Add(curCount, Const(1)); context.Store(address, count); @@ -477,14 +511,14 @@ namespace ARMeilleure.Translation // Ensure no attempt will be made to compile new functions due to rejit. ClearRejitQueue(allowRequeue: false); - foreach (var func in _funcs.Values) + foreach (var func in Functions.Values) { JitCache.Unmap(func.FuncPtr); func.CallCounter?.Dispose(); } - _funcs.Clear(); + Functions.Clear(); while (_oldFuncs.TryDequeue(out var kv)) { @@ -502,7 +536,7 @@ namespace ARMeilleure.Translation { while (_backgroundStack.TryPop(out var request)) { - if (_funcs.TryGetValue(request.Address, out var func) && func.CallCounter != null) + if (Functions.TryGetValue(request.Address, out var func) && func.CallCounter != null) { Volatile.Write(ref func.CallCounter.Value, 0); } diff --git a/ARMeilleure/Translation/TranslatorStubs.cs b/ARMeilleure/Translation/TranslatorStubs.cs new file mode 100644 index 0000000000..aff2ac7e4c --- /dev/null +++ b/ARMeilleure/Translation/TranslatorStubs.cs @@ -0,0 +1,248 @@ +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation.Cache; +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.OperandHelper; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a stub manager. + /// + class TranslatorStubs : IDisposable + { + private static readonly Lazy _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); + + private bool _disposed; + + private readonly Translator _translator; + private readonly Lazy _dispatchStub; + private readonly Lazy _dispatchLoop; + + /// + /// Gets the dispatch stub. + /// + /// instance was disposed + public IntPtr DispatchStub + { + get + { + if (_disposed) + { + throw new ObjectDisposedException(null); + } + + return _dispatchStub.Value; + } + } + + /// + /// Gets the slow dispatch stub. + /// + /// instance was disposed + public IntPtr SlowDispatchStub + { + get + { + if (_disposed) + { + throw new ObjectDisposedException(null); + } + + return _slowDispatchStub.Value; + } + } + + /// + /// Gets the dispatch loop function. + /// + /// instance was disposed + public DispatcherFunction DispatchLoop + { + get + { + if (_disposed) + { + throw new ObjectDisposedException(null); + } + + return _dispatchLoop.Value; + } + } + + /// + /// Initializes a new instance of the class with the specified + /// instance. + /// + /// instance to use + /// is null + public TranslatorStubs(Translator translator) + { + _translator = translator ?? throw new ArgumentNullException(nameof(translator)); + _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); + _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (_dispatchStub.IsValueCreated) + { + JitCache.Unmap(_dispatchStub.Value); + } + + if (_dispatchLoop.IsValueCreated) + { + JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~TranslatorStubs() + { + Dispose(false); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateDispatchStub() + { + var context = new EmitterContext(); + + Operand lblFallback = Label(); + Operand lblEnd = Label(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + // Check if guest address is within range of the AddressTable. + Operand masked = context.BitwiseAnd(guestAddress, Const(~_translator.FunctionTable.Mask)); + context.BranchIfTrue(lblFallback, masked); + + Operand index = null; + Operand page = Const((long)_translator.FunctionTable.Base); + + for (int i = 0; i < _translator.FunctionTable.Levels.Length; i++) + { + ref var level = ref _translator.FunctionTable.Levels[i]; + + // level.Mask is not used directly because it is more often bigger than 32-bits, so it will not + // be encoded as an immediate on x86's bitwise and operation. + Operand mask = Const(level.Mask >> level.Index); + + index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask); + + if (i < _translator.FunctionTable.Levels.Length - 1) + { + page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3)))); + context.BranchIfFalse(lblFallback, page); + } + } + + Operand hostAddress; + Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3))); + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + context.Tailcall(hostAddress, nativeContext); + + context.MarkLabel(lblFallback); + hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Generates a . + /// + /// Generated + private static IntPtr GenerateSlowDispatchStub() + { + var context = new EmitterContext(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + MethodInfo getFuncAddress = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)); + Operand hostAddress = context.Call(getFuncAddress, guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Generates a function. + /// + /// function + private DispatcherFunction GenerateDispatchLoop() + { + var context = new EmitterContext(); + + Operand beginLbl = Label(); + Operand endLbl = Label(); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Copy( + context.AllocateLocal(OperandType.I64), + context.LoadArgument(OperandType.I64, 1)); + + Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset())); + Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + + context.MarkLabel(beginLbl); + context.Store(dispatchAddress, guestAddress); + context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext)); + context.BranchIfFalse(endLbl, guestAddress); + context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress)); + context.Branch(beginLbl); + + context.MarkLabel(endLbl); + context.Return(); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.None; + var argTypes = new[] { OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq); + } + } +} diff --git a/Ryujinx.Cpu/CpuContext.cs b/Ryujinx.Cpu/CpuContext.cs index 407353fdf2..3e422546a3 100644 --- a/Ryujinx.Cpu/CpuContext.cs +++ b/Ryujinx.Cpu/CpuContext.cs @@ -8,9 +8,9 @@ namespace Ryujinx.Cpu { private readonly Translator _translator; - public CpuContext(IMemoryManager memory) + public CpuContext(IMemoryManager memory, bool for64Bit) { - _translator = new Translator(new JitMemoryAllocator(), memory); + _translator = new Translator(new JitMemoryAllocator(), memory, for64Bit); memory.UnmapEvent += UnmapHandler; } diff --git a/Ryujinx.HLE/HOS/ArmProcessContext.cs b/Ryujinx.HLE/HOS/ArmProcessContext.cs index ae5fe601f2..457d121813 100644 --- a/Ryujinx.HLE/HOS/ArmProcessContext.cs +++ b/Ryujinx.HLE/HOS/ArmProcessContext.cs @@ -3,7 +3,6 @@ using ARMeilleure.State; using Ryujinx.Cpu; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Memory; -using System; namespace Ryujinx.HLE.HOS { @@ -14,7 +13,7 @@ namespace Ryujinx.HLE.HOS public IVirtualMemoryManager AddressSpace => _memoryManager; - public ArmProcessContext(T memoryManager) + public ArmProcessContext(T memoryManager, bool for64Bit) { if (memoryManager is IRefCounted rc) { @@ -22,7 +21,7 @@ namespace Ryujinx.HLE.HOS } _memoryManager = memoryManager; - _cpuContext = new CpuContext(memoryManager); + _cpuContext = new CpuContext(memoryManager, for64Bit); } public void Execute(ExecutionContext context, ulong codeAddress) diff --git a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 14617cf222..04d06e1f95 100644 --- a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -9,19 +9,19 @@ namespace Ryujinx.HLE.HOS { class ArmProcessContextFactory : IProcessContextFactory { - public IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler) + public IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) { MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode; switch (mode) { case MemoryManagerMode.SoftwarePageTable: - return new ArmProcessContext(new MemoryManager(addressSpaceSize, invalidAccessHandler)); + return new ArmProcessContext(new MemoryManager(addressSpaceSize, invalidAccessHandler), for64Bit); case MemoryManagerMode.HostMapped: case MemoryManagerMode.HostMappedUnsafe: bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe; - return new ArmProcessContext(new MemoryManagerHostMapped(addressSpaceSize, unsafeMode, invalidAccessHandler)); + return new ArmProcessContext(new MemoryManagerHostMapped(addressSpaceSize, unsafeMode, invalidAccessHandler), for64Bit); default: throw new ArgumentOutOfRangeException(); diff --git a/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs b/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs index e9fbf618a2..fbd6c139d3 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs @@ -1,10 +1,9 @@ -using Ryujinx.Cpu; -using Ryujinx.Memory; +using Ryujinx.Memory; namespace Ryujinx.HLE.HOS.Kernel.Process { interface IProcessContextFactory { - IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler); + IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit); } } diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index f2ba675f90..90cd01f01c 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1049,7 +1049,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process _ => 39 }; - Context = _contextFactory.Create(KernelContext, 1UL << addrSpaceBits, InvalidAccessHandler); + bool for64Bit = flags.HasFlag(ProcessCreationFlags.Is64Bit); + + Context = _contextFactory.Create(KernelContext, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit); // TODO: This should eventually be removed. // The GPU shouldn't depend on the CPU memory manager at all. diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs index 29860b3bb7..5920fe44d5 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs @@ -1,11 +1,10 @@ -using Ryujinx.Cpu; -using Ryujinx.Memory; +using Ryujinx.Memory; namespace Ryujinx.HLE.HOS.Kernel.Process { class ProcessContextFactory : IProcessContextFactory { - public IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler) + public IProcessContext Create(KernelContext context, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) { return new ProcessContext(new AddressSpaceManager(addressSpaceSize)); } diff --git a/Ryujinx.Tests/Cpu/CpuTest.cs b/Ryujinx.Tests/Cpu/CpuTest.cs index e823ea17b0..26f0d286cd 100644 --- a/Ryujinx.Tests/Cpu/CpuTest.cs +++ b/Ryujinx.Tests/Cpu/CpuTest.cs @@ -1,3 +1,4 @@ +using ARMeilleure; using ARMeilleure.State; using ARMeilleure.Translation; using NUnit.Framework; @@ -60,7 +61,12 @@ namespace Ryujinx.Tests.Cpu _context = CpuContext.CreateExecutionContext(); Translator.IsReadyForTranslation.Set(); - _cpuContext = new CpuContext(_memory); + _cpuContext = new CpuContext(_memory, for64Bit: true); + + // Prevent registering LCQ functions in the FunctionTable to avoid initializing and populating the table, + // which improves test durations. + Optimizations.AllowLcqInFunctionTable = false; + Optimizations.UseUnmanagedDispatchLoop = false; if (_unicornAvailable) { diff --git a/Ryujinx.Tests/Cpu/CpuTest32.cs b/Ryujinx.Tests/Cpu/CpuTest32.cs index 380c86e85d..5d24af39c3 100644 --- a/Ryujinx.Tests/Cpu/CpuTest32.cs +++ b/Ryujinx.Tests/Cpu/CpuTest32.cs @@ -1,4 +1,5 @@ -using ARMeilleure.State; +using ARMeilleure; +using ARMeilleure.State; using ARMeilleure.Translation; using NUnit.Framework; using Ryujinx.Cpu; @@ -56,7 +57,12 @@ namespace Ryujinx.Tests.Cpu _context.IsAarch32 = true; Translator.IsReadyForTranslation.Set(); - _cpuContext = new CpuContext(_memory); + _cpuContext = new CpuContext(_memory, for64Bit: false); + + // Prevent registering LCQ functions in the FunctionTable to avoid initializing and populating the table, + // which improves test durations. + Optimizations.AllowLcqInFunctionTable = false; + Optimizations.UseUnmanagedDispatchLoop = false; if (_unicornAvailable) {