From 2cc0a867a97ff179e0798b81da8b199d2bf34908 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 16:24:54 +0000 Subject: [PATCH 1/3] ARM64EC: Set the InSimulation TEB flag when inside the JIT This is used by the kernel to determine whether to perform cooperative suspend. --- Source/Windows/ARM64EC/Module.S | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Windows/ARM64EC/Module.S b/Source/Windows/ARM64EC/Module.S index e68d19730..750f08ce0 100644 --- a/Source/Windows/ARM64EC/Module.S +++ b/Source/Windows/ARM64EC/Module.S @@ -34,6 +34,8 @@ ExitToX64: enter_jit: ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo + mov w16, #1 + strb w16, [x17, #0x0] // ChpeV2CpuAreaInfo->InSimulation ldr x16, [x17, #0x40] // ChpeV2CpuAreaInfo->EmulatorData[2] - DispatcherLoopTopEnterEC br x16 // DispatcherLoopTopEnterEC(RIP:x9, CPUArea:x17) @@ -57,6 +59,8 @@ ExitFunctionEC: mrs x17, fpcr and x17, x17, #~6 // NEP + AH msr fpcr, x17 + ldr x17, [x18, #0x1788] // TEB->ChpeV2CpuAreaInfo + strb wzr, [x17, #0x0] // ChpeV2CpuAreaInfo->InSimulation // Either return to an exit thunk (return to ARM64EC function) or call an entry thunk (call to ARM64EC function). // It is assumed that a 'blr x16' instruction is only ever used to call into x86 code from an exit thunk, and that all From a469047e7a55beb3942cc42e3e9e6fa5a59f474b Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 23 Jul 2024 15:49:25 +0000 Subject: [PATCH 2/3] FEXCore: Support cooperative suspend on ARM64EC ARM64EC doesn't have an explicit callback for suspend, instead this task is delegated to the kernel setting a bool at a JIT-defined memory location, as such the faulting based approach as was done for WOW64 cannot be used. --- FEXCore/Source/Interface/Core/Core.cpp | 4 ++-- FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp | 10 ++++++++++ FEXCore/include/FEXCore/Core/CoreState.h | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 1ebcea8f7..d57af1b5e 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -343,8 +343,8 @@ bool ContextImpl::InitCore() { #ifndef _WIN32 ThunkHandler = FEXCore::ThunkHandler::Create(); -#else - // WIN32 always needs the interrupt fault check to be enabled. +#elif !defined(_M_ARM64EC) + // WOW64 always needs the interrupt fault check to be enabled. Config.NeedsPendingInterruptFaultCheck = true; #endif diff --git a/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp b/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp index d2c9e0591..087fd5b33 100644 --- a/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp +++ b/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp @@ -724,6 +724,16 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, const FEXCore offsetof(FEXCore::Core::InternalThreadState, InterruptFaultPage) - offsetof(FEXCore::Core::InternalThreadState, BaseFrameState)); } +#ifdef _M_ARM_64EC + static constexpr uint16_t SuspendMagic {0xCAFE}; + + ldr(TMP2.W(), STATE_PTR(CpuStateFrame, SuspendDoorbell)); + ARMEmitter::SingleUseForwardLabel l_NoSuspend; + cbz(ARMEmitter::Size::i32Bit, TMP2, &l_NoSuspend); + brk(SuspendMagic); + Bind(&l_NoSuspend); +#endif + SpillSlots = RAData->SpillSlots(); if (SpillSlots) { diff --git a/FEXCore/include/FEXCore/Core/CoreState.h b/FEXCore/include/FEXCore/Core/CoreState.h index 380117656..89c10c0b5 100644 --- a/FEXCore/include/FEXCore/Core/CoreState.h +++ b/FEXCore/include/FEXCore/Core/CoreState.h @@ -341,6 +341,11 @@ struct CpuStateFrame { InternalThreadState* Thread; +#ifdef _M_ARM_64EC + // Set by the kernel on ARM64EC whenever the JIT should cooperatively suspend running guest code. + uint32_t SuspendDoorbell {}; +#endif + // Pointers that the JIT needs to load to remove relocations JITPointers Pointers; }; From ea4004ec9dee5dc44cc5e542396719f827848209 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Wed, 24 Jul 2024 13:22:10 +0000 Subject: [PATCH 3/3] ARM64EC: Support cooperative suspend When a suspend request occurs on a running thread, Windows gives the JIT ~10s to pause and NtContinue with the x86 context. Resumption is then done through BeginSimulation. --- Source/Windows/ARM64EC/Module.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index e9d58e6b1..6f95a8b32 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -577,6 +577,15 @@ bool ResetToConsistentStateImpl(EXCEPTION_RECORD* Exception, CONTEXT* GuestConte return true; } + // The JIT (in CompileBlock) emits code to check the suspend doorbell at the start of every block, and run the following instruction if it is set: + static constexpr uint32_t SuspendTrapMagic {0xD4395FC0}; // brk #0xCAFE + if (Exception->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION && *reinterpret_cast(NativeContext->Pc) == SuspendTrapMagic) { + *NativeContext = Exception::ReconstructPackedECContext(*NativeContext); + LogMan::Msg::DFmt("Suspending: RIP: {:X} SP: {:X}", NativeContext->Pc, NativeContext->Sp); + CPUArea.Area->InSimulation = 0; + *CPUArea.Area->SuspendDoorbell = 0; + return true; + } if (IsEmulatorStackAddress(reinterpret_cast(__builtin_frame_address(0)))) { Exception::RethrowGuestException(*Exception, *NativeContext); @@ -736,6 +745,7 @@ NTSTATUS ThreadInit() { } CPUArea.ThreadState() = Thread; + CPUArea.Area->SuspendDoorbell = reinterpret_cast(&Thread->CurrentFrame->SuspendDoorbell); return STATUS_SUCCESS; }