From 2a82f581950b6cbc09c657e83b06899559c53a26 Mon Sep 17 00:00:00 2001 From: "Pierre-Loup A. Griffais" Date: Wed, 26 Aug 2026 21:00:25 -0700 Subject: [PATCH] DiskCache: use memory-mapped IO for reads when possible On Wine, use a unixlib call to get a quality mapping that can track a growing file. --- .../Source/Interface/Config/Config.json.in | 7 ++++ FEXCore/Source/Interface/Core/DiskCache.cpp | 33 +++++++++++++++- FEXCore/include/FEXCore/Core/DiskCache.h | 7 ++++ .../FEXCore/Core/DiskCacheFileMapper.h | 11 ++++++ FEXCore/include/FEXCore/Utils/File.h | 4 ++ .../Tools/FEXInterpreter/FEXInterpreter.cpp | 13 +++++++ Source/Windows/ARM64EC/Module.cpp | 4 ++ Source/Windows/Common/FEXUnixLib.cpp | 39 +++++++++++++++---- Source/Windows/Common/FEXUnixLib.h | 3 ++ Source/Windows/UnixLib/FEXUnixLib.cpp | 14 +++++++ Source/Windows/UnixLib/FEXUnixLib.h | 8 ++++ Source/Windows/WOW64/Module.cpp | 4 ++ 12 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 FEXCore/include/FEXCore/Core/DiskCacheFileMapper.h diff --git a/FEXCore/Source/Interface/Config/Config.json.in b/FEXCore/Source/Interface/Config/Config.json.in index b225b680a..21aa19382 100644 --- a/FEXCore/Source/Interface/Config/Config.json.in +++ b/FEXCore/Source/Interface/Config/Config.json.in @@ -140,6 +140,13 @@ "Enables disk caching for code blocks" ] }, + "DiskCacheFileMapping": { + "Type": "bool", + "Default": "true", + "Desc": [ + "Maps cache files for faster reading" + ] + }, "DiskCacheRelocationFilter": { "Type": "bool", "Default": "true", diff --git a/FEXCore/Source/Interface/Core/DiskCache.cpp b/FEXCore/Source/Interface/Core/DiskCache.cpp index f070ac563..59ccbc58b 100644 --- a/FEXCore/Source/Interface/Core/DiskCache.cpp +++ b/FEXCore/Source/Interface/Core/DiskCache.cpp @@ -4,6 +4,7 @@ #include "FEXCore/fextl/string.h" #include "FEXHeaderUtils/Filesystem.h" #include "FEXCore/Core/DiskCache.h" +#include "FEXCore/Core/DiskCacheFileMapper.h" #include "FEXCore/Utils/LogManager.h" #include "Interface/Context/Context.h" #include "FEXCore/HLE/SyscallHandler.h" @@ -38,6 +39,8 @@ namespace DiskCache { } // namespace MesaFOZ + static FileMapperFunc FileMapper = nullptr; + bool FOZFile::Open(const fextl::string& FOZFileName, bool ReadOnly) { FileName = FOZFileName; this->ReadOnly = ReadOnly; @@ -157,6 +160,12 @@ namespace DiskCache { return false; } + File::File::FileHandleType CacheFileHandle = CacheFOZ.GetHandle(); + if (FileMapper && CacheFileHandle != (File::File::FileHandleType)-1) { + CacheFileMapping = reinterpret_cast(FileMapper(CacheFileHandle, ReadOnly ? CacheFOZ.Size() : BIG_MAPPING_SIZE)); + CacheFileSize = CacheFOZ.Size(); + } + this->ReadOnly = ReadOnly; return true; } @@ -207,7 +216,16 @@ namespace DiskCache { } bool IndexedDB::ReadCacheBlob(uint64_t Offset, std::span OutBlob) { - return CacheFOZ.ReadBlob(Offset, OutBlob); + if (CacheFileMapping && (ReadOnly || Offset + OutBlob.size() <= BIG_MAPPING_SIZE)) { + if (Offset + OutBlob.size() > CacheFileSize) { + return false; + } + // todo could reduce copies by having a private mapping for relocs, etc + memcpy(OutBlob.data(), CacheFileMapping + Offset, OutBlob.size()); + return true; + } else { + return CacheFOZ.ReadBlob(Offset, OutBlob); + } } bool IndexedDB::StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span Blob, Index& Index, std::mutex& IndexMutex) { @@ -260,6 +278,11 @@ namespace DiskCache { CacheFOZ.Unlock(); IndexFOZ.Unlock(); + // publish new file size for memory-mapped reads + if (CacheFileMapping) { + CacheFileSize = BlobOffset + Blob.size(); + } + std::lock_guard Guard(IndexMutex); Index[Hash] = {this, BlobOffset, (uint32_t)Blob.size()}; return true; @@ -294,6 +317,10 @@ namespace DiskCache { return true; } + FEX_DEFAULT_VISIBILITY void SetFileMapper(FileMapperFunc Func) { + FileMapper = Func; + } + void DiskCache::Init(FEXCore::Context::ContextImpl* CTX) { this->CTX = CTX; @@ -322,6 +349,10 @@ namespace DiskCache { } FHU::Filesystem::CreateDirectories(BasePath); + if (!MapDiskCacheFiles) { + FileMapper = nullptr; + } + fextl::string RWDBBasePath = BasePath + "RWCacheDB"; OpenCacheDB(RWDBBasePath, false); diff --git a/FEXCore/include/FEXCore/Core/DiskCache.h b/FEXCore/include/FEXCore/Core/DiskCache.h index 6b9f14bc0..4bd5ce763 100644 --- a/FEXCore/include/FEXCore/Core/DiskCache.h +++ b/FEXCore/include/FEXCore/Core/DiskCache.h @@ -124,6 +124,9 @@ namespace DiskCache { } return FD->Unlock(); } + File::File::FileHandleType GetHandle() { + return FD ? FD->GetHandle() : (File::File::FileHandleType)-1; + } ssize_t Size(); bool ReadAll(fextl::vector& Out); // from first blob bool ReadBlob(uint64_t Offset, std::span OutBlob); @@ -147,8 +150,11 @@ namespace DiskCache { private: // stores run on the Writer, so returning quick isn't as important static constexpr uint32_t STORE_LOCK_TIMEOUT_MS = 1000; + static constexpr uint64_t BIG_MAPPING_SIZE = 1ULL << 33; FOZFile CacheFOZ; + uint8_t* CacheFileMapping = nullptr; + std::atomic CacheFileSize; FOZFile IndexFOZ; bool ReadOnly = false; }; @@ -187,6 +193,7 @@ namespace DiskCache { fextl::unique_ptr Writer; FEX_CONFIG_OPT(EnableDiskCache, DISKCACHE); + FEX_CONFIG_OPT(MapDiskCacheFiles, DISKCACHEFILEMAPPING); FEX_CONFIG_OPT(RelocationFilter, DISKCACHERELOCATIONFILTER); FEX_CONFIG_OPT(BasePathOverride, DISKCACHEPATH); FEX_CONFIG_OPT(RODBNames, DISKCACHERODBNAMES); diff --git a/FEXCore/include/FEXCore/Core/DiskCacheFileMapper.h b/FEXCore/include/FEXCore/Core/DiskCacheFileMapper.h new file mode 100644 index 000000000..6e32c07bf --- /dev/null +++ b/FEXCore/include/FEXCore/Core/DiskCacheFileMapper.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include "FEXCore/Utils/CompilerDefs.h" +#include "FEXCore/Utils/File.h" + +namespace FEXCore::DiskCache { +using FileMapperFunc = void* (*)(FEXCore::File::File::FileHandleType Handle, uint64_t MapSize); + +FEX_DEFAULT_VISIBILITY void SetFileMapper(FileMapperFunc Func); +} // namespace FEXCore::DiskCache diff --git a/FEXCore/include/FEXCore/Utils/File.h b/FEXCore/include/FEXCore/Utils/File.h index 140e40a1c..9de2d26d2 100644 --- a/FEXCore/include/FEXCore/Utils/File.h +++ b/FEXCore/include/FEXCore/Utils/File.h @@ -69,6 +69,10 @@ public: ShouldClose = IsValidHandle; } + FileHandleType GetHandle() { + return Handle; + } + /** * @brief Write Bytes to File * diff --git a/Source/Tools/FEXInterpreter/FEXInterpreter.cpp b/Source/Tools/FEXInterpreter/FEXInterpreter.cpp index 6bbfa02b1..c2eb2fbea 100644 --- a/Source/Tools/FEXInterpreter/FEXInterpreter.cpp +++ b/Source/Tools/FEXInterpreter/FEXInterpreter.cpp @@ -28,6 +28,7 @@ $end_info$ #include #include #include +#include #include #include #include @@ -192,6 +193,16 @@ void Shutdown(fextl::vector&& MemoryRegions) { } } // namespace FEX::Allocator +static void* DiskCacheFilemapper(FEXCore::File::File::FileHandleType Handle, uint64_t MapSize) { + int mapFD = dup(Handle); + void* Result = FEXCore::Allocator::mmap(nullptr, MapSize, PROT_READ, MAP_SHARED | MAP_NORESERVE, mapFD, 0); + close(mapFD); + if (Result == (void*)-1) { + return nullptr; + } + return Result; +} + bool InterpreterHandler(fextl::string* Filename, const fextl::string& RootFS, fextl::vector* args) { int FD {-1}; @@ -521,6 +532,8 @@ int main(int argc, char** argv, char** const envp) { // Setup Thread handlers, so FEXCore can create threads. auto StackTracker = FEX::LinuxEmulation::Threads::SetupThreadHandlers(); + FEXCore::DiskCache::SetFileMapper(DiskCacheFilemapper); + auto MemoryRegions = FEX::Allocator::InitMemoryRegions(Loader.Is64BitMode()); auto Allocator = FEX::Allocator::InitAllocator(Loader.Is64BitMode()); diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 0d132e939..c8df78bfe 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -11,6 +11,7 @@ $end_info$ #include #include #include +#include #include #include #include @@ -606,6 +607,9 @@ NTSTATUS ProcessInit() { FEX::Windows::Allocator::SetupHooks(NtDll); FEX::Windows::UnixLib::Init(NtDll); + if (FEX::Windows::UnixLib::Available()) { + FEXCore::DiskCache::SetFileMapper(FEX::Windows::UnixLib::MapFile); + } { auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine, FEXCore::HostFeatures::HostTypeEnum::Arm64ec); diff --git a/Source/Windows/Common/FEXUnixLib.cpp b/Source/Windows/Common/FEXUnixLib.cpp index 979782395..057c7258f 100644 --- a/Source/Windows/Common/FEXUnixLib.cpp +++ b/Source/Windows/Common/FEXUnixLib.cpp @@ -26,6 +26,9 @@ unixlib_handle_t UnixLibHandle {}; decltype(__wine_unix_call_dispatcher) UnixCallDispatcher {}; +using wine_server_handle_to_fd_t = NTSTATUS(CDECL*)(HANDLE, unsigned int, int*, unsigned int*); +static wine_server_handle_to_fd_t WineServerHandleToFd {}; + #ifdef ARCHITECTURE_arm64ec // On ARM64EC, indirect calls go through __os_arm64x_dispatch_icall which invokes // FEX's custom call checker. Use a naked trampoline to bypass the dispatch mechanism @@ -88,6 +91,7 @@ namespace Illegal { bool Init(HMODULE NtDll) { const auto Sym = GetProcAddress(NtDll, "__wine_unix_call_dispatcher"); + WineServerHandleToFd = reinterpret_cast(GetProcAddress(NtDll, "wine_server_handle_to_fd")); if (!Sym) { return false; @@ -134,12 +138,12 @@ bool Init(HMODULE NtDll) { return true; } -static bool UnixLibAvailable() { +bool Available() { return UnixLibHandle != 0; } bool TryEnableHardwareTSO() { - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. FEXUnixLib_SetHardwareTSOControlArgs Args { .Enable = true, @@ -155,7 +159,7 @@ bool TryEnableHardwareTSO() { } bool SetKernelUnalignedAtomicControl(uint64_t Flags) { - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. FEXUnixLib_SetKernelUnalignedAtomicControl Args { .Flags = Flags, @@ -169,7 +173,7 @@ bool SetKernelUnalignedAtomicControl(uint64_t Flags) { } void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control) { - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. FEXUnixLib_Madvise Args { .Addr = Ptr, @@ -190,7 +194,7 @@ void VirtualName(const char* Name, const void* Ptr, size_t Size) { return; } - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. FEXUnixLib_SetVMAName Args { .Addr = Ptr, @@ -209,7 +213,7 @@ void VirtualName(const char* Name, const void* Ptr, size_t Size) { } SHMSlotResult AllocateSHMSlots(void* SHMBase, uint32_t MapSize, uint32_t MaxSize) { - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. FEXUnixLib_GetSHMStatsVMA Args { .SHMBase = SHMBase, @@ -277,7 +281,7 @@ SHMSlotResult AllocateSHMSlots(void* SHMBase, uint32_t MapSize, uint32_t MaxSize } void DeleteSHMStatsFile() { - if (UnixLibAvailable()) { + if (Available()) { // UnixLib path. Call(FEXUnixLibFunctions::DeleteSHMStatsFile, nullptr); return; @@ -287,4 +291,25 @@ void DeleteSHMStatsFile() { DeleteFileA(fextl::fmt::format("/dev/shm/fex-{}-stats", Illegal::linux_getpid()).c_str()); } +void* MapFile(HANDLE FileHandle, uint64_t MapSize) { + if (Available() && WineServerHandleToFd) { + FEXUnixLib_MapFile Args { + .FD = -1, + .MapSize = MapSize, + }; + + // this is a dup equivalent + auto Result = WineServerHandleToFd(FileHandle, FILE_READ_DATA, &Args.FD, nullptr); + if (Result != STATUS_SUCCESS || Args.FD == -1) { + return nullptr; + } + + if (Call(FEXUnixLibFunctions::MapFile, &Args) == STATUS_SUCCESS) { + return Args.Result; + } + } + + return nullptr; +} + } // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/Common/FEXUnixLib.h b/Source/Windows/Common/FEXUnixLib.h index 36da52c54..c8a09bfed 100644 --- a/Source/Windows/Common/FEXUnixLib.h +++ b/Source/Windows/Common/FEXUnixLib.h @@ -19,6 +19,7 @@ inline NTSTATUS Call(FEXUnixLibFunctions code, void* args) { } bool Init(HMODULE NtDll); +bool Available(); /** * @brief Tries to enable hardware TSO if supported. @@ -45,4 +46,6 @@ struct SHMSlotResult { }; SHMSlotResult AllocateSHMSlots(void* SHMBase, uint32_t MapSize, uint32_t MaxSize); void DeleteSHMStatsFile(); + +void* MapFile(HANDLE FileHandle, uint64_t MapSize); } // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/UnixLib/FEXUnixLib.cpp b/Source/Windows/UnixLib/FEXUnixLib.cpp index 692e459f1..ade82220c 100644 --- a/Source/Windows/UnixLib/FEXUnixLib.cpp +++ b/Source/Windows/UnixLib/FEXUnixLib.cpp @@ -163,6 +163,19 @@ static NTSTATUS FEXUnixLib_DeleteSHMStatsFileHandler(void* _Args) { return STATUS_SUCCESS; } +static NTSTATUS FEXUnixLib_MapFileHandler(void* _Args) { + auto Args = reinterpret_cast(_Args); + // could find a way to dedupe with DiskCacheFilemapper in FEXInterpreter but it's barely any code + // fd was already duped by Wine handle translation + Args->Result = mmap(nullptr, Args->MapSize, PROT_READ, MAP_SHARED | MAP_NORESERVE, Args->FD, 0); + close(Args->FD); + if (Args->Result == (void*)-1) { + Args->Result = nullptr; + return STATUS_INTERNAL_ERROR; + } + return STATUS_SUCCESS; +} + extern "C" const unixlib_entry_t __wine_unix_call_funcs[] = { FEXUnixLib_SetHardwareTSOControlHandler, FEXUnixLib_SetKernelUnalignedAtomicControlHandler, @@ -170,6 +183,7 @@ extern "C" const unixlib_entry_t __wine_unix_call_funcs[] = { FEXUnixLib_SetVMANameHandler, FEXUnixLib_GetSHMStatsVMAHandler, FEXUnixLib_DeleteSHMStatsFileHandler, + FEXUnixLib_MapFileHandler, }; static_assert(sizeof(__wine_unix_call_funcs) / sizeof(__wine_unix_call_funcs[0]) == ToUnderlying(FEXUnixLibFunctions::MAX)); diff --git a/Source/Windows/UnixLib/FEXUnixLib.h b/Source/Windows/UnixLib/FEXUnixLib.h index 4795d62c0..80ff24cf0 100644 --- a/Source/Windows/UnixLib/FEXUnixLib.h +++ b/Source/Windows/UnixLib/FEXUnixLib.h @@ -10,6 +10,7 @@ enum class FEXUnixLibFunctions : uint32_t { SetVMAName, GetSHMStatsVMA, DeleteSHMStatsFile, + MapFile, MAX, }; @@ -51,3 +52,10 @@ struct FEXUnixLib_GetSHMStatsVMA { uint32_t MaxSize; }; static_assert(sizeof(FEXUnixLib_GetSHMStatsVMA) == 16); + +struct FEXUnixLib_MapFile { + int32_t FD; + uint64_t MapSize; + void* Result; +}; +static_assert(sizeof(FEXUnixLib_MapFile) == 24); diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 6ef2c3f0a..15e182e3d 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -13,6 +13,7 @@ $end_info$ #include #include #include +#include #include #include #include @@ -538,6 +539,9 @@ void BTCpuProcessInit() { FEX::Windows::Allocator::SetupHooks(NtDll); FEX::Windows::UnixLib::Init(NtDll); + if (FEX::Windows::UnixLib::Available()) { + FEXCore::DiskCache::SetFileMapper(FEX::Windows::UnixLib::MapFile); + } { auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine, FEXCore::HostFeatures::HostTypeEnum::Wow64);