mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
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:
1 parent
25e8f1d57c
commit
a99a82cc6b
4 files changed
+133
-20
No files matched your search
@@ -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
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user