IR interpreter: Merge a conditional exit with the ExitToConst after it

Blocks ending in a branch dispatch a conditional exit and then the
fallthrough ExitToConst. One op now returns either target, reading the
second from the ExitToConst, which stays behind unexecuted.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-25 11:47:17 -06:00
1 parent c6aea794f6
commit 7eb371b231
5 files changed
+53

No files matched your search

+6
View File
@@ -131,6 +131,12 @@ static const IRMeta irMeta[] = {
{ IROp::FMovToGPR, "FMovToGPR", "GF" },
{ IROp::OptFMovToGPRShr8, "OptFMovToGPRShr8", "GF" },
{ IROp::OptFCvtSWFromGPR, "OptFCvtSWFromGPR", "FG" },
{ IROp::OptExitToConstIfEqElse, "OptExitIfEqElse", "CGG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfNeqElse, "OptExitIfNeqElse", "CGG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfGtZElse, "OptExitIfGtZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfGeZElse, "OptExitIfGeZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfLtZElse, "OptExitIfLtZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfLeZElse, "OptExitIfLeZElse", "CG", IRFLAG_EXIT },
{ IROp::FpCondFromReg, "FpCondFromReg", "_G" },
{ IROp::FpCondToReg, "FpCondToReg", "G" },
{ IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" },
+8
View File
@@ -141,6 +141,14 @@ enum class IROp : uint8_t {
OptFCvtSWFromGPR,
FMovToGPR,
OptFMovToGPRShr8,
// A conditional exit merged with the ExitToConst after it, which stays behind as the target
// to take otherwise (in its constant).
OptExitToConstIfEqElse,
OptExitToConstIfNeqElse,
OptExitToConstIfGtZElse,
OptExitToConstIfGeZElse,
OptExitToConstIfLtZElse,
OptExitToConstIfLeZElse,
FSat0_1,
FSatMinus1_1,
+13
View File
@@ -1034,6 +1034,19 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
case IROp::ExitToReg:
return mips->r[inst->src1];
// The next instruction is the ExitToConst to take otherwise, never run itself.
case IROp::OptExitToConstIfEqElse:
return mips->r[inst->src1] == mips->r[inst->src2] ? inst->constant : inst[1].constant;
case IROp::OptExitToConstIfNeqElse:
return mips->r[inst->src1] != mips->r[inst->src2] ? inst->constant : inst[1].constant;
case IROp::OptExitToConstIfGtZElse:
return (s32)mips->r[inst->src1] > 0 ? inst->constant : inst[1].constant;
case IROp::OptExitToConstIfGeZElse:
return (s32)mips->r[inst->src1] >= 0 ? inst->constant : inst[1].constant;
case IROp::OptExitToConstIfLtZElse:
return (s32)mips->r[inst->src1] < 0 ? inst->constant : inst[1].constant;
case IROp::OptExitToConstIfLeZElse:
return (s32)mips->r[inst->src1] <= 0 ? inst->constant : inst[1].constant;
case IROp::ExitToConstIfEq:
if (mips->r[inst->src1] == mips->r[inst->src2])
return inst->constant;
+13
View File
@@ -2407,6 +2407,19 @@ bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions &
}
out.Write(inst);
break;
case IROp::ExitToConstIfEq:
case IROp::ExitToConstIfNeq:
case IROp::ExitToConstIfGtZ:
case IROp::ExitToConstIfGeZ:
case IROp::ExitToConstIfLtZ:
case IROp::ExitToConstIfLeZ:
// With an ExitToConst right after, one op can pick either target. The ExitToConst stays
// as the second target's holder.
static_assert((int)IROp::OptExitToConstIfLeZElse - (int)IROp::OptExitToConstIfEqElse == (int)IROp::ExitToConstIfLeZ - (int)IROp::ExitToConstIfEq, "Else exits must match the exits' order");
if (!last && in.GetInstructions()[i + 1].op == IROp::ExitToConst)
inst.op = (IROp)((int)IROp::OptExitToConstIfEqElse + ((int)inst.op - (int)IROp::ExitToConstIfEq));
out.Write(inst);
break;
default:
out.Write(inst);
break;
+13
View File
@@ -510,6 +510,19 @@ static const IRVerification tests[] = {
},
{ &OptimizeLoadsAfterStores },
},
{
"InterpreterExitElse",
{
{ IROp::ExitToConstIfLtZ, { 0 }, MIPS_REG_A0, 0, 0x08804000 },
{ IROp::ExitToConst, { 0 }, 0, 0, 0x08804100 },
},
{
{ IROp::Downcount, { 0 }, 0, 0, 0 },
{ IROp::OptExitToConstIfLtZElse, { 0 }, MIPS_REG_A0, 0, 0x08804000 },
{ IROp::ExitToConst, { 0 }, 0, 0, 0x08804100 },
},
{ &OptimizeForInterpreter },
},
};
bool TestIRPassSimplify() {