Jit64: Improve cmpXX imm handling

In the past, the register cache would contain either a host register or
an immediate (or neither) for a given guest register. Jit64::cmpXX would
roll with whichever one it got. But now that PR 12134 is merged, the
register cache can contain a host register at the same time as an
immediate. This gives us an optimization opportunity in Jit64::cmpXX.
Some paths are more efficient when we use an immediate and some paths
are more efficient when we don't, so to generate the best code, we
should carefully choose if we want an immediate or not rather than
leaving it to the register cache.
This commit is contained in:
JosJuice
2025-12-05 23:21:17 +01:00
parent 373e35ed5b
commit 711c3e1013
+57 -35
View File
@@ -568,39 +568,38 @@ void Jit64::cmpXX(UGeckoInstruction inst)
u32 crf = inst.CRFD;
bool merge_branch = CheckMergedBranch(crf);
bool signedCompare;
RCOpArg comparand;
bool signedCompare = false;
std::optional<u32> imm_comparand;
switch (inst.OPCD)
{
// cmp / cmpl
case 31:
signedCompare = (inst.SUBOP10 == 0);
comparand = signedCompare ? gpr.Use(b, RCMode::Read) : gpr.Bind(b, RCMode::Read);
RegCache::Realize(comparand);
if (gpr.IsImm(b))
imm_comparand = gpr.Imm32(b);
break;
// cmpli
case 10:
signedCompare = false;
comparand = RCOpArg::Imm32((u32)inst.UIMM);
imm_comparand = u32(inst.UIMM);
break;
// cmpi
case 11:
signedCompare = true;
comparand = RCOpArg::Imm32((u32)(s32)(s16)inst.UIMM);
imm_comparand = u32(s32(s16(inst.UIMM)));
break;
default:
signedCompare = false; // silence compiler warning
PanicAlertFmt("cmpXX");
}
if (gpr.IsImm(a) && comparand.IsImm())
if (gpr.IsImm(a) && imm_comparand.has_value())
{
// Both registers contain immediate values, so we can pre-compile the compare result
s64 compareResult = signedCompare ? (s64)gpr.SImm32(a) - (s64)comparand.SImm32() :
(u64)gpr.Imm32(a) - (u64)comparand.Imm32();
s64 compareResult = signedCompare ? s64(gpr.SImm32(a)) - s64(s32(imm_comparand.value())) :
u64(gpr.Imm32(a)) - u64(imm_comparand.value());
if (compareResult == (s32)compareResult)
{
MOV(64, PPCSTATE_CR(crf), Imm32((u32)compareResult));
@@ -612,15 +611,12 @@ void Jit64::cmpXX(UGeckoInstruction inst)
}
if (merge_branch)
{
RegCache::Unlock(comparand);
DoMergedBranchImmediate(compareResult);
}
return;
}
if (!gpr.IsImm(a) && !signedCompare && comparand.IsImm() && comparand.Imm32() == 0)
if (!gpr.IsImm(a) && !signedCompare && imm_comparand == u32(0))
{
RCX64Reg Ra = gpr.Bind(a, RCMode::Read);
RegCache::Realize(Ra);
@@ -629,12 +625,38 @@ void Jit64::cmpXX(UGeckoInstruction inst)
if (merge_branch)
{
TEST(64, Ra, Ra);
RegCache::Unlock(comparand, Ra);
RegCache::Unlock(Ra);
DoMergedBranchCondition();
}
return;
}
const bool comparand_needs_reg =
!signedCompare && imm_comparand.has_value() && (imm_comparand.value() & 0x80000000U) != 0;
RCOpArg Rb;
OpArg comparand;
if (inst.OPCD != 31)
{
// The comparand is from the instruction encoding, so we can't get it from the register cache.
comparand = Imm32(imm_comparand.value());
}
else if (signedCompare && imm_comparand.has_value())
{
// Asking the register cache for the comparand might give us a register, but we prefer an imm.
comparand = Imm32(imm_comparand.value());
}
else if (imm_comparand != u32(0))
{
// Ask the register cache for the comparand. We prefer getting a register,
// but may also accept imm or memory depending on the circumstances.
Rb = signedCompare ?
gpr.Use(b, RCMode::Read) :
(comparand_needs_reg ? gpr.Bind(b, RCMode::Read) : gpr.BindOrImm(b, RCMode::Read));
RegCache::Realize(Rb);
comparand = Rb;
}
const X64Reg input = RSCRATCH;
if (gpr.IsImm(a))
{
@@ -653,25 +675,7 @@ void Jit64::cmpXX(UGeckoInstruction inst)
MOVZX(64, 32, input, Ra);
}
if (comparand.IsImm())
{
// sign extension will ruin this, so store it in a register
if (!signedCompare && (comparand.Imm32() & 0x80000000U) != 0)
{
MOV(32, R(RSCRATCH2), comparand);
comparand = RCOpArg::R(RSCRATCH2);
}
}
else
{
if (signedCompare)
{
MOVSX(64, 32, RSCRATCH2, comparand);
comparand = RCOpArg::R(RSCRATCH2);
}
}
if (comparand.IsImm() && comparand.Imm32() == 0)
if (imm_comparand == u32(0))
{
MOV(64, PPCSTATE_CR(crf), R(input));
// Place the comparison next to the branch for macro-op fusion
@@ -680,13 +684,31 @@ void Jit64::cmpXX(UGeckoInstruction inst)
}
else
{
if (comparand.IsImm())
{
if (comparand_needs_reg)
{
// sign extension will ruin this, so store it in a register
MOV(32, R(RSCRATCH2), comparand);
comparand = R(RSCRATCH2);
}
}
else
{
if (signedCompare)
{
MOVSX(64, 32, RSCRATCH2, comparand);
comparand = R(RSCRATCH2);
}
}
SUB(64, R(input), comparand);
MOV(64, PPCSTATE_CR(crf), R(input));
}
if (merge_branch)
{
RegCache::Unlock(comparand);
RegCache::Unlock(Rb);
DoMergedBranchCondition();
}
}