From 156edcacb26cabb49fba90b1505d91494360addb Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 2 Sep 2025 21:37:56 +0100 Subject: [PATCH 1/2] Dispatcher: Move the EC entrypoint check prior to the L1 lookup Profiling shows this is by far the hottest path, as every DX call will hit this (as arm64ec functions in a vtable). L2 hits are much rarer and L1 almost never hits since that is looked up inline in most cases. --- .../Interface/Core/Dispatcher/Dispatcher.cpp | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp b/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp index ab251ccf0..63af2dc3d 100644 --- a/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp +++ b/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp @@ -149,6 +149,30 @@ void Dispatcher::EmitDispatcher() { // Load in our RIP ldr(RipReg, STATE_PTR(CpuStateFrame, State.rip)); +#ifdef _M_ARM_64EC + // Clobbers TMP1/2 + // Check the EC code bitmap incase we need to exit the JIT to call into native code. + ARMEmitter::ForwardLabel l_NotECCode; + ldr(TMP1, ARMEmitter::XReg::x18, TEB_PEB_OFFSET); + ldr(TMP1, TMP1, PEB_EC_CODE_BITMAP_OFFSET); + + lsr(ARMEmitter::Size::i64Bit, TMP2, RipReg, 15); + and_(ARMEmitter::Size::i64Bit, TMP2, TMP2, 0x1fffffffffff8); + ldr(TMP1, TMP1, TMP2, ARMEmitter::ExtendedType::LSL_64, 0); + lsr(ARMEmitter::Size::i64Bit, TMP2, RipReg, 12); + lsrv(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP2); + tbz(TMP1, 0, &l_NotECCode); + + str(REG_CALLRET_SP, STATE_PTR(CpuStateFrame, State.callret_sp)); + + add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, StaticRegisters[X86State::REG_RSP], 0); + mov(EC_CALL_CHECKER_PC_REG, RipReg); + ldr(TMP2, STATE_PTR(CpuStateFrame, Pointers.Common.ExitFunctionEC)); + br(TMP2); + + Bind(&l_NotECCode); +#endif + ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); cbnz(ARMEmitter::Size::i32Bit, TMP1, &CompileSingleStep); @@ -293,40 +317,10 @@ void Dispatcher::EmitDispatcher() { br(TMP1); } -#ifdef _M_ARM_64EC - // Clobbers TMP1/2 - auto EmitECExitCheck = [&]() { - // Check the EC code bitmap incase we need to exit the JIT to call into native code. - ARMEmitter::ForwardLabel l_NotECCode; - ldr(TMP1, ARMEmitter::XReg::x18, TEB_PEB_OFFSET); - ldr(TMP1, TMP1, PEB_EC_CODE_BITMAP_OFFSET); - - lsr(ARMEmitter::Size::i64Bit, TMP2, RipReg, 15); - and_(ARMEmitter::Size::i64Bit, TMP2, TMP2, 0x1fffffffffff8); - ldr(TMP1, TMP1, TMP2, ARMEmitter::ExtendedType::LSL_64, 0); - lsr(ARMEmitter::Size::i64Bit, TMP2, RipReg, 12); - lsrv(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP2); - tbz(TMP1, 0, &l_NotECCode); - - str(REG_CALLRET_SP, STATE_PTR(CpuStateFrame, State.callret_sp)); - - add(ARMEmitter::Size::i64Bit, ARMEmitter::Reg::rsp, StaticRegisters[X86State::REG_RSP], 0); - mov(EC_CALL_CHECKER_PC_REG, RipReg); - ldr(TMP2, STATE_PTR(CpuStateFrame, Pointers.Common.ExitFunctionEC)); - br(TMP2); - - Bind(&l_NotECCode); - }; -#endif - // Need to create the block { Bind(&NoBlock); -#ifdef _M_ARM_64EC - EmitECExitCheck(); -#endif - EmitSignalGuardedRegion([&]() { SpillStaticRegs(TMP1); @@ -361,10 +355,6 @@ void Dispatcher::EmitDispatcher() { { Bind(&CompileSingleStep); -#ifdef _M_ARM_64EC - EmitECExitCheck(); -#endif - EmitSignalGuardedRegion([&]() { SpillStaticRegs(TMP1); From 06466bac0e9b77e83462e53c5ec81a7b2b79c148 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Wed, 3 Sep 2025 18:26:23 +0100 Subject: [PATCH 2/2] Dispatcher: Drop the L1 lookup This is almost never hit outside of exceptional cases like signals/SMC, for which other overheads dominate. --- .../Interface/Core/Dispatcher/Dispatcher.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp b/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp index 63af2dc3d..14983f410 100644 --- a/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp +++ b/FEXCore/Source/Interface/Core/Dispatcher/Dispatcher.cpp @@ -140,8 +140,6 @@ void Dispatcher::EmitDispatcher() { // We want to ensure that we are 16 byte aligned at the top of this loop Align16B(); - ARMEmitter::BiDirectionalLabel FullLookup {}; - ARMEmitter::BiDirectionalLabel CallBlock {}; Bind(&LoopTop); AbsoluteLoopTopAddress = GetCursorAddress(); @@ -176,20 +174,6 @@ void Dispatcher::EmitDispatcher() { ldrb(TMP1, STATE_PTR(CpuStateFrame, State.flags[X86State::RFLAG_TF_RAW_LOC])); cbnz(ARMEmitter::Size::i32Bit, TMP1, &CompileSingleStep); - // L1 Cache - ldr(TMP1, STATE_PTR(CpuStateFrame, Pointers.Common.L1Pointer)); - - and_(ARMEmitter::Size::i64Bit, TMP4, RipReg.R(), LookupCache::L1_ENTRIES_MASK); - add(ARMEmitter::Size::i64Bit, TMP1, TMP1, TMP4, ARMEmitter::ShiftType::LSL, 4); - ldp(TMP4, TMP1, TMP1, 0); - sub(ARMEmitter::Size::i64Bit, TMP1, TMP1, RipReg); - cbnz(ARMEmitter::Size::i64Bit, TMP1, &FullLookup); - - br(TMP4); - - // L1C check failed, do a full lookup - Bind(&FullLookup); - // This is the block cache lookup routine // It matches what is going on it LookupCache.h::FindBlock ldr(TMP1, STATE_PTR(CpuStateFrame, Pointers.Common.L2Pointer));