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.
This commit is contained in:
Ryan Houdek committed 2026-10-19 13:50:34 -07:00
1 parent 741e146fdf
commit 651b94f435
3 files changed
+128 -2

No files matched your search

@@ -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": {
@@ -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) {
@@ -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<FEXCore::X86State::RFLAG_PF_RAW_LOC>(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