diff --git a/FEXCore/Source/Interface/Config/Config.json.in b/FEXCore/Source/Interface/Config/Config.json.in index d1045decc..131755b47 100644 --- a/FEXCore/Source/Interface/Config/Config.json.in +++ b/FEXCore/Source/Interface/Config/Config.json.in @@ -522,6 +522,27 @@ "Desc": [ "Disables inline syscalls in order to support seccomp handling" ] + }, + "ExtendedVolatileMetadata": { + "Type": "str", + "Default": "", + "Desc": [ + "Configuration provided volatile metadata. Only implemented for WoW64/arm64ec.", + "Limited in its use but can be handy.", + "Extends on top of what Microsoft has for volatile metadata, but also supported for WoW64.", + "Colon delimited modules, then semi-colon delimited instructions, then comma delimited ranges", + "Default disables TSO in the module, unless instructions overlap the range", + ";-,...;,...:", + "examples:", + " * Disable TSO for a full module: Just provide the module name:", + " `hl2_linux`", + " * Disable TSO for a part of the module:", + " `hl2_linux;-`", + " * Disable TSO for a part of the module, but enable TSO for some instructions within the module", + " `hl2_linux;-;,`", + " * Disable TSO for multiple modules", + " `hl2_linux:libsdl2.so`" + ] } } }, diff --git a/Source/Common/CMakeLists.txt b/Source/Common/CMakeLists.txt index 822a69ac5..95cd7feb7 100644 --- a/Source/Common/CMakeLists.txt +++ b/Source/Common/CMakeLists.txt @@ -9,7 +9,9 @@ set(SRCS HostFeatures.cpp JSONPool.cpp StringUtil.cpp - SHMStats.cpp) + SHMStats.cpp + VolatileMetadata.cpp + ) if (NOT MINGW_BUILD) list (APPEND SRCS diff --git a/Source/Common/VolatileMetadata.cpp b/Source/Common/VolatileMetadata.cpp new file mode 100644 index 000000000..90ace901f --- /dev/null +++ b/Source/Common/VolatileMetadata.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +#include "Common/VolatileMetadata.h" + +namespace FEX::VolatileMetadata { +fextl::unordered_map ParseExtendedVolatileMetadata(std::string_view ListOfDescriptors) { + // Parsing: `;
-,;:` + if (ListOfDescriptors.empty()) { + return {}; + } + + fextl::unordered_map ExtendedMetaData {}; + + auto current_module = ExtendedMetaData.end(); + + for (size_t module_offset = 0; module_offset != ListOfDescriptors.npos;) { + size_t end_of_module = ListOfDescriptors.find(":", module_offset); + std::string_view module_config = ListOfDescriptors.substr(module_offset, end_of_module - module_offset); + + if (module_config.empty()) { + module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1; + continue; + } + + size_t end_of_name = module_config.find(";"); + size_t end_of_address_ranges = module_config.npos; + size_t end_of_individual_inst = module_config.npos; + + // Module name handling + { + std::string_view section_str = module_config.substr(0, end_of_name); + + if (section_str.empty()) { + module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1; + continue; + } + + current_module = ExtendedMetaData + .insert_or_assign(fextl::string(section_str), + ExtendedVolatileMetadata { + .ModuleTSODisabled = true, + }) + .first; + } + + // Address range handling + if (end_of_name != module_config.npos) { + end_of_address_ranges = module_config.find(";", end_of_name + 1); + std::string_view section_str = module_config.substr(end_of_name + 1, end_of_address_ranges - (end_of_name + 1)); + + if (section_str.empty()) { + module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1; + continue; + } + + current_module->second.ModuleTSODisabled = false; + + // Walk all the address ranges provided. + for (size_t non_tso_region_offset = 0; non_tso_region_offset != section_str.npos;) { + size_t end_of_region_substr = section_str.find(",", non_tso_region_offset); + std::string_view tso_region_view = section_str.substr(non_tso_region_offset, end_of_region_substr - non_tso_region_offset); + + if (tso_region_view.empty()) { + non_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1; + continue; + } + + uint64_t begin {}, end {}; + char* str_end; + begin = std::strtoull(tso_region_view.data(), &str_end, 16); + LOGMAN_THROW_A_FMT(tso_region_view.data() != str_end, "Couldn't parse begin {}", tso_region_view); + + // Skip `-` separator. + ++str_end; + + LOGMAN_THROW_A_FMT(str_end != tso_region_view.end(), "Couldn't parse end {}", tso_region_view); + auto str_begin = str_end; + end = std::strtoull(str_begin, &str_end, 16); + LOGMAN_THROW_A_FMT(str_begin != str_end, "Couldn't parse end {}", tso_region_view); + + current_module->second.VolatileValidRanges.Insert({begin, end}); + non_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1; + } + } + + // Individual instruction handling + if (end_of_address_ranges != module_config.npos) { + end_of_individual_inst = module_config.find(";", end_of_address_ranges + 1); + std::string_view section_str = module_config.substr(end_of_address_ranges + 1, end_of_individual_inst); + + if (section_str.empty()) { + module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1; + continue; + } + + for (size_t force_tso_region_offset = 0; force_tso_region_offset != section_str.npos;) { + size_t end_of_region_substr = section_str.find(",", force_tso_region_offset); + std::string_view tso_region_view = section_str.substr(force_tso_region_offset, end_of_region_substr - force_tso_region_offset); + + if (tso_region_view.empty()) { + force_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1; + continue; + } + + uint64_t offset {}; + char* str_end; + offset = std::strtoull(tso_region_view.data(), &str_end, 16); + LOGMAN_THROW_A_FMT(tso_region_view.data() != str_end, "Couldn't parse offset {}", tso_region_view); + + current_module->second.VolatileInstructions.insert(offset); + + force_tso_region_offset = end_of_region_substr == section_str.npos ? section_str.npos : end_of_region_substr + 1; + } + } + + module_offset = end_of_module == ListOfDescriptors.npos ? ListOfDescriptors.npos : end_of_module + 1; + } + + return ExtendedMetaData; +} +} // namespace FEX::VolatileMetadata diff --git a/Source/Common/VolatileMetadata.h b/Source/Common/VolatileMetadata.h new file mode 100644 index 000000000..21df597c6 --- /dev/null +++ b/Source/Common/VolatileMetadata.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include +#include +#include +#include + +namespace FEX::VolatileMetadata { +struct ExtendedVolatileMetadata { + FEXCore::IntervalList VolatileValidRanges; + fextl::set VolatileInstructions; + bool ModuleTSODisabled; +}; + +fextl::unordered_map ParseExtendedVolatileMetadata(std::string_view ListOfDescriptors); +} // namespace FEX::VolatileMetadata diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index b25fa43c6..aca9e877b 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -43,6 +43,7 @@ $end_info$ #include "DummyHandlers.h" #include "BTInterface.h" #include "Windows/Common/SHMStats.h" +#include "Windows/Common/VolatileMetadata.h" #include #include @@ -80,6 +81,7 @@ void* WineSyscallDispatcher; uint64_t WineNtContinueSyscallId; uint64_t WineNtAllocateVirtualMemorySyscallId; uint64_t WineNtProtectVirtualMemorySyscallId; +static fextl::unordered_map ExtendedMetaData {}; NTSTATUS NtContinueNative(ARM64_NT_CONTEXT* NativeContext, BOOLEAN Alert); NTSTATUS NtAllocateVirtualMemoryNative(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG); @@ -267,11 +269,10 @@ void InitSyscalls() { PatchCallChecker(); } -void LoadImageVolatileMetadata(uint64_t Address) { - const auto Module = reinterpret_cast(Address); - IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module); - uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage; +void LoadImageVolatileMetadata(fextl::set& VolatileInstructions, FEXCore::IntervalList& VolatileValidRanges, + HMODULE Module, IMAGE_NT_HEADERS* Nt, uint64_t Address, uint64_t EndAddress) { ULONG Size; + const auto* LoadConfig = reinterpret_cast<_IMAGE_LOAD_CONFIG_DIRECTORY64*>(RtlImageDirectoryEntryToData(Module, true, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &Size)); if (!LoadConfig || LoadConfig->Size <= offsetof(_IMAGE_LOAD_CONFIG_DIRECTORY64, VolatileMetadataPointer)) { @@ -288,7 +289,6 @@ void LoadImageVolatileMetadata(uint64_t Address) { return; } - fextl::set VolatileInstructions; const auto* VolatileAccessTableBegin = reinterpret_cast(Address + VolatileMetadata->VolatileAccessTable); const auto* VolatileAccessTableEnd = VolatileAccessTableBegin + (VolatileMetadata->VolatileAccessTableSize / sizeof(IMAGE_VOLATILE_RVA_METADATA)); @@ -296,13 +296,31 @@ void LoadImageVolatileMetadata(uint64_t Address) { VolatileInstructions.emplace(Address + It->Rva); } - FEXCore::IntervalList VolatileValidRanges; const auto* VolatileInfoRangeTableBegin = reinterpret_cast(Address + VolatileMetadata->VolatileInfoRangeTable); const auto* VolatileInfoRangeTableEnd = VolatileInfoRangeTableBegin + (VolatileMetadata->VolatileInfoRangeTableSize / sizeof(IMAGE_VOLATILE_RANGE_METADATA)); for (auto It = VolatileInfoRangeTableBegin; It != VolatileInfoRangeTableEnd; It++) { VolatileValidRanges.Insert({Address + It->Rva, Address + It->Rva + It->Size}); } +} + +void LoadImageVolatileMetadata(const fextl::string& ModuleName, uint64_t Address) { + const auto Module = reinterpret_cast(Address); + IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module); + uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage; + + fextl::set VolatileInstructions {}; + FEXCore::IntervalList VolatileValidRanges {}; + LoadImageVolatileMetadata(VolatileInstructions, VolatileValidRanges, Module, Nt, Address, EndAddress); + + auto it = ExtendedMetaData.find(ModuleName); + if (it != ExtendedMetaData.end()) { + FEX::Windows::ApplyFEXExtendedVolatileMetadata(it->second, VolatileInstructions, VolatileValidRanges, Address, EndAddress); + } + + if (VolatileInstructions.empty() && VolatileValidRanges.Empty()) { + return; + } LogMan::Msg::DFmt("Loaded volatile metadata for {:X}: {} entries", Address, VolatileInstructions.size()); std::scoped_lock Lock(CTX->GetCodeInvalidationMutex()); @@ -314,7 +332,7 @@ void HandleImageMap(uint64_t Address) { LogMan::Msg::DFmt("Load module {}: {:X}", ModuleName, Address); FEX_CONFIG_OPT(VolatileMetadata, VOLATILEMETADATA); if (VolatileMetadata) { - LoadImageVolatileMetadata(Address); + LoadImageVolatileMetadata(ModuleName, Address); } InvalidationTracker->HandleImageMap(ModuleName, Address); } @@ -630,6 +648,9 @@ NTSTATUS ProcessInit() { FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT); + FEX_CONFIG_OPT(ExtendedVolatileMetadataConfig, EXTENDEDVOLATILEMETADATA); + ExtendedMetaData = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(ExtendedVolatileMetadataConfig()); + SignalDelegator = fextl::make_unique(); SyscallHandler = fextl::make_unique(); Exception::HandlerConfig.emplace(); @@ -941,7 +962,7 @@ NTSTATUS ThreadInit() { auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32]; // Setup initial code-segment GDT - auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; + auto& GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; FEXCore::Core::CPUState::SetGDTBase(&GDT, 0); FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU); GDT.L = 1; // L = Long Mode = 64-bit @@ -1033,7 +1054,7 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { auto ThreadState = CPUArea.ThreadState(); // GDT and LDT are mirrored, only free one. - delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; + delete[] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; FEX::Windows::CallRetStack::DestroyThread(ThreadState); CTX->DestroyThread(ThreadState); diff --git a/Source/Windows/Common/VolatileMetadata.h b/Source/Windows/Common/VolatileMetadata.h new file mode 100644 index 000000000..8d6d3f813 --- /dev/null +++ b/Source/Windows/Common/VolatileMetadata.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include "Common/VolatileMetadata.h" + +#include +#include +#include +#include + +namespace FEX::Windows { +inline void ApplyFEXExtendedVolatileMetadata(FEX::VolatileMetadata::ExtendedVolatileMetadata& ExtendedMetaData, + fextl::set& VolatileInstructions, + FEXCore::IntervalList& VolatileValidRanges, uint64_t Address, uint64_t EndAddress) { + // Load FEX extended volatile metadata. + // Walk the volatile instructions first if they exist. + for (const auto it_inst : ExtendedMetaData.VolatileInstructions) { + const auto inst_address = it_inst + Address; + if (inst_address < EndAddress) { + VolatileInstructions.emplace(Address + it_inst); + } else { + LogMan::Msg::DFmt("Volatile instruction 0x{:x} couldn't fit in to module range [0x{:x}, 0x{:x}). Not adding anymore volatile " + "instructions. Inspect your config!", + inst_address, Address, EndAddress); + return; + } + } + + // Walk the volatile list + for (const auto it_ranges : ExtendedMetaData.VolatileValidRanges) { + VolatileValidRanges.Insert({Address + it_ranges.Offset, Address + it_ranges.End}); + } + + // If it is fully disabled, then set the entire module range + if (ExtendedMetaData.ModuleTSODisabled) { + VolatileValidRanges.Clear(); + VolatileValidRanges.Insert({Address, EndAddress}); + } +} + + +} // namespace FEX::Windows diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 26f2315cd..692553257 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -26,6 +26,7 @@ $end_info$ #include #include #include +#include #include "Common/ArgumentLoader.h" #include "Common/CallRetStack.h" @@ -43,6 +44,7 @@ $end_info$ #include "DummyHandlers.h" #include "BTInterface.h" #include "Windows/Common/SHMStats.h" +#include "Windows/Common/VolatileMetadata.h" #include #include @@ -127,6 +129,8 @@ std::mutex ThreadCreationMutex; // Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing std::unordered_map Threads; +static fextl::unordered_map ExtendedMetaData {}; + decltype(__wine_unix_call_dispatcher) WineUnixCall; std::pair GetThreadTLS(HANDLE Thread) { @@ -159,9 +163,36 @@ bool IsAddressInJit(uint64_t Address) { return Thread->CTX->IsAddressInCodeBuffer(Thread, Address); } +void LoadImageVolatileMetadata(const fextl::string& ModuleName, uint64_t Address) { + const auto Module = reinterpret_cast(Address); + IMAGE_NT_HEADERS* Nt = RtlImageNtHeader(Module); + uint64_t EndAddress = Address + Nt->OptionalHeader.SizeOfImage; + + fextl::set VolatileInstructions; + FEXCore::IntervalList VolatileValidRanges; + + // Load FEX extended volatile metadata. + auto it = ExtendedMetaData.find(ModuleName); + if (it != ExtendedMetaData.end()) { + FEX::Windows::ApplyFEXExtendedVolatileMetadata(it->second, VolatileInstructions, VolatileValidRanges, Address, EndAddress); + } + + if (VolatileInstructions.empty() && VolatileValidRanges.Empty()) { + return; + } + + LogMan::Msg::DFmt("Loaded volatile metadata for {:X}: {} entries", Address, VolatileInstructions.size()); + std::scoped_lock Lock(CTX->GetCodeInvalidationMutex()); + CTX->AddForceTSOInformation(VolatileValidRanges, std::move(VolatileInstructions)); +} + void HandleImageMap(uint64_t Address) { fextl::string ModuleName = FEX::Windows::GetSectionFilePath(Address); LogMan::Msg::DFmt("Load module {}: {:X}", ModuleName, Address); + FEX_CONFIG_OPT(VolatileMetadata, VOLATILEMETADATA); + if (VolatileMetadata) { + LoadImageVolatileMetadata(ModuleName, Address); + } InvalidationTracker->HandleImageMap(ModuleName, Address); } } // namespace @@ -494,6 +525,9 @@ void BTCpuProcessInit() { FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_32BIT); + FEX_CONFIG_OPT(ExtendedVolatileMetadataConfig, EXTENDEDVOLATILEMETADATA); + ExtendedMetaData = FEX::VolatileMetadata::ParseExtendedVolatileMetadata(ExtendedVolatileMetadataConfig()); + SignalDelegator = fextl::make_unique(); SyscallHandler = fextl::make_unique(); Context::HandlerConfig.emplace(); @@ -572,7 +606,7 @@ void BTCpuThreadInit() { auto NewSegments = new FEXCore::Core::CPUState::gdt_segment[32]; // Setup initial code-segment GDT - auto &GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; + auto& GDT = NewSegments[FEXCore::Core::CPUState::DEFAULT_USER_CS]; FEXCore::Core::CPUState::SetGDTBase(&GDT, 0); FEXCore::Core::CPUState::SetGDTLimit(&GDT, 0xF'FFFFU); GDT.L = 0; // L = Long Mode = 32-bit @@ -638,7 +672,7 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { auto ThreadState = TLS.ThreadState(); // GDT and LDT are mirrored, only free one. - delete [] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; + delete[] ThreadState->CurrentFrame->State.segment_arrays[FEXCore::Core::CPUState::SEGMENT_ARRAY_INDEX_GDT]; FEX::Windows::CallRetStack::DestroyThread(ThreadState); CTX->DestroyThread(ThreadState); @@ -734,7 +768,7 @@ __attribute__((naked)) void BTCpuSimulate() { ".seh_endproc;"); } -extern "C" void BTCpuSimulateImpl(CONTEXT *entry_context) { +extern "C" void BTCpuSimulateImpl(CONTEXT* entry_context) { auto TLS = GetTLS(); TLS.EntryContext() = entry_context; TLS.CachedCallRetSp() = TLS.ThreadState()->CurrentFrame->State.callret_sp;