Jit64: Flush carry flag in FallBackToInterpreter

We have an optimization where the guest carry flag is kept in the host
carry flag between certain back-to-back pairs of integer instructions.
If the second instruction falls back to the interpreter, then
FallBackToInterpreter should flush the carry flag to m_ppc_state,
otherwise the interpreter reads a stale carry flag and at some later
point Jit64 trips the "Attempt to modify flags while flags locked!"
assertion.

An alternative solution would be to not store the guest carry flag in
the host carry flag to begin with if we know the next instruction is
going to fall back to the interpreter, but knowing that in advance is
non-trivial. Since interpreter fallbacks aren't exactly intended to be
super optimized, I went for the flushing solution instead, which is how
JitArm64 already works. In most cases, the emitted code shouldn't even
differ between these two solutions.

Note that the problematic situation only happens if the first integer
instruction doesn't fall back to the interpreter but the second one
does. This used to be impossible because there's no "JIT disable"
setting that's granular enough to disable some integer instructions but
not all, but with the constant propagation PR, it's possible if constant
propagation is able to entirely evaluate the first instruction but not
the second.
This commit is contained in:
JosJuice 2025-11-29 11:40:05 +01:00
parent 887c68eed4
commit 0c024de591
3 changed files with 21 additions and 0 deletions

View File

@ -351,6 +351,7 @@ void Jit64::Shutdown()
void Jit64::FallBackToInterpreter(UGeckoInstruction inst)
{
FlushCarry();
gpr.Flush(BitSet32(0xFFFFFFFF), RegCache::IgnoreDiscardedRegisters::Yes);
fpr.Flush(BitSet32(0xFFFFFFFF), RegCache::IgnoreDiscardedRegisters::Yes);

View File

@ -124,6 +124,7 @@ public:
void FinalizeCarryOverflow(bool oe, bool inv = false);
void FinalizeCarry(Gen::CCFlags cond);
void FinalizeCarry(bool ca);
void FlushCarry();
void ComputeRC(preg_t preg, bool needs_test = true, bool needs_sext = true);
void FinalizeImmediateRC(s32 value);

View File

@ -137,6 +137,25 @@ void Jit64::FinalizeCarryOverflow(bool oe, bool inv)
FinalizeCarry(inv ? CC_NC : CC_C);
}
void Jit64::FlushCarry()
{
switch (js.carryFlag)
{
case CarryFlag::InPPCState:
break;
case CarryFlag::InHostCarry:
JitSetCAIf(CC_C);
UnlockFlags();
break;
case CarryFlag::InHostCarryInverted:
JitSetCAIf(CC_NC);
UnlockFlags();
break;
}
js.carryFlag = CarryFlag::InPPCState;
}
// Be careful; only set needs_test to false if we can be absolutely sure flags don't need
// to be recalculated and haven't been clobbered. Keep in mind not all instructions set
// sufficient flags -- for example, the flags from SHL/SHR are *not* sufficient for LT/GT