// SPDX-License-Identifier: MIT #include "Interface/Core/LookupCache.h" #include "Interface/Core/SharedCodeBufferManager.h" #include #include #include #include #ifndef _WIN32 #include #endif namespace FEXCore::CPU { static constexpr size_t INITIAL_CODE_SIZE = 1024 * 1024 * 16; // We don't want to move above 128MB atm because that means we will have to encode longer jumps static constexpr size_t MAX_CODE_SIZE = 1024 * 1024 * 128; CodeBuffer::CodeBuffer(size_t Size) : AllocatedSize(Size) { Ptr = static_cast(FEXCore::Allocator::VirtualAlloc(Size, true)); LOGMAN_THROW_A_FMT(!!Ptr, "Couldn't allocate code buffer"); // Protect the last page of the allocated buffer to trigger SIGSEGV on write access uintptr_t LastPageAddr = AlignDown(reinterpret_cast(Ptr) + Size - 1, FEXCore::Utils::FEX_PAGE_SIZE); if (!FEXCore::Allocator::VirtualProtect(reinterpret_cast(LastPageAddr), FEXCore::Utils::FEX_PAGE_SIZE, FEXCore::Allocator::ProtectOptions::None)) { LogMan::Msg::EFmt("Failed to mprotect last page of code buffer."); } FEXCore::Allocator::VirtualName("FEXMemJIT", Ptr, Size); // Huge-pages reduce the amount of iTLB misses dramatically when it works. FEXCore::Allocator::VirtualTHPControl(Ptr, Size, FEXCore::Allocator::THPControl::Enable); LookupCache = fextl::make_unique(); CodeBufferEnd = Ptr + UsableSize(); CodeBufferOffset = Ptr; } CodeBuffer::~CodeBuffer() { FEXCore::Allocator::VirtualFree(Ptr, AllocatedSize); } fextl::shared_ptr SharedCodeBufferManager::AllocateNew(size_t Size) { #ifndef _WIN32 // MDWE (Memory-Deny-Write-Execute) is a new Linux 6.3 feature. // It's equivalent to systemd's `MemoryDenyWriteExecute` but implemented entirely in the kernel. // // MDWE prevents applications from creating RWX memory mappings. // This prevents FEX from doing anything JIT related, as FEX uses RWX for JIT memory mappings. // // A potential workaround to make FEX work with MDWE is to call mprotect every time we need to write or modify code. // Alternatively, FEX could use a memory mirror where one half is mapped as RW and the other is RX. // // Once MDWE is enabled with the prctl, the feature is sealed and it can /NOT/ be turned off. // // Status of MDWE is queried through prctl using `PR_GET_MDWE`: // -1: The kernel doesn't support MDWE // 0: MDWE is supported but disabled // >0: MDWE is enabled, hence prohibiting RWX mappings int MDWE = ::prctl(PR_GET_MDWE, 0, 0, 0, 0); if (MDWE != -1 && MDWE != 0) { LogMan::Msg::EFmt("MDWE was set to 0x{:x} which means FEX can't allocate executable memory", MDWE); } #endif auto Buffer = fextl::make_shared(Size); Latest = Buffer; OnCodeBufferAllocated(Buffer); return Buffer; } fextl::shared_ptr SharedCodeBufferManager::GetLatest() { if (!Latest) { AllocateNew(INITIAL_CODE_SIZE); } return Latest; } fextl::shared_ptr SharedCodeBufferManager::StartLargerCodeBuffer() { if (!Latest) { // Allocate initial CodeBuffer and return it return GetLatest(); } auto NewCodeBufferSize = GetLatest()->TotalAllocationSize(); NewCodeBufferSize = std::min(NewCodeBufferSize * 2, MAX_CODE_SIZE); return AllocateNew(NewCodeBufferSize); } fextl::shared_ptr SharedCodeBufferManager::StartMaximalCodeBuffer() { return AllocateNew(MAX_CODE_SIZE); } } // namespace FEXCore::CPU