From 954581c750f65fe49de01cc7e0bcb3ea63bb5426 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 28 Jun 2026 10:55:08 -0700 Subject: [PATCH] Windows/UnixLib: Adds remaining helpers Centralizes all the nasty behaviour that will end up breaking when WINE eventually turns on userspace syscall dispatch. Pushes all of the logic in to the UnixLib. Support both paths until everyone is migrated to supporting the UnixLib, then we can delete the bit of code duplication between the PE side and UnixLib side. Helpful that everything that gets punched through the UnixLib is optional, so worst case some optional bits can break for a while. --- Source/Windows/Common/Allocator.cpp | 224 +---------------------- Source/Windows/Common/FEXUnixLib.cpp | 187 +++++++++++++++++++ Source/Windows/Common/FEXUnixLib.h | 20 ++ Source/Windows/Common/SHMStats.cpp | 79 ++------ Source/Windows/Common/SHMStats.h | 1 - Source/Windows/Common/TSOHandlerConfig.h | 2 +- Source/Windows/UnixLib/FEXUnixLib.cpp | 126 ++++++++++++- Source/Windows/UnixLib/FEXUnixLib.h | 38 ++++ Source/Windows/include/winternl.h | 5 - 9 files changed, 389 insertions(+), 293 deletions(-) diff --git a/Source/Windows/Common/Allocator.cpp b/Source/Windows/Common/Allocator.cpp index 0643e062f..d535bdef7 100644 --- a/Source/Windows/Common/Allocator.cpp +++ b/Source/Windows/Common/Allocator.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT #include #include +#include "Windows/Common/FEXUnixLib.h" #include #include @@ -13,228 +14,13 @@ #include namespace FEX::Windows::Allocator { -#define PR_SET_VMA 0x53564d41 -#define PR_SET_VMA_ANON_NAME 0 - -#define MADV_HUGEPAGE 14 -#define MADV_NOHUGEPAGE 15 - -namespace Trampoline { - struct madvise_data { - const void* addr; - size_t size; - int advise; - }; - - struct prctl_data { - int op; - uint64_t attr; - const void* addr; - size_t size; - const char* name; - uint64_t ret; - }; - - __attribute__((naked)) uint64_t wine_prctl(prctl_data* d) { - asm volatile( - R"( - .globl wine_prctl_begin - wine_prctl_begin: - mov x19, x0; - mov x8, 167; // prctl - ldr x0, [x19]; // op - ldp x1, x2, [x19, %[attr_offset]]; // {attr, addr} - ldp x3, x4, [x19, %[size_offset]]; // {size, name} - svc #0; - str x0, [x19, %[ret_offset]]; - // Tell wine it was all groovy. - mov x0, 0; - ret; - - .globl wine_prctl_end - wine_prctl_end: - )" - : - : [attr_offset] "i"(offsetof(prctl_data, attr)), [size_offset] "i"(offsetof(prctl_data, size)), [ret_offset] "i"(offsetof(prctl_data, ret)) - : "memory"); - }; - - __attribute__((naked)) uint64_t wine_madvise(madvise_data* d) { - asm volatile(R"( - .globl wine_madvise_begin - wine_madvise_begin: - mov x8, 233; // madvise - ldr x2, [x0, %[advise_offset]]; // advise - ldp x0, x1, [x0]; // {addr, size} - svc #0; - // Tell wine it was all groovy. - mov x0, 0; - ret; - - .globl wine_madvise_end - wine_madvise_end: - )" ::[advise_offset] "i"(offsetof(madvise_data, advise)) - : "memory"); - } - - extern "C" uint64_t wine_madvise_begin; - extern "C" uint64_t wine_madvise_end; - - void* const wine_madvise_begin_loc = &wine_madvise_begin; - void* const wine_madvise_end_loc = &wine_madvise_end; - - extern "C" uint64_t wine_prctl_begin; - extern "C" uint64_t wine_prctl_end; - - void* const wine_prctl_begin_loc = &wine_prctl_begin; - void* const wine_prctl_end_loc = &wine_prctl_end; - - extern NTSTATUS(WINAPI* __wine_unix_call_dispatcher)(uint64_t, unsigned int, void*); - decltype(__wine_unix_call_dispatcher) WineUnixCall; - - enum unix_function_indexes { - INDEX_PRCTL = 0, - INDEX_MADVISE = 1, - INDEX_MAX, - }; - static std::array unix_functions {}; - - static uint64_t wine_prctl_trampoline(int op, uint64_t attr, const void* addr, size_t size, const char* name) { - prctl_data d { - .op = op, - .attr = attr, - .addr = addr, - .size = size, - .name = name, - }; - WineUnixCall(reinterpret_cast(unix_functions.data()), INDEX_PRCTL, &d); - return d.ret; - } - - static uint64_t wine_madvise_trampoline(const void* addr, size_t size, int advice) { - madvise_data d { - .addr = addr, - .size = size, - .advise = advice, - }; - WineUnixCall(reinterpret_cast(unix_functions.data()), INDEX_MADVISE, &d); - return 0; - } - - void VirtualName(const char* Name, const void* Ptr, size_t Size) { - static bool Supports {true}; - if (Supports) { - auto Result = wine_prctl_trampoline(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Ptr, Size, Name); - if (Result != 0) { - // Disable any additional attempts. - Supports = false; - } - } - } - - void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control) { - wine_madvise_trampoline(Ptr, Size, Control == FEXCore::Allocator::THPControl::Disable ? MADV_NOHUGEPAGE : MADV_HUGEPAGE); - } -} // namespace Trampoline - -// This code path will eventually crash once Wine and the kernel implements `userspace syscall dispatch`. -// FEX will need to switch over to using WINE's unixlib syscall approach then. -// See `SetupHooks` for why unixlib doesn't work today. -namespace Illegal { - __attribute__((naked)) uint64_t prctl(int op, uint64_t attr, const void* addr, size_t size, const char* Name) { - asm volatile(R"( - mov x8, 167; // prctl - svc #0; - ret; - )" :: - : "memory"); - } - - __attribute__((naked)) uint64_t madvise(const void* addr, size_t size, int advice) { - asm volatile(R"( - mov x8, 233; // madvise - svc #0; - ret; - )" :: - : "memory"); - } - - void VirtualName(const char* Name, const void* Ptr, size_t Size) { - static bool Supports {true}; - if (Supports) { - auto Result = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Ptr, Size, Name); - if (Result != 0) { - // Disable any additional attempts. - Supports = false; - } - } - } - - void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control) { - madvise(Ptr, Size, Control == FEXCore::Allocator::THPControl::Disable ? MADV_NOHUGEPAGE : MADV_HUGEPAGE); - } -} // namespace Illegal - void SetupHooks(HMODULE ntdll) { - // If this symbol doesn't exist, then we aren't running under WINE. - const auto Sym = GetProcAddress(ntdll, "__wine_unix_call_dispatcher"); - - if (!Sym) { - return; - } - FEXCore::Allocator::HookPtrs Ptrs {}; - // Wine will soon require us to use unixlib for calling helper routines that use Linux syscalls. - // It needs this for `userspace syscall dispatch` to capture rogue applications doing raw Windows syscalls. - // If FEX doesn't use unixlib, then when WINE and a kernel implements this, then our "illegal" path will start crashing. - // - // This code currently conflicts with FEX's `Call Checker` since the latter captures uses of `unixlib`. - // We hence have to stick to the `Illegal::` functions until a workaround is implemented. - if constexpr (false) { - // NTSTATUS __wine_unix_call_dispatcher( unixlib_handle_t, unsigned int, void * ); - // - unixlib_handle_t is just an array of functions - // - uint32_t is just an index in to that - // - void* is the user provided pointer, gets loaded in to x0 in for the unix_function called. - // - Return value - SUCCESS or other error. - Trampoline::WineUnixCall = *reinterpret_cast(Sym); - - // This code must be copied over to allocated memory from top down allocations apparently. - auto Code = reinterpret_cast( - ::VirtualAlloc(nullptr, FEXCore::Utils::FEX_PAGE_SIZE, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE)); - if (!Code) { - return; - } - - size_t CurrentOffset {}; - const size_t prctl_size = - reinterpret_cast(Trampoline::wine_prctl_end_loc) - reinterpret_cast(Trampoline::wine_prctl_begin_loc); - const size_t madvise_size = - reinterpret_cast(Trampoline::wine_madvise_end_loc) - reinterpret_cast(Trampoline::wine_madvise_begin_loc); - - // Copy prctl. - memcpy(Code + CurrentOffset, Trampoline::wine_prctl_begin_loc, prctl_size); - Trampoline::unix_functions[Trampoline::INDEX_PRCTL] = Code + CurrentOffset; - CurrentOffset += prctl_size; - - // Copy madvise. - memcpy(Code + CurrentOffset, Trampoline::wine_madvise_begin_loc, madvise_size); - Trampoline::unix_functions[Trampoline::INDEX_MADVISE] = Code + CurrentOffset; - - // Protect the page now. - FEXCore::Allocator::VirtualProtect(Code, FEXCore::Utils::FEX_PAGE_SIZE, - FEXCore::Allocator::ProtectOptions::Read | FEXCore::Allocator::ProtectOptions::Exec); - - Ptrs = { - .VirtualName = Trampoline::VirtualName, - .VirtualTHPControl = Trampoline::VirtualTHPControl, - }; - } else { - Ptrs = { - .VirtualName = Illegal::VirtualName, - .VirtualTHPControl = Illegal::VirtualTHPControl, - }; - } + Ptrs = { + .VirtualName = UnixLib::VirtualName, + .VirtualTHPControl = UnixLib::VirtualTHPControl, + }; SYSTEM_INFO system_info {}; GetSystemInfo(&system_info); diff --git a/Source/Windows/Common/FEXUnixLib.cpp b/Source/Windows/Common/FEXUnixLib.cpp index 301a9f29c..dfdd5bc83 100644 --- a/Source/Windows/Common/FEXUnixLib.cpp +++ b/Source/Windows/Common/FEXUnixLib.cpp @@ -1,16 +1,26 @@ // SPDX-License-Identifier: MIT +#include #include #include #include +#include #define __WINESRC__ #include #include #include "FEXUnixLib.h" +#define PR_SET_VMA 0x53564d41 +#define PR_SET_VMA_ANON_NAME 0 + +#define MADV_HUGEPAGE 14 +#define MADV_NOHUGEPAGE 15 + extern "C" IMAGE_DOS_HEADER __ImageBase; namespace FEX::Windows::UnixLib { +static bool UsingNTQueryPath {}; +static bool SupportsVirtualName {true}; unixlib_handle_t UnixLibHandle {}; decltype(__wine_unix_call_dispatcher) UnixCallDispatcher {}; @@ -30,6 +40,50 @@ static NTSTATUS __attribute__((naked)) TrampolineCall(unixlib_handle_t, unsigned : "memory"); } #endif +// This code path will eventually crash once Wine and the kernel implements `userspace syscall dispatch`. +// FEX will need to switch over to using WINE's unixlib syscall approach then. +namespace Illegal { + __attribute__((naked)) uint64_t prctl(int op, uint64_t attr, const void* addr, size_t size, const char* Name) { + asm volatile(R"( + mov x8, 167; // prctl + svc #0; + ret; + )" :: + : "memory"); + } + + __attribute__((naked)) uint64_t madvise(const void* addr, size_t size, int advice) { + asm volatile(R"( + mov x8, 233; // madvise + svc #0; + ret; + )" :: + : "memory"); + } + + __attribute__((naked)) uint64_t linux_getpid() { + asm volatile(R"( + mov x8, 172; + svc #0; + ret; + )" :: + : "r0", "r8"); + } + + void VirtualName(const char* Name, const void* Ptr, size_t Size) { + if (SupportsVirtualName) { + auto Result = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Ptr, Size, Name); + if (Result != 0) { + // Disable any additional attempts. + SupportsVirtualName = false; + } + } + } + + void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control) { + madvise(Ptr, Size, Control == FEXCore::Allocator::THPControl::Disable ? MADV_NOHUGEPAGE : MADV_HUGEPAGE); + } +} // namespace Illegal bool Init(HMODULE NtDll) { const auto Sym = GetProcAddress(NtDll, "__wine_unix_call_dispatcher"); @@ -99,4 +153,137 @@ bool TryEnableHardwareTSO() { return Status == STATUS_SUCCESS; } +bool SetKernelUnalignedAtomicControl(uint64_t Flags) { + if (UnixLibAvailable()) { + // UnixLib path. + FEXUnixLib_SetKernelUnalignedAtomicControl Args { + .Flags = Flags, + }; + + return Call(FEXUnixLibFunctions::SetKernelUnalignedAtomicControl, &Args) == STATUS_SUCCESS; + } + + // Legacy Proton path. + return NtSetInformationProcess(NtCurrentProcess(), ProcessFexUnalignAtomic, &Flags, sizeof(Flags)) == STATUS_SUCCESS; +} + +void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control) { + if (UnixLibAvailable()) { + // UnixLib path. + FEXUnixLib_Madvise Args { + .Addr = Ptr, + .Size = Size, + .Advise = Control == FEXCore::Allocator::THPControl::Disable ? MADV_NOHUGEPAGE : MADV_HUGEPAGE, + }; + + Call(FEXUnixLibFunctions::Madvise, &Args); + return; + } + + // Legacy Proton path. + Illegal::VirtualTHPControl(Ptr, Size, Control); +} + +void VirtualName(const char* Name, const void* Ptr, size_t Size) { + if (!SupportsVirtualName) { + return; + } + + if (UnixLibAvailable()) { + // UnixLib path. + FEXUnixLib_SetVMAName Args { + .Addr = Ptr, + .Size = Size, + .Name = Name, + }; + + if (Call(FEXUnixLibFunctions::SetVMAName, &Args) != STATUS_SUCCESS) { + SupportsVirtualName = false; + } + return; + } + + // Legacy Proton path. + Illegal::VirtualName(Name, Ptr, Size); +} + +SHMSlotResult AllocateSHMSlots(void* SHMBase, uint32_t MapSize, uint32_t MaxSize) { + if (UnixLibAvailable()) { + // UnixLib path. + FEXUnixLib_GetSHMStatsVMA Args { + .SHMBase = SHMBase, + .MapSize = MapSize, + .MaxSize = MaxSize, + }; + + if (Call(FEXUnixLibFunctions::GetSHMStatsVMA, &Args) == STATUS_SUCCESS) { + return { + .SHMBase = Args.SHMBase, + .MappedSize = Args.MapSize, + }; + } + + return {}; + } + + // Legacy Proton path. + // Magic WINE+FEX path. + if (SHMBase == nullptr || UsingNTQueryPath) { + MEMORY_FEX_STATS_SHM_INFORMATION Info { + .shm_base = SHMBase, + .map_size = MapSize, + .max_size = MaxSize, + }; + size_t Length {}; + auto Result = NtQueryVirtualMemory(NtCurrentProcess(), nullptr, MemoryFexStatsShm, &Info, sizeof(Info), &Length); + if (!Result) { + UsingNTQueryPath = true; + return { + .SHMBase = Info.shm_base, + .MappedSize = MapSize, + }; + } + } + + // Opaque handle path, doesn't support resizing. + auto handle = CreateFile(fextl::fmt::format("/dev/shm/fex-{}-stats", Illegal::linux_getpid()).c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + // Create the section mapping for the file handle for the full size. + HANDLE SectionMapping; + LARGE_INTEGER SectionSize {{MaxSize}}; + auto Result = NtCreateSection(&SectionMapping, SECTION_EXTEND_SIZE | SECTION_MAP_READ | SECTION_MAP_WRITE, nullptr, &SectionSize, + PAGE_READWRITE, SEC_COMMIT, handle); + if (Result != STATUS_SUCCESS) { + CloseHandle(handle); + return {}; + } + + // Section mapping is used from now on. + CloseHandle(handle); + + // Now actually map the view of the section. + void* Base = nullptr; + size_t FullSize = MaxSize; + Result = NtMapViewOfSection(SectionMapping, NtCurrentProcess(), &Base, 0, 0, nullptr, &FullSize, ViewUnmap, MEM_RESERVE | MEM_TOP_DOWN, + PAGE_READWRITE); + if (Result != STATUS_SUCCESS) { + CloseHandle(SectionMapping); + return {}; + } + + return {.SHMBase = Base, .MappedSize = MaxSize}; +} + +void DeleteSHMStatsFile() { + if (UnixLibAvailable()) { + // UnixLib path. + Call(FEXUnixLibFunctions::DeleteSHMStatsFile, nullptr); + return; + } + + // Legacy Proton path. + DeleteFile(fextl::fmt::format("/dev/shm/fex-{}-stats", Illegal::linux_getpid()).c_str()); +} + } // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/Common/FEXUnixLib.h b/Source/Windows/Common/FEXUnixLib.h index bd98c27f5..36da52c54 100644 --- a/Source/Windows/Common/FEXUnixLib.h +++ b/Source/Windows/Common/FEXUnixLib.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once #include +#include #include #include "../UnixLib/FEXUnixLib.h" #include "wine/unixlib.h" @@ -25,4 +26,23 @@ bool Init(HMODULE NtDll); * @return true if hardware TSO is supported and enabled. */ bool TryEnableHardwareTSO(); + +/** + * @brief Tries to enable kernel unaligned atomic handling if supported. + * + * @param Flags Which unaligned types to enable + * + * @return true if the flags were enabled + */ +bool SetKernelUnalignedAtomicControl(uint64_t Flags); + +void VirtualTHPControl(const void* Ptr, size_t Size, FEXCore::Allocator::THPControl Control); +void VirtualName(const char* Name, const void* Ptr, size_t Size); + +struct SHMSlotResult { + void* SHMBase; + uint32_t MappedSize; +}; +SHMSlotResult AllocateSHMSlots(void* SHMBase, uint32_t MapSize, uint32_t MaxSize); +void DeleteSHMStatsFile(); } // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/Common/SHMStats.cpp b/Source/Windows/Common/SHMStats.cpp index 9d844b3a3..81daa0d72 100644 --- a/Source/Windows/Common/SHMStats.cpp +++ b/Source/Windows/Common/SHMStats.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +#include "Windows/Common/FEXUnixLib.h" #include "Windows/Common/SHMStats.h" #include @@ -12,88 +13,34 @@ #include namespace FEX::Windows { -__attribute__((naked)) uint64_t linux_getpid() { - asm volatile(R"( - mov x8, 172; - svc #0; - ret; - )" :: - : "r0", "r8"); -} - uint32_t StatAlloc::FrontendAllocateSlots(uint32_t NewSize) { - if (CurrentSize == MAX_STATS_SIZE || !UsingNTQueryPath) { + if (CurrentSize == MAX_STATS_SIZE) { LogMan::Msg::DFmt("Ran out of slots. Can't allocate more"); return CurrentSize; } - MEMORY_FEX_STATS_SHM_INFORMATION Info { - .shm_base = nullptr, - .map_size = std::min(CurrentSize * 2, MAX_STATS_SIZE), - .max_size = MAX_STATS_SIZE, - }; - size_t Length {}; - auto Result = NtQueryVirtualMemory(NtCurrentProcess(), nullptr, MemoryFexStatsShm, &Info, sizeof(Info), &Length); - if (!Result) { - CurrentSize = Info.map_size; + auto Result = UnixLib::AllocateSHMSlots(Base, std::min(CurrentSize * 2, MAX_STATS_SIZE), MAX_STATS_SIZE); + + if (Result.SHMBase) { + CurrentSize = Result.MappedSize; } return CurrentSize; } StatAlloc::StatAlloc(FEXCore::SHMStats::AppType AppType) { - // Try wine+fex magic path. + auto Result = UnixLib::AllocateSHMSlots(Base, FEXCore::Utils::FEX_PAGE_SIZE, MAX_STATS_SIZE); - { - MEMORY_FEX_STATS_SHM_INFORMATION Info { - .shm_base = nullptr, - .map_size = FEXCore::Utils::FEX_PAGE_SIZE, - .max_size = MAX_STATS_SIZE, - }; - size_t Length {}; - auto Result = NtQueryVirtualMemory(NtCurrentProcess(), nullptr, MemoryFexStatsShm, &Info, sizeof(Info), &Length); - if (!Result) { - UsingNTQueryPath = true; - CurrentSize = Info.map_size; - Base = Info.shm_base; - SaveHeader(AppType); - return; - } - } - CurrentSize = MAX_STATS_SIZE; - - auto handle = CreateFile(fextl::fmt::format("/dev/shm/fex-{}-stats", linux_getpid()).c_str(), GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - // Create the section mapping for the file handle for the full size. - HANDLE SectionMapping; - LARGE_INTEGER SectionSize {{MAX_STATS_SIZE}}; - auto Result = NtCreateSection(&SectionMapping, SECTION_EXTEND_SIZE | SECTION_MAP_READ | SECTION_MAP_WRITE, nullptr, &SectionSize, - PAGE_READWRITE, SEC_COMMIT, handle); - if (Result != 0) { - CloseHandle(handle); + if (Result.SHMBase) { + CurrentSize = Result.MappedSize; + Base = Result.SHMBase; + SaveHeader(AppType); return; } - - // Section mapping is used from now on. - CloseHandle(handle); - - // Now actually map the view of the section. - Base = 0; - size_t FullSize = MAX_STATS_SIZE; - Result = NtMapViewOfSection(SectionMapping, NtCurrentProcess(), &Base, 0, 0, nullptr, &FullSize, ViewUnmap, MEM_RESERVE | MEM_TOP_DOWN, - PAGE_READWRITE); - if (Result != 0) { - CloseHandle(SectionMapping); - return; - } - - // Once WINE supports NtExtendSection and SECTION_EXTEND_SIZE correctly then we can map/commit a single page, map the full MAX_STATS_SIZE - // view as reserved, and extend the view using NtExtendSection. - SaveHeader(AppType); } + StatAlloc::~StatAlloc() { - DeleteFile(fextl::fmt::format("/dev/shm/fex-{}-stats", linux_getpid()).c_str()); + UnixLib::DeleteSHMStatsFile(); } } // namespace FEX::Windows diff --git a/Source/Windows/Common/SHMStats.h b/Source/Windows/Common/SHMStats.h index c8bbc6172..786efc2c6 100644 --- a/Source/Windows/Common/SHMStats.h +++ b/Source/Windows/Common/SHMStats.h @@ -23,7 +23,6 @@ public: private: uint32_t FrontendAllocateSlots(uint32_t NewSize) override; - bool UsingNTQueryPath {}; }; } // namespace FEX::Windows diff --git a/Source/Windows/Common/TSOHandlerConfig.h b/Source/Windows/Common/TSOHandlerConfig.h index cbde622da..f63478d79 100644 --- a/Source/Windows/Common/TSOHandlerConfig.h +++ b/Source/Windows/Common/TSOHandlerConfig.h @@ -24,7 +24,7 @@ public: uint64_t Flags = (StrictInProcessSplitLocks() ? FEX_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS : 0) | (KernelUnalignedAtomicBackpatching() ? FEX_UNALIGN_ATOMIC_BACKPATCH : 0) | FEX_UNALIGN_ATOMIC_EMULATE; - if (NtSetInformationProcess(NtCurrentProcess(), ProcessFexUnalignAtomic, &Flags, sizeof(Flags)) == STATUS_SUCCESS) { + if (UnixLib::SetKernelUnalignedAtomicControl(Flags)) { LogMan::Msg::IFmt("FEX: Kernel unaligned atomics enabled!"); } } diff --git a/Source/Windows/UnixLib/FEXUnixLib.cpp b/Source/Windows/UnixLib/FEXUnixLib.cpp index e5d6014a5..692e459f1 100644 --- a/Source/Windows/UnixLib/FEXUnixLib.cpp +++ b/Source/Windows/UnixLib/FEXUnixLib.cpp @@ -1,7 +1,11 @@ // SPDX-License-Identifier: MIT #include #include +#include +#include +#include #include +#include #include "FEXUnixLib.h" @@ -31,6 +35,15 @@ constexpr NTSTATUS STATUS_INTERNAL_ERROR = 0xC00000E5; #ifndef PR_SET_MEM_MODEL_TSO #define PR_SET_MEM_MODEL_TSO 1 #endif +#ifndef PR_ARM64_SET_UNALIGN_ATOMIC +#define PR_ARM64_SET_UNALIGN_ATOMIC 0x46455849 +#endif +#ifndef PR_SET_VMA +#define PR_SET_VMA 0x53564d41 +#endif +#ifndef PR_SET_VMA_ANON_NAME +#define PR_SET_VMA_ANON_NAME 0 +#endif static NTSTATUS FEXUnixLib_SetHardwareTSOControlHandler(void* _Args) { auto Args = reinterpret_cast(_Args); @@ -46,6 +59,117 @@ static NTSTATUS FEXUnixLib_SetHardwareTSOControlHandler(void* _Args) { return STATUS_SUCCESS; } -extern "C" const unixlib_entry_t __wine_unix_call_funcs[] = {FEXUnixLib_SetHardwareTSOControlHandler}; +static NTSTATUS FEXUnixLib_SetKernelUnalignedAtomicControlHandler(void* _Args) { + auto Args = reinterpret_cast(_Args); + return prctl(PR_ARM64_SET_UNALIGN_ATOMIC, Args->Flags, 0, 0, 0) == 0 ? STATUS_SUCCESS : STATUS_NOT_SUPPORTED; +} + +static NTSTATUS FEXUnixLib_MadviseHandler(void* _Args) { + auto Args = reinterpret_cast(_Args); + return madvise(const_cast(Args->Addr), Args->Size, Args->Advise) == 0 ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR; +} + +static NTSTATUS FEXUnixLib_SetVMANameHandler(void* _Args) { + auto Args = reinterpret_cast(_Args); + return prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, reinterpret_cast(Args->Addr), Args->Size, Args->Name) == 0 ? + STATUS_SUCCESS : + STATUS_INTERNAL_ERROR; +} + +constexpr static int USER_PERMS = S_IRWXU | S_IRWXG | S_IRWXO; +static inline void* InitializeSHM(uint32_t MapSize, uint32_t MaxSize) { + void* Base {}; + const std::string Name = "fex-" + std::to_string(getpid()) + "-stats"; + int fd = shm_open(Name.c_str(), O_CREAT | O_TRUNC | O_RDWR, USER_PERMS); + + if (fd == -1) { + return nullptr; + } + + if (ftruncate(fd, MapSize) == -1) { + goto err; + } + + // Reserve a region of MAX_STATS_SIZE so we can grow the allocation buffer. + // Number of thread slots when ThreadStatsHeader == 64bytes and ThreadStats == 40bytes: + // 1 page: 99 slots + // 1 MB: 26211 slots + // 128 MB: 3355440 slots + Base = mmap(nullptr, MaxSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (Base == MAP_FAILED) { + Base = nullptr; + goto err; + } + + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, reinterpret_cast(Base), MaxSize, "FEXMem_Misc"); + + // Allocate a small working shared space for now, grow as necessary. + { + auto SharedBase = mmap(Base, MapSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); + if (SharedBase == MAP_FAILED) { + ::munmap(Base, MaxSize); + Base = nullptr; + goto err; + } + } + +err: + close(fd); + + return Base; +} + +static inline bool AllocateMoreSlots(void* SHMBase, uint32_t NewSize) { + bool Result {}; + const std::string Name = "fex-" + std::to_string(getpid()) + "-stats"; + + // When allocating more slots, open the fd without O_TRUNC | O_CREAT. + int fd = shm_open(Name.c_str(), O_RDWR, USER_PERMS); + if (fd == -1) { + return false; + } + + if (ftruncate(fd, NewSize) == -1) { + goto err; + } + + { + auto SharedBase = mmap(SHMBase, NewSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); + if (SharedBase == MAP_FAILED) { + goto err; + } + } + + Result = true; + +err: + close(fd); + return Result; +} + +static NTSTATUS FEXUnixLib_GetSHMStatsVMAHandler(void* _Args) { + auto Args = reinterpret_cast(_Args); + if (Args->SHMBase == nullptr) { + Args->SHMBase = InitializeSHM(Args->MapSize, Args->MaxSize); + return Args->SHMBase ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR; + } + + return AllocateMoreSlots(Args->SHMBase, Args->MapSize) ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR; +} + +static NTSTATUS FEXUnixLib_DeleteSHMStatsFileHandler(void* _Args) { + const std::string Name = "fex-" + std::to_string(getpid()) + "-stats"; + shm_unlink(Name.c_str()); + return STATUS_SUCCESS; +} + +extern "C" const unixlib_entry_t __wine_unix_call_funcs[] = { + FEXUnixLib_SetHardwareTSOControlHandler, + FEXUnixLib_SetKernelUnalignedAtomicControlHandler, + FEXUnixLib_MadviseHandler, + FEXUnixLib_SetVMANameHandler, + FEXUnixLib_GetSHMStatsVMAHandler, + FEXUnixLib_DeleteSHMStatsFileHandler, +}; 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 4fa18fcf5..4795d62c0 100644 --- a/Source/Windows/UnixLib/FEXUnixLib.h +++ b/Source/Windows/UnixLib/FEXUnixLib.h @@ -1,9 +1,15 @@ // SPDX-License-Identifier: MIT #pragma once +#include #include enum class FEXUnixLibFunctions : uint32_t { SetHardwareTSOControl, + SetKernelUnalignedAtomicControl, + Madvise, + SetVMAName, + GetSHMStatsVMA, + DeleteSHMStatsFile, MAX, }; @@ -13,3 +19,35 @@ struct FEXUnixLib_SetHardwareTSOControlArgs { bool Enable; }; static_assert(sizeof(FEXUnixLib_SetHardwareTSOControlArgs) == 1); + +// These match the prctl flag values for PR_ARM64_SET_UNALIGN_ATOMIC +static constexpr uint64_t FEX_UNALIGN_ATOMIC_EMULATE = 1ULL << 0; +static constexpr uint64_t FEX_UNALIGN_ATOMIC_BACKPATCH = 1ULL << 1; +static constexpr uint64_t FEX_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS = 1ULL << 2; + +struct FEXUnixLib_SetKernelUnalignedAtomicControl { + uint64_t Flags; +}; +static_assert(sizeof(FEXUnixLib_SetKernelUnalignedAtomicControl) == 8); + +struct FEXUnixLib_Madvise { + const void* Addr; + size_t Size; + int32_t Advise; + uint32_t pad; +}; +static_assert(sizeof(FEXUnixLib_Madvise) == 24); + +struct FEXUnixLib_SetVMAName { + const void* Addr; + size_t Size; + const char* Name; +}; +static_assert(sizeof(FEXUnixLib_SetVMAName) == 24); + +struct FEXUnixLib_GetSHMStatsVMA { + void* SHMBase; + uint32_t MapSize; + uint32_t MaxSize; +}; +static_assert(sizeof(FEXUnixLib_GetSHMStatsVMA) == 16); diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index d15e27c48..de9e28741 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -482,11 +482,6 @@ typedef enum _MEMORY_INFORMATION_CLASS { #define ProcessFexHardwareTso (PROCESSINFOCLASS)2000 #define ProcessFexUnalignAtomic (PROCESSINFOCLASS)2001 -// These match the prctl flag values -#define FEX_UNALIGN_ATOMIC_EMULATE (1ULL << 0) -#define FEX_UNALIGN_ATOMIC_BACKPATCH (1ULL << 1) -#define FEX_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS (1ULL << 2) - typedef enum _KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation, KeyValueFullInformation,