IR: Keep more constants known in PropagateConstants

- A constant stays known after it's written out for a read (by a store,
  MovZ, a multiply...), so later uses still fold. Whatever an op writes is
  forgotten after its inputs are written, and setting a reg to the value it
  already holds isn't written twice.
- A conditional exit that isn't taken keeps the constants known.
- The saturating and min/max FP ops, FSign and the 31-bit Vec2 pack/unpack
  no longer flush every GPR constant.
- A load through its own base is folded (lui v0, hi; lw v0, lo(v0)).

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-24 16:35:11 -06:00
1 parent 25e8f1d57c
commit a99a82cc6b
4 files changed
+133 -20

No files matched your search

+31 -6
View File
@@ -627,6 +627,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
case IROp::MovZ:
case IROp::MovNZ:
gpr.MapInInIn(inst.dest, inst.src1, inst.src2);
// The dest is read, then maybe written.
gpr.MapDirty(inst.dest);
goto doDefault;
case IROp::Min:
@@ -664,11 +666,16 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
case IROp::Store32Left:
case IROp::Store32Right:
case IROp::Store32Conditional:
if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) {
if (gpr.IsImm(inst.src1)) {
gpr.MapIn(inst.dest);
// sc also writes its value reg, with the result.
if (inst.op == IROp::Store32Conditional)
gpr.MapDirty(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, gpr.GetImm(inst.src1) + inst.constant);
} else {
gpr.MapInIn(inst.dest, inst.src1);
if (inst.op == IROp::Store32Conditional)
gpr.MapDirty(inst.dest);
goto doDefault;
}
break;
@@ -688,9 +695,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
case IROp::Load16Ext:
case IROp::Load32:
case IROp::Load32Linked:
if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) {
if (gpr.IsImm(inst.src1)) {
// Read the address first, as the dest may be the base (lui v0, hi; lw v0, lo(v0)).
u32 addr = gpr.GetImm(inst.src1) + inst.constant;
gpr.MapDirty(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, gpr.GetImm(inst.src1) + inst.constant);
out.Write(inst.op, inst.dest, 0, 0, addr);
} else {
gpr.MapDirtyIn(inst.dest, inst.src1);
goto doDefault;
@@ -707,11 +716,15 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
break;
case IROp::Load32Left:
case IROp::Load32Right:
// These merge into the dest, so it's read, then written.
if (gpr.IsImm(inst.src1)) {
u32 addr = gpr.GetImm(inst.src1) + inst.constant;
gpr.MapIn(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, gpr.GetImm(inst.src1) + inst.constant);
gpr.MapDirty(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, addr);
} else {
gpr.MapInIn(inst.dest, inst.src1);
gpr.MapDirty(inst.dest);
goto doDefault;
}
break;
@@ -773,6 +786,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
case IROp::FLog2:
case IROp::FHalfToFloat:
case IROp::FSinCos:
case IROp::FMin:
case IROp::FMax:
case IROp::FSign:
case IROp::FSat0_1:
case IROp::FSatMinus1_1:
out.Write(inst);
break;
@@ -837,6 +855,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
case IROp::Vec2Pack32To16:
case IROp::Vec4Unpack8To32:
case IROp::Vec2Unpack16To32:
case IROp::Vec2Unpack16To31:
case IROp::Vec2Pack31To16:
case IROp::Vec4DuplicateUpperBitsAndShift1:
out.Write(inst);
break;
@@ -885,7 +905,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
}
break;
}
gpr.FlushAll();
// Only exits when taken, so the values stay known after.
gpr.FlushAll(true);
goto doDefault;
case IROp::ExitToConstIfGtZ:
@@ -909,7 +930,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
}
break;
}
gpr.FlushAll();
// Only exits when taken, so the values stay known after.
gpr.FlushAll(true);
goto doDefault;
case IROp::ExitToConst:
@@ -944,6 +966,9 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
{
gpr.FlushAll();
doDefault:
// Whatever the op writes isn't known anymore (its inputs were written out above).
if (GetIRMeta(inst.op)->types[0] == 'G' && (GetIRMeta(inst.op)->flags & IRFLAG_SRC3) == 0)
gpr.MapDirty(inst.dest);
out.Write(inst);
break;
}
+10 -13
View File
@@ -30,13 +30,10 @@
#include "Core/MIPS/JitCommon/JitState.h"
void IRImmRegCache::Flush(IRReg rd) {
if (isImm_[rd]) {
if (rd == 0) {
return;
}
if (isImm_[rd] && !isWritten_[rd]) {
_assert_((rd > 0 && rd < 32) || (rd >= IRTEMP_0 && rd < IRREG_VFPU_CTRL_BASE));
ir_->WriteSetConstant(rd, immVal_[rd]);
isImm_[rd] = false;
isWritten_[rd] = true;
}
}
@@ -49,19 +46,23 @@ void IRImmRegCache::Discard(IRReg rd) {
IRImmRegCache::IRImmRegCache(IRWriter *ir) : ir_(ir) {
memset(&isImm_, 0, sizeof(isImm_));
memset(&isWritten_, 0, sizeof(isWritten_));
memset(&immVal_, 0, sizeof(immVal_));
isImm_[0] = true;
isWritten_[0] = true;
ir_ = ir;
}
void IRImmRegCache::FlushAll() {
void IRImmRegCache::FlushAll(bool keepKnown) {
for (int i = 1; i < TOTAL_MAPPABLE_IRREGS; ) {
if (isImm_[i]) {
Flush(i);
if (!keepKnown)
isImm_[i] = false;
}
// Most of the time, lots are not. This speeds it up a lot.
bool *next = (bool *)memchr(&isImm_[i], 1, TOTAL_MAPPABLE_IRREGS - i);
bool *next = (bool *)memchr(&isImm_[i + 1], 1, TOTAL_MAPPABLE_IRREGS - i - 1);
if (!next)
break;
i = (int)(next - &isImm_[0]);
@@ -88,18 +89,14 @@ void IRImmRegCache::MapInInIn(IRReg rd, IRReg rs, IRReg rt) {
}
void IRImmRegCache::MapDirtyIn(IRReg rd, IRReg rs) {
if (rs != rd) {
Discard(rd);
}
Flush(rs);
Discard(rd);
}
void IRImmRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
if (rs != rd && rt != rd) {
Discard(rd);
}
Flush(rs);
Flush(rt);
Discard(rd);
}
IRNativeRegCacheBase::IRNativeRegCacheBase(MIPSComp::JitOptions *jo)
+10 -1
View File
@@ -49,6 +49,9 @@ public:
IRImmRegCache(IRWriter *ir);
void SetImm(IRReg r, u32 immVal) {
// Setting the value it already holds doesn't need writing again.
if (!isImm_[r] || immVal_[r] != immVal)
isWritten_[r] = false;
isImm_[r] = true;
immVal_[r] = immVal;
}
@@ -56,8 +59,12 @@ public:
bool IsImm(IRReg r) const { return isImm_[r]; }
u32 GetImm(IRReg r) const { return immVal_[r]; }
void FlushAll();
// Writes out all pending values. With keepKnown, the values stay known afterward (for a
// conditional exit), otherwise they're forgotten (anything may change them after a barrier).
void FlushAll(bool keepKnown = false);
// The Map functions write out the values of the regs read, which stay known. A dirty reg
// is forgotten after its inputs are written.
void MapDirty(IRReg rd);
void MapIn(IRReg rd);
void MapInIn(IRReg rs, IRReg rt);
@@ -70,6 +77,8 @@ private:
void Discard(IRReg rd);
bool isImm_[TOTAL_MAPPABLE_IRREGS];
// Whether the known value has been written to the reg.
bool isWritten_[TOTAL_MAPPABLE_IRREGS];
uint32_t immVal_[TOTAL_MAPPABLE_IRREGS];
IRWriter *ir_;
};
+82
View File
@@ -322,6 +322,88 @@ static const IRVerification tests[] = {
false,
true,
},
{
"PropagateConstantsPastFSat",
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0x100 },
{ IROp::FSat0_1, { 1 }, 1 },
{ IROp::Add, { MIPS_REG_A1 }, MIPS_REG_A0, MIPS_REG_A0 },
},
{
{ IROp::FSat0_1, { 1 }, 1 },
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0x100 },
{ IROp::SetConst, { MIPS_REG_A1 }, 0, 0, 0x200 },
},
{ &PropagateConstants },
},
{
// lui v0, hi; lw v0, lo(v0)
"PropagateConstantsLoadIntoBase",
{
{ IROp::SetConst, { MIPS_REG_V0 }, 0, 0, 0x08810000 },
{ IROp::Load32, { MIPS_REG_V0 }, MIPS_REG_V0, 0, 0x20 },
},
{
{ IROp::Load32, { MIPS_REG_V0 }, MIPS_REG_ZERO, 0, 0x08810020 },
},
{ &PropagateConstants },
},
{
// The store writes a0 out, and it stays known.
"PropagateConstantsPastStore",
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 },
{ IROp::AddConst, { MIPS_REG_A1 }, MIPS_REG_A0, 0, 0x20 },
},
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 },
{ IROp::SetConst, { MIPS_REG_A1 }, 0, 0, 0x25 },
},
{ &PropagateConstants },
},
{
"PropagateConstantsPastCondExit",
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::ExitToConstIfEq, { 0 }, MIPS_REG_A1, MIPS_REG_A2, 0x08804000 },
{ IROp::AddConst, { MIPS_REG_A3 }, MIPS_REG_A0, 0, 1 },
},
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::ExitToConstIfEq, { 0 }, MIPS_REG_A1, MIPS_REG_A2, 0x08804000 },
{ IROp::SetConst, { MIPS_REG_A3 }, 0, 0, 6 },
},
{ &PropagateConstants },
},
{
// Each of these may change a0 after reading it.
"PropagateConstantsReadThenWritten",
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::MovZ, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 },
{ IROp::AddConst, { MIPS_REG_T0 }, MIPS_REG_A0, 0, 1 },
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Store32Conditional, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 0 },
{ IROp::AddConst, { MIPS_REG_T1 }, MIPS_REG_A0, 0, 1 },
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Load32Left, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 3 },
{ IROp::AddConst, { MIPS_REG_T2 }, MIPS_REG_A0, 0, 1 },
},
{
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::MovZ, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 },
{ IROp::AddConst, { MIPS_REG_T0 }, MIPS_REG_A0, 0, 1 },
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Store32Conditional, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 0 },
{ IROp::AddConst, { MIPS_REG_T1 }, MIPS_REG_A0, 0, 1 },
{ IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 5 },
{ IROp::Load32Left, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 3 },
{ IROp::AddConst, { MIPS_REG_T2 }, MIPS_REG_A0, 0, 1 },
},
{ &PropagateConstants },
},
};
bool TestIRPassSimplify() {