From 651b94f43541c8595ee4404b6078eebc7d32408a Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 19 Oct 2026 13:44:39 -0700 Subject: [PATCH] OpcodeDispatcher: Add undefined flags acid test Useful debug feature to make sure FEX returns consistent flag data for instructions that have undefined flags. Just sets everything that is undefined to zero always. Manually walked through all the instructions in the architecture manual to check for ones doing undefined flag setting, then glued the handler on to the end. Also fixes a comment that has incorrect information about undefined flags. --- .../Source/Interface/Config/Config.json.in | 9 +++ .../Interface/Core/OpcodeDispatcher.cpp | 68 +++++++++++++++++++ .../Source/Interface/Core/OpcodeDispatcher.h | 53 ++++++++++++++- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/FEXCore/Source/Interface/Config/Config.json.in b/FEXCore/Source/Interface/Config/Config.json.in index 938377f32..f8fa936e3 100644 --- a/FEXCore/Source/Interface/Config/Config.json.in +++ b/FEXCore/Source/Interface/Config/Config.json.in @@ -354,6 +354,15 @@ "Can potentially introduce more stutters, more likely the higher the number.", "Don't have this number larger than the increase count!" ] + }, + "UndefinedFlagAcidTest": { + "Type": "bool", + "Default": "false", + "AffectsCodeGen": "true", + "Desc": [ + "Ensures that all undefined flags generate consistent results", + "This is a useful test to determine if undefined flags are causing problems" + ] } }, "Debug": { diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 8dc605ca7..cae9c6cdd 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -1004,6 +1004,7 @@ void OpDispatchBuilder::TESTOp(OpcodeArgs, uint32_t SrcIndex, bool DestRAX) { } InvalidateAF(); + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::ARPLOp(OpcodeArgs) { @@ -1410,6 +1411,7 @@ void OpDispatchBuilder::SHLOp(OpcodeArgs) { Ref Result = _Lshl(Size == OpSize::i64Bit ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src); HandleShift(Op, Result, Dest, ShiftType::LSL, Src); + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHLImmediateOp(OpcodeArgs, bool SHL1Bit) { @@ -1423,6 +1425,7 @@ void OpDispatchBuilder::SHLImmediateOp(OpcodeArgs, bool SHL1Bit) { CalculateFlags_ShiftLeftImmediate(OpSizeFromSrc(Op), Result, Dest, Shift); CalculateDeferredFlags(); StoreResultGPR(Op, Result); + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHROp(OpcodeArgs) { @@ -1432,6 +1435,8 @@ void OpDispatchBuilder::SHROp(OpcodeArgs) { auto ALUOp = _Lshr(std::max(OpSize::i32Bit, Size), Dest, Src); HandleShift(Op, ALUOp, Dest, ShiftType::LSR, Src); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHRImmediateOp(OpcodeArgs, bool SHR1Bit) { @@ -1444,6 +1449,8 @@ void OpDispatchBuilder::SHRImmediateOp(OpcodeArgs, bool SHR1Bit) { CalculateFlags_ShiftRightImmediate(OpSizeFromSrc(Op), ALUOp, Dest, Shift); CalculateDeferredFlags(); StoreResultGPR(Op, ALUOp); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHLDOp(OpcodeArgs) { @@ -1487,6 +1494,8 @@ void OpDispatchBuilder::SHLDOp(OpcodeArgs) { Res = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::EQ, Shift, Constant(0), Dest, Res); HandleShift(Op, Res, Dest, ShiftType::LSL, Shift); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHLDImmediateOp(OpcodeArgs) { @@ -1518,6 +1527,8 @@ void OpDispatchBuilder::SHLDImmediateOp(OpcodeArgs) { // Ensure Zext still occurs StoreResultGPR(Op, Dest); } + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHRDOp(OpcodeArgs) { @@ -1552,6 +1563,8 @@ void OpDispatchBuilder::SHRDOp(OpcodeArgs) { Res = _Select(OpSize::i64Bit, OpSize::i64Bit, CondClass::EQ, Shift, Constant(0), Dest, Res); HandleShift(Op, Res, Dest, ShiftType::LSR, Shift); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::SHRDImmediateOp(OpcodeArgs) { @@ -1582,6 +1595,8 @@ void OpDispatchBuilder::SHRDImmediateOp(OpcodeArgs) { // Ensure Zext still occurs StoreResultGPR(Op, Dest); } + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::ASHROp(OpcodeArgs, bool Immediate, bool SHR1Bit) { @@ -1611,6 +1626,8 @@ void OpDispatchBuilder::ASHROp(OpcodeArgs, bool Immediate, bool SHR1Bit) { HandleShift(Op, Result, Dest, ShiftType::ASR, Src); } + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); } void OpDispatchBuilder::RotateOp(OpcodeArgs, bool Left, bool IsImmediate, bool Is1Bit) { @@ -1681,6 +1698,8 @@ void OpDispatchBuilder::ANDNBMIOp(OpcodeArgs) { StoreResultGPR(Op, Dest); CalculateFlags_Logical(OpSizeFromSrc(Op), Dest); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::BEXTRBMIOp(OpcodeArgs) { @@ -1724,6 +1743,8 @@ void OpDispatchBuilder::BEXTRBMIOp(OpcodeArgs) { // AF are undefined. SetNZ_ZeroCV(GetOpSize(Dest), Dest); InvalidatePF_AF(); + + AcidTestFlags(X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::BLSIBMIOp(OpcodeArgs) { @@ -1745,6 +1766,8 @@ void OpDispatchBuilder::BLSIBMIOp(OpcodeArgs) { SetNZ_ZeroCV(Size, Result); InvalidatePF_AF(); SetCFInverted(GetRFLAG(X86State::RFLAG_ZF_RAW_LOC)); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::BLSMSKBMIOp(OpcodeArgs) { @@ -1765,6 +1788,8 @@ void OpDispatchBuilder::BLSMSKBMIOp(OpcodeArgs) { // and O) while setting S. SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::BLSRBMIOp(OpcodeArgs) { @@ -1782,6 +1807,8 @@ void OpDispatchBuilder::BLSRBMIOp(OpcodeArgs) { SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); InvalidatePF_AF(); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } // Handles SARX, SHLX, and SHRX @@ -1841,6 +1868,8 @@ void OpDispatchBuilder::BZHI(OpcodeArgs) { InvalidatePF_AF(); SetNZ_ZeroCV(Size, Result); SetCFInverted(CFInv); + + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::RORX(OpcodeArgs) { @@ -2565,6 +2594,9 @@ void OpDispatchBuilder::BTOp(OpcodeArgs, uint32_t SrcIndex, BTAction Action) { // to insert into CF directly. We could optimize perhaps. SetCFDirect(Value, 0, true); } + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, + X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::IMUL1SrcOp(OpcodeArgs) { @@ -2604,6 +2636,8 @@ void OpDispatchBuilder::IMUL1SrcOp(OpcodeArgs) { StoreResultGPR(Op, Dest); CalculateFlags_MUL(Size, Dest, ResultHigh); + + AcidTestFlags(X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::IMUL2SrcOp(OpcodeArgs) { @@ -2643,6 +2677,8 @@ void OpDispatchBuilder::IMUL2SrcOp(OpcodeArgs) { StoreResultGPR(Op, Dest); CalculateFlags_MUL(Size, Dest, ResultHigh); + + AcidTestFlags(X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::IMULOp(OpcodeArgs) { @@ -2698,6 +2734,8 @@ void OpDispatchBuilder::IMULOp(OpcodeArgs) { } CalculateFlags_MUL(Size, Result, ResultHigh); + + AcidTestFlags(X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::MULOp(OpcodeArgs) { @@ -2868,6 +2906,8 @@ void OpDispatchBuilder::DAAOp(OpcodeArgs) { SetCFInverted(CFInv); CalculatePF(AL); SetAFAndFixup(AF); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC); } void OpDispatchBuilder::DASOp(OpcodeArgs) { @@ -2894,6 +2934,8 @@ void OpDispatchBuilder::DASOp(OpcodeArgs) { SetCFDirect(NewCF); CalculatePF(AL); SetAFAndFixup(AF); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC); } void OpDispatchBuilder::AAAOp(OpcodeArgs) { @@ -2911,6 +2953,7 @@ void OpDispatchBuilder::AAAOp(OpcodeArgs) { // AL = AL & 0x0F A = _And(OpSize::i32Bit, A, Constant(0xFF0F)); StoreGPRRegister(X86State::REG_RAX, A, OpSize::i16Bit); + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::AASOp(OpcodeArgs) { @@ -2928,6 +2971,8 @@ void OpDispatchBuilder::AASOp(OpcodeArgs) { // AL = AL & 0x0F A = _And(OpSize::i32Bit, A, Constant(0xFF0F)); StoreGPRRegister(X86State::REG_RAX, A, OpSize::i16Bit); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::AAMOp(OpcodeArgs) { @@ -2942,6 +2987,8 @@ void OpDispatchBuilder::AAMOp(OpcodeArgs) { SetNZ_ZeroCV(OpSize::i8Bit, Res); CalculatePF(Res); InvalidateAF(); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::AADOp(OpcodeArgs) { @@ -2955,6 +3002,8 @@ void OpDispatchBuilder::AADOp(OpcodeArgs) { SetNZ_ZeroCV(OpSize::i8Bit, Result); CalculatePF(Result); InvalidateAF(); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::XLATOp(OpcodeArgs) { @@ -3754,6 +3803,10 @@ void OpDispatchBuilder::DIVOp(OpcodeArgs) { StoreGPRRegister(X86State::REG_RAX, Quotient, Size); StoreGPRRegister(X86State::REG_RDX, Remainder, Size); } + + // All flags are undefined. + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, + X86State::RFLAG_PF_RAW_LOC, X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::IDIVOp(OpcodeArgs) { @@ -3797,6 +3850,10 @@ void OpDispatchBuilder::IDIVOp(OpcodeArgs) { StoreGPRRegister(X86State::REG_RAX, Quotient, Size); StoreGPRRegister(X86State::REG_RDX, Remainder, Size); } + + // All flags are undefined. + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_ZF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, + X86State::RFLAG_PF_RAW_LOC, X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::BSFOp(OpcodeArgs) { @@ -3819,6 +3876,9 @@ void OpDispatchBuilder::BSFOp(OpcodeArgs) { // applications might rely on that in the wild. auto SelectOp = NZCVSelect(GPRSize, CondClass::EQ, Dest, Result); StoreResultGPR_WithOpSize(Op, Op->Dest, SelectOp, DstSize); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC, + X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::BSROp(OpcodeArgs) { @@ -3837,6 +3897,9 @@ void OpDispatchBuilder::BSROp(OpcodeArgs) { // If Src was zero then the destination doesn't get modified auto SelectOp = NZCVSelect(GPRSize, CondClass::EQ, Dest, Result); StoreResultGPR_WithOpSize(Op, Op->Dest, SelectOp, DstSize); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC, + X86State::RFLAG_CF_RAW_LOC); } void OpDispatchBuilder::CMPXCHGOp(OpcodeArgs) { @@ -4782,12 +4845,14 @@ void OpDispatchBuilder::ALUOp(OpcodeArgs, FEXCore::IR::IROps ALUIROp, FEXCore::I case FEXCore::IR::IROps::OP_AND: case FEXCore::IR::IROps::OP_OR: { CalculateFlags_Logical(Size, Result); + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); break; } case FEXCore::IR::IROps::OP_ANDWITHFLAGS: { HandleNZ00Write(); CalculatePF(Result); InvalidateAF(); + AcidTestFlags(X86State::RFLAG_AF_RAW_LOC); break; } default: break; @@ -4938,6 +5003,8 @@ void OpDispatchBuilder::TZCNT(OpcodeArgs) { StoreResultGPR(Op, Src); CalculateFlags_ZCNT(OpSizeFromSrc(Op), Src); + + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::LZCNT(OpcodeArgs) { @@ -4947,6 +5014,7 @@ void OpDispatchBuilder::LZCNT(OpcodeArgs) { auto Res = _CountLeadingZeroes(OpSizeFromSrc(Op), Src); StoreResultGPR(Op, Res); CalculateFlags_ZCNT(OpSizeFromSrc(Op), Res); + AcidTestFlags(X86State::RFLAG_OF_RAW_LOC, X86State::RFLAG_SF_RAW_LOC, X86State::RFLAG_AF_RAW_LOC, X86State::RFLAG_PF_RAW_LOC); } void OpDispatchBuilder::MOVBEOp(OpcodeArgs) { diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h index 71766e010..d410fdf5e 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h @@ -1321,6 +1321,7 @@ protected: private: FEX_CONFIG_OPT(ReducedPrecisionMode, X87REDUCEDPRECISION); + FEX_CONFIG_OPT(UndefinedFlagAcidTest, UNDEFINEDFLAGACIDTEST); struct JumpTargetInfo { Ref BlockEntry; @@ -1904,6 +1905,55 @@ private: InvalidateReg(Core::CPUState::AF_AS_GREG); } + // Sets the desired flags to zero when acid test is enabled. + // Not meant to be fast, just meant to produce consistent results. + void AcidTestFlags(unsigned BitOffset = 0, unsigned BitOffset2 = 0, unsigned BitOffset3 = 0, unsigned BitOffset4 = 0, + unsigned BitOffset5 = 0, unsigned BitOffset6 = 0) { + if (!UndefinedFlagAcidTest) [[likely]] { + return; + } + + unsigned BitFlags {}; + if (BitOffset) { + BitFlags |= 1U << BitOffset; + } + if (BitOffset2) { + BitFlags |= 1U << BitOffset2; + } + if (BitOffset3) { + BitFlags |= 1U << BitOffset3; + } + if (BitOffset4) { + BitFlags |= 1U << BitOffset4; + } + if (BitOffset5) { + BitFlags |= 1U << BitOffset5; + } + if (BitOffset6) { + BitFlags |= 1U << BitOffset6; + } + + auto Flag = Constant(0); + if (BitFlags & (1U << X86State::RFLAG_SF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_SF_RAW_LOC); + } + if (BitFlags & (1U << X86State::RFLAG_ZF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_ZF_RAW_LOC); + } + if (BitFlags & (1U << X86State::RFLAG_CF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_CF_RAW_LOC); + } + if (BitFlags & (1U << X86State::RFLAG_OF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_OF_RAW_LOC); + } + if (BitFlags & (1U << X86State::RFLAG_AF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_AF_RAW_LOC); + } + if (BitFlags & (1U << X86State::RFLAG_AF_RAW_LOC)) { + SetRFLAG(Flag, FEXCore::X86State::RFLAG_AF_RAW_LOC); + } + } + [[nodiscard]] static CondClass CondForNZCVBit(unsigned BitOffset, bool Invert) { switch (BitOffset) { @@ -2207,8 +2257,7 @@ private: Ref V_inv = GetRFLAG(FEXCore::X86State::RFLAG_OF_RAW_LOC, true); SetRFLAG(V_inv); - // Intel: OF, SF, and AF set to zero - // AMD: no mention of OF, SF and AF but actual hardware seems to always zero + // Both Intel and AMD manual state that OF, SF, and AF are set to zero. // // Zero AF. Note that the comparison sets the raw PF to 0/1 above, so // PF[4] is 0 so the XOR with PF will have no effect, so setting the AF