From d67b1645c979acd621143079b72cbeb71ffc9ea7 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 14 Jul 2025 19:19:40 +0100 Subject: [PATCH] FEXCore: Hold a frontend allocation for the call-ret stack This can't be handled fully within FEXCore due to the frontend-specific handling of guard pages. Frontends can populate this at init time and are expected to handle setting the CPUState field and register as approriate. --- FEXCore/Source/Interface/Core/Core.cpp | 11 ++++++++++- FEXCore/Source/Interface/Core/JIT/JIT.cpp | 1 + FEXCore/include/FEXCore/Debug/InternalThreadState.h | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index c30bbdf47..2ed90ba03 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -516,6 +516,7 @@ void ContextImpl::ClearCodeCache(FEXCore::Core::InternalThreadState* Thread, boo // Clear L1+L2 cache of this thread, and clear L3 cache across any threads using it Thread->LookupCache->ClearCache(); } + Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); } static void IRDumper(FEXCore::Core::InternalThreadState* Thread, IR::IREmitter* IREmitter, uint64_t GuestRIP) { @@ -940,11 +941,19 @@ static void InvalidateGuestThreadCodeRange(FEXCore::Core::InternalThreadState* T Accumulator.emplace_back(std::move(it->second)); } + bool InvalidatedAnyEntries = false; for (const auto& PageEntries : Accumulator) { for (const auto& Entry : PageEntries) { - ContextImpl::ThreadRemoveCodeEntry(Thread, Entry); + if (ContextImpl::ThreadRemoveCodeEntry(Thread, Entry)) { + InvalidatedAnyEntries = true; + } } } + + if (InvalidatedAnyEntries) { + // This may cause access violations in the thread on Windows as zeroing is not atomic, this is handled by the frontend + Allocator::VirtualDontNeed(Thread->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); + } } void ContextImpl::InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState* Thread, InvalidatedEntryAccumulator& Accumulator, diff --git a/FEXCore/Source/Interface/Core/JIT/JIT.cpp b/FEXCore/Source/Interface/Core/JIT/JIT.cpp index 2a46f0788..bc93171f5 100644 --- a/FEXCore/Source/Interface/Core/JIT/JIT.cpp +++ b/FEXCore/Source/Interface/Core/JIT/JIT.cpp @@ -957,6 +957,7 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, uint64_t Size LOGMAN_THROW_A_FMT(CurrentCodeBuffer->LookupCache.get() == ThreadState->LookupCache->Shared, "INVARIANT VIOLATED: SharedLookupCache " "doesn't match up!\n"); if (auto Prev = CheckCodeBufferUpdate()) { + Allocator::VirtualDontNeed(ThreadState->CallRetStackBase, FEXCore::Core::InternalThreadState::CALLRET_STACK_SIZE); ThreadState->LookupCache->ChangeGuestToHostMapping(*Prev, *CurrentCodeBuffer->LookupCache); } diff --git a/FEXCore/include/FEXCore/Debug/InternalThreadState.h b/FEXCore/include/FEXCore/Debug/InternalThreadState.h index 5c8bc424f..fb881ee54 100644 --- a/FEXCore/include/FEXCore/Debug/InternalThreadState.h +++ b/FEXCore/include/FEXCore/Debug/InternalThreadState.h @@ -105,6 +105,11 @@ struct InternalThreadState : public FEXCore::Allocator::FEXAllocOperators { ///< Data pointer for exclusive use by the frontend void* FrontendPtr; + static constexpr size_t CALLRET_STACK_SIZE {0x400000}; + + // The low address of the call-ret stack allocation (not including guard pages) + void* CallRetStackBase {}; + // BaseFrameState should always be at the end, directly before the interrupt fault page alignas(16) FEXCore::Core::CpuStateFrame BaseFrameState {};