From cbcb0034256bed78d6c869faf8505efd3b5e0aca Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Wed, 21 Oct 2026 14:03:35 -0700 Subject: [PATCH] HostFeatures: Make a stronger distinction between CPU features and Process features We want to have two distinct control knobs here. CPU features that don't change between Linux, Wow64, Arm64ec, and additional options that change depending on the process environment. This distinction was a /bit/ muddy where I added `SupportsFaultFreeSynchronousFaults` to the wrong side of that. Make it more distinct by logically splitting it out so that HostType, the fault-free-faults, and added Is64BitMode all lives together. This lets the DiskCache code which is hashing these variables look a bit nicer and also correctly logically separate the two halves. --- FEXCore/Source/Interface/Core/DiskCache.cpp | 11 +- FEXCore/Source/Interface/Core/DiskCache.h | 2 +- FEXCore/Source/Interface/Core/Frontend.cpp | 4 +- FEXCore/Source/Interface/Core/JIT/MiscOps.cpp | 2 +- .../Interface/Core/OpcodeDispatcher.cpp | 12 +-- FEXCore/include/FEXCore/Core/HostFeatures.h | 101 ++++++++++-------- Source/Common/HostFeatures.cpp | 7 +- Source/Common/HostFeatures.h | 2 +- Source/Tools/CodeSizeValidation/Main.cpp | 2 +- .../Tools/FEXInterpreter/FEXInterpreter.cpp | 2 +- Source/Tools/FEXOfflineCompiler/Main.cpp | 2 +- .../TestHarnessRunner/TestHarnessRunner.cpp | 2 +- Source/Windows/Common/CPUFeatures.cpp | 15 ++- 13 files changed, 92 insertions(+), 72 deletions(-) diff --git a/FEXCore/Source/Interface/Core/DiskCache.cpp b/FEXCore/Source/Interface/Core/DiskCache.cpp index 1ddd90568..ed63ddb62 100644 --- a/FEXCore/Source/Interface/Core/DiskCache.cpp +++ b/FEXCore/Source/Interface/Core/DiskCache.cpp @@ -409,17 +409,18 @@ namespace DiskCache { uint64_t HostFeaturesHash; } MachineBucketData = {FormatVersion, HostFeatureHash.HostFeaturesHash}; - fextl::vector BucketBytes(sizeof(MachineBucketData) + (sizeof(uint8_t) * 2) + SerializedConfig.size()); + fextl::vector BucketBytes(sizeof(MachineBucketData) + sizeof(HostFeatureHash.ProcessFeaturesHash) + SerializedConfig.size()); memcpy(BucketBytes.data(), &MachineBucketData, sizeof(MachineBucketData)); // 64-bit mode and HostType is in the ProcessBucket hash only instead of the MachineBucketHash // These effect code-gen, but they aren't part of the MachineBucketHash, as it comes from Process state. - BucketBytes[sizeof(MachineBucketData)] = CTX->Config.Is64BitMode; - BucketBytes[sizeof(MachineBucketData) + 1] = FEXCore::ToUnderlying(HostFeatureHash.HostType); - memcpy(BucketBytes.data() + sizeof(MachineBucketData) + 2, SerializedConfig.data(), SerializedConfig.size()); + memcpy(&BucketBytes[sizeof(MachineBucketData)], &HostFeatureHash.ProcessFeaturesHash, sizeof(HostFeatureHash.ProcessFeaturesHash)); + memcpy(BucketBytes.data() + sizeof(MachineBucketData) + sizeof(HostFeatureHash.ProcessFeaturesHash), SerializedConfig.data(), + SerializedConfig.size()); uint64_t MachineBucketHash = XXH3_64bits(BucketBytes.data(), sizeof(MachineBucketData)); - uint64_t ProcessBucketHash = XXH3_64bits(BucketBytes.data() + sizeof(MachineBucketData), 2 + SerializedConfig.size()); + uint64_t ProcessBucketHash = + XXH3_64bits(BucketBytes.data() + sizeof(MachineBucketData), sizeof(HostFeatureHash.ProcessFeaturesHash) + SerializedConfig.size()); BucketHash.high64 = MachineBucketHash; BucketHash.low64 = ProcessBucketHash; diff --git a/FEXCore/Source/Interface/Core/DiskCache.h b/FEXCore/Source/Interface/Core/DiskCache.h index fb4a340ab..e39f4308e 100644 --- a/FEXCore/Source/Interface/Core/DiskCache.h +++ b/FEXCore/Source/Interface/Core/DiskCache.h @@ -249,7 +249,7 @@ namespace DiskCache { // The current version of the diskcache. // This must be changed any time codegen changes occur! // Be aware of the impact of changing this frequently! - static constexpr uint16_t FormatVersion = 34; + static constexpr uint16_t FormatVersion = 35; static constexpr uint32_t LOOKUP_KEY_MAX_BUCKET_DEPTH = 500; diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 78d5f1a2e..31d647a38 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -1352,8 +1352,8 @@ const Decoder::DecodeStream Decoder::AdjustAddrForSpecialRegion(const uint8_t* _ constexpr uint64_t VSyscall_Base = 0xFFFF'FFFF'FF60'0000ULL; constexpr uint64_t VSyscall_End = VSyscall_Base + 0x1000; - if (BlockInfo.Is64BitMode && CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux && RIP >= VSyscall_Base && - RIP < VSyscall_End) { + if (BlockInfo.Is64BitMode && CTX->HostFeatures.ProcessFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux && + RIP >= VSyscall_Base && RIP < VSyscall_End) { // VSyscall // This doesn't exist on AArch64 and on x86_64 hosts this is emulated with faults to a region mapped with --xp permissions // Offset 0: vgettimeofday diff --git a/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp b/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp index b2dfee5bc..998f46ae7 100644 --- a/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp +++ b/FEXCore/Source/Interface/Core/JIT/MiscOps.cpp @@ -76,7 +76,7 @@ DEF_OP(Break) { LoadConstant(ARMEmitter::Size::i64Bit, TMP1, Constant); str(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, SynchronousFaultData)); - if (CTX->HostFeatures.SupportsFaultFreeSynchronousFaults) { + if (CTX->HostFeatures.ProcessFeatures.SupportsFaultFreeSynchronousFaults) { ldr(TMP1, STATE, offsetof(FEXCore::Core::CpuStateFrame, Pointers.GuestSignal_SynchronousFault)); br(TMP1); } else { diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 0cf57cfe2..9a7494350 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -4884,7 +4884,7 @@ void OpDispatchBuilder::INTOp(OpcodeArgs) { case 0xCD: { // INT imm8 uint8_t Literal = Op->Src[0].Literal(); - if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux) { + if (CTX->HostFeatures.ProcessFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Linux) { constexpr uint8_t SYSCALL_LITERAL = 0x80; if (Literal == SYSCALL_LITERAL) { if (Is64BitMode) [[unlikely]] { @@ -4896,8 +4896,8 @@ void OpDispatchBuilder::INTOp(OpcodeArgs) { SyscallOp(Op, false); return; } - } else if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Wow64 || - CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { + } else if (CTX->HostFeatures.ProcessFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Wow64 || + CTX->HostFeatures.ProcessFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { constexpr uint8_t SYSCALL_LITERAL = 0x2E; if (Literal == SYSCALL_LITERAL) { // Can be used for both 64-bit and 32-bit syscalls on windows @@ -4905,7 +4905,7 @@ void OpDispatchBuilder::INTOp(OpcodeArgs) { return; } - if (CTX->HostFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { + if (CTX->HostFeatures.ProcessFeatures.HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec) { // This is used when QueryPerformanceCounter is called on recent Windows versions, it causes CNTVCT to be written into RAX. constexpr uint8_t GET_CNTVCT_LITERAL = 0x81; if (Literal == GET_CNTVCT_LITERAL) { @@ -5151,7 +5151,7 @@ void OpDispatchBuilder::RDTSCPOp(OpcodeArgs) { // - Explicitly use an MFENCE before this instruction if you want this behaviour // This instruction is not an execution fence, so subsequent instructions can execute after this // - Explicitly use an LFENCE after RDTSCP if you want to block this behaviour - if (CTX->HostFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { + if (CTX->HostFeatures.ProcessFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { // RDTSCP is unsupported on Win32 platforms if TPIDRRO isn't supported. UnimplementedOp(Op); return; @@ -5165,7 +5165,7 @@ void OpDispatchBuilder::RDTSCPOp(OpcodeArgs) { } void OpDispatchBuilder::RDPIDOp(OpcodeArgs) { - if (CTX->HostFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { + if (CTX->HostFeatures.ProcessFeatures.HostType != FEXCore::HostFeatures::HostTypeEnum::Linux && !CTX->HostFeatures.SupportsCPUIndexInTPIDRRO) { // RDTSCP is unsupported on Win32 platforms if TPIDRRO isn't supported. UnimplementedOp(Op); return; diff --git a/FEXCore/include/FEXCore/Core/HostFeatures.h b/FEXCore/include/FEXCore/Core/HostFeatures.h index 93584c011..80897b5a3 100644 --- a/FEXCore/include/FEXCore/Core/HostFeatures.h +++ b/FEXCore/include/FEXCore/Core/HostFeatures.h @@ -14,7 +14,7 @@ namespace FEXCore { */ struct HostFeatures { // Changes code generation slightly. - enum class HostTypeEnum : uint32_t { + enum class HostTypeEnum : uint8_t { Unknown, Linux, Wow64, @@ -32,65 +32,82 @@ struct HostFeatures { return 4 << DCacheLineLog2; } + /** + * @brief This structure contains all the data required to determine hashing at the "process" level. + * Unlike the `HostFeaturesHash` below, this data changes codegen depending on if the process is Linux, Arm64EC, or Wow64. + */ + struct ProcessFeaturesHash { + HostTypeEnum HostType : 2 {}; + uint8_t SupportsFaultFreeSynchronousFaults : 1 {}; + // This only determines the starting mode a process started in. + // The process can technically long jump to-from the other modes. + uint8_t Is64BitMode : 1 {}; + uint8_t pad : 4 {}; + }; + static_assert(sizeof(ProcessFeaturesHash) == 1); + struct CacheHash { + // This hash is used to determine the "bucket" a machine goes in to. + // This should match between Linux, Arm64EC, and Wow64. uint64_t HostFeaturesHash; - HostTypeEnum HostType; + + // This hash is used to determine features that are different between process types. + ProcessFeaturesHash ProcessFeaturesHash; }; [[nodiscard]] CacheHash HashForCaching() const { - static_assert(offsetof(HostFeatures, HostType) == 8); + static_assert(offsetof(HostFeatures, ProcessFeatures) == 8); CacheHash Result {}; memcpy(&Result.HostFeaturesHash, this, sizeof(uint64_t)); - Result.HostType = HostType; + Result.ProcessFeaturesHash = ProcessFeatures; return Result; } // Affects codegen and is basically machine description. - uint32_t DCacheLineLog2 : 4 {}; - uint32_t SupportsCacheMaintenanceOps : 1 {}; - uint32_t SupportsAES : 1 {}; - uint32_t SupportsCRC : 1 {}; - uint32_t SupportsCLZERO : 1 {}; - uint32_t SupportsAtomics : 1 {}; - uint32_t SupportsRCPC : 1 {}; - uint32_t SupportsTSOImm9 : 1 {}; - uint32_t SupportsRAND : 1 {}; - uint32_t SupportsAVX : 1 {}; - uint32_t SupportsSVE128 : 1 {}; - uint32_t SupportsSVE256 : 1 {}; - uint32_t SupportsSHA : 1 {}; - uint32_t SupportsPMULL_128Bit : 1 {}; - uint32_t SupportsCSSC : 1 {}; - uint32_t SupportsFCMA : 1 {}; - uint32_t SupportsFlagM : 1 {}; - uint32_t SupportsFlagM2 : 1 {}; - uint32_t SupportsRPRES : 1 {}; - uint32_t SupportsPreserveAllABI : 1 {}; - uint32_t SupportsAES256 : 1 {}; - uint32_t SupportsSVEBitPerm : 1 {}; - uint32_t SupportsCPUIndexInTPIDRRO : 1 {}; - uint32_t SupportsFRINTTS : 1 {}; - uint32_t SupportsECV : 1 {}; - uint32_t SupportsWFXT : 1 {}; - uint32_t Supports3DNow : 1 {}; - uint32_t SupportsSSE4a : 1 {}; - uint32_t SupportsMOPS : 1 {}; - uint32_t SupportsI8MM : 1 {}; - uint32_t SupportsDotProd : 1 {}; - uint32_t PreferZVAForVZero : 1 {}; - uint32_t SupportsAFP : 1 {}; - uint32_t SupportsFloatExceptions : 1 {}; - uint32_t SupportsFaultFreeSynchronousFaults : 1 {}; - uint32_t SupportsBF16 : 1 {}; + uint32_t DCacheLineLog2 : 4 {}; + uint32_t SupportsCacheMaintenanceOps : 1 {}; + uint32_t SupportsAES : 1 {}; + uint32_t SupportsCRC : 1 {}; + uint32_t SupportsCLZERO : 1 {}; + uint32_t SupportsAtomics : 1 {}; + uint32_t SupportsRCPC : 1 {}; + uint32_t SupportsTSOImm9 : 1 {}; + uint32_t SupportsRAND : 1 {}; + uint32_t SupportsAVX : 1 {}; + uint32_t SupportsSVE128 : 1 {}; + uint32_t SupportsSVE256 : 1 {}; + uint32_t SupportsSHA : 1 {}; + uint32_t SupportsPMULL_128Bit : 1 {}; + uint32_t SupportsCSSC : 1 {}; + uint32_t SupportsFCMA : 1 {}; + uint32_t SupportsFlagM : 1 {}; + uint32_t SupportsFlagM2 : 1 {}; + uint32_t SupportsRPRES : 1 {}; + uint32_t SupportsPreserveAllABI : 1 {}; + uint32_t SupportsAES256 : 1 {}; + uint32_t SupportsSVEBitPerm : 1 {}; + uint32_t SupportsCPUIndexInTPIDRRO : 1 {}; + uint32_t SupportsFRINTTS : 1 {}; + uint32_t SupportsECV : 1 {}; + uint32_t SupportsWFXT : 1 {}; + uint32_t Supports3DNow : 1 {}; + uint32_t SupportsSSE4a : 1 {}; + uint32_t SupportsMOPS : 1 {}; + uint32_t SupportsI8MM : 1 {}; + uint32_t SupportsDotProd : 1 {}; + uint32_t PreferZVAForVZero : 1 {}; + uint32_t SupportsAFP : 1 {}; + uint32_t SupportsFloatExceptions : 1 {}; + uint32_t SupportsBF16 : 1 {}; // Flag if this is InstCountCI uint32_t IsInstCountCI : 1 {}; - uint32_t pad : 24 {}; + uint32_t pad : 25 {}; // This affects codegen, but it isn't machine state - HostTypeEnum HostType {}; + ProcessFeaturesHash ProcessFeatures {}; // MIDR information // Also used for determining number of CPU cores for CPUID diff --git a/Source/Common/HostFeatures.cpp b/Source/Common/HostFeatures.cpp index 429725006..97a0cbcee 100644 --- a/Source/Common/HostFeatures.cpp +++ b/Source/Common/HostFeatures.cpp @@ -601,7 +601,7 @@ void FetchHostFeatures(FEX::CPUFeatures& Features, FEXCore::HostFeatures& HostFe OverrideFeatures(&HostFeatures, ForceSVEWidth()); } -FEXCore::HostFeatures FetchHostFeatures() { +FEXCore::HostFeatures FetchHostFeatures(bool Is64BitMode) { FEX_CONFIG_OPT(CPUFeatureRegisters, CPUFEATUREREGISTERS); CPUFeatures Features {}; @@ -634,7 +634,10 @@ FEXCore::HostFeatures FetchHostFeatures() { FetchHostFeatures(Features, HostFeatures, true, CTR, MIDR); HostFeatures.SupportsCPUIndexInTPIDRRO = false; - HostFeatures.HostType = FEXCore::HostFeatures::HostTypeEnum::Linux; + HostFeatures.ProcessFeatures.HostType = FEXCore::HostFeatures::HostTypeEnum::Linux; + HostFeatures.ProcessFeatures.Is64BitMode = Is64BitMode; + // Fault free syncronous faults unsupported on Linux for now. + HostFeatures.ProcessFeatures.SupportsFaultFreeSynchronousFaults = false; HostFeatures.ProcessPID = ::getpid(); return HostFeatures; } diff --git a/Source/Common/HostFeatures.h b/Source/Common/HostFeatures.h index 0fe73c9c5..667d67896 100644 --- a/Source/Common/HostFeatures.h +++ b/Source/Common/HostFeatures.h @@ -692,6 +692,6 @@ void FillMIDRInformationViaLinux(FEXCore::HostFeatures* Features); void FetchHostFeatures(FEX::CPUFeatures& Features, FEXCore::HostFeatures& HostFeatures, bool SupportsCacheMaintenanceOps, uint64_t CTR, uint64_t MIDR); -FEXCore::HostFeatures FetchHostFeatures(); +FEXCore::HostFeatures FetchHostFeatures(bool Is64BitMode); FEX::CPUFeatures GetCPUFeaturesFromIDRegisters(); } // namespace FEX diff --git a/Source/Tools/CodeSizeValidation/Main.cpp b/Source/Tools/CodeSizeValidation/Main.cpp index 94e4c75cd..c3ed88f4e 100644 --- a/Source/Tools/CodeSizeValidation/Main.cpp +++ b/Source/Tools/CodeSizeValidation/Main.cpp @@ -702,7 +702,7 @@ int main(int argc, char** argv, char** const envp) { // Create FEXCore context. fextl::unique_ptr CTX; { - auto HostFeatures = FEX::FetchHostFeatures(); + auto HostFeatures = FEX::FetchHostFeatures(TestHeaderData->Bitness == 64); HostFeatures.IsInstCountCI = true; CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures); } diff --git a/Source/Tools/FEXInterpreter/FEXInterpreter.cpp b/Source/Tools/FEXInterpreter/FEXInterpreter.cpp index 52bdba1e6..2881ad9ce 100644 --- a/Source/Tools/FEXInterpreter/FEXInterpreter.cpp +++ b/Source/Tools/FEXInterpreter/FEXInterpreter.cpp @@ -550,7 +550,7 @@ int main(int argc, char** argv, char** const envp) { bool SupportsSVE256 {}; fextl::unique_ptr CTX; { - auto HostFeatures = FEX::FetchHostFeatures(); + auto HostFeatures = FEX::FetchHostFeatures(Loader.Is64BitMode()); CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures); SupportsAVX = HostFeatures.SupportsAVX; SupportsSVE256 = HostFeatures.SupportsSVE256; diff --git a/Source/Tools/FEXOfflineCompiler/Main.cpp b/Source/Tools/FEXOfflineCompiler/Main.cpp index 484aeb8c7..866d5d12e 100644 --- a/Source/Tools/FEXOfflineCompiler/Main.cpp +++ b/Source/Tools/FEXOfflineCompiler/Main.cpp @@ -410,7 +410,7 @@ static std::optional GenerateSingleCache(FEXCore::ExecutableFileInf // Load HostFeatures #ifndef _WIN32 - auto HostFeatures = FEX::FetchHostFeatures(); + auto HostFeatures = FEX::FetchHostFeatures(Is64Bit); #else const auto NtDll = GetModuleHandleW(L"ntdll.dll"); const bool IsWine = !!GetProcAddress(NtDll, "wine_get_version"); diff --git a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp index 420a03333..87e5d2d24 100644 --- a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp +++ b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp @@ -245,7 +245,7 @@ int main(int argc, char** argv, char** const envp) { bool SupportsAVX = false; FEXCore::Core::CPUState State; - auto HostFeatures = FEX::FetchHostFeatures(); + auto HostFeatures = FEX::FetchHostFeatures(Loader.Is64BitMode()); auto CTX = FEXCore::Context::Context::CreateNewContext(HostFeatures); #ifndef _WIN32 diff --git a/Source/Windows/Common/CPUFeatures.cpp b/Source/Windows/Common/CPUFeatures.cpp index 4da5c0803..bc4dfb08c 100644 --- a/Source/Windows/Common/CPUFeatures.cpp +++ b/Source/Windows/Common/CPUFeatures.cpp @@ -83,16 +83,15 @@ FEXCore::HostFeatures CPUFeatures::FetchHostFeatures(bool IsWine, FEXCore::HostF HostFeatures.SupportsCPUIndexInTPIDRRO = !IsWine; - HostFeatures.HostType = HostType; HostFeatures.ProcessPID = ProcessPID; - if (HostType == FEXCore::HostFeatures::HostTypeEnum::Wow64) { - // AVX is unsupported for WOW64 - HostFeatures.SupportsAVX = false; - } else { - // arm64ec supports synchronous faults without a double-fault dance. - HostFeatures.SupportsFaultFreeSynchronousFaults = true; - } + // AVX is unsupported for WOW64 + HostFeatures.SupportsAVX = HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec; + + HostFeatures.ProcessFeatures.HostType = HostType; + HostFeatures.ProcessFeatures.Is64BitMode = HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec; + // arm64ec supports synchronous faults without a double-fault dance. + HostFeatures.ProcessFeatures.SupportsFaultFreeSynchronousFaults = HostType == FEXCore::HostFeatures::HostTypeEnum::Arm64ec; return HostFeatures; }