IR: Fix lwl/lwr pairing into the base reg, and sp validation past barriers

- An lwl/lwr pair was combined into one load even when the first half loads
  into the base register, which changes the address of the second half.
- ApplyMemoryValidation shared one sp check across the block even past an
  Interpret or CallReplacement, which may change sp.
- Drop a duplicate FSqrt meta entry, and name Load8Ext correctly.

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 1dbb62d570
commit 25e8f1d57c
3 files changed
+64 -5

No files matched your search

+1 -2
View File
@@ -78,7 +78,7 @@ static const IRMeta irMeta[] = {
{ IROp::Ext16to32, "Ext16to32", "GG" },
{ IROp::ReverseBits, "ReverseBits", "GG" },
{ IROp::Load8, "Load8", "GGC" },
{ IROp::Load8Ext, "Load8", "GGC" },
{ IROp::Load8Ext, "Load8Ext", "GGC" },
{ IROp::Load16, "Load16", "GGC" },
{ IROp::Load16Ext, "Load16Ext", "GGC" },
{ IROp::Load32, "Load32", "GGC" },
@@ -105,7 +105,6 @@ static const IRMeta irMeta[] = {
{ IROp::FSqrt, "FSqrt", "FF" },
{ IROp::FSin, "FSin", "FF" },
{ IROp::FCos, "FCos", "FF" },
{ IROp::FSqrt, "FSqrt", "FF" },
{ IROp::FRSqrt, "FRSqrt", "FF" },
{ IROp::FRecip, "FRecip", "FF" },
{ IROp::FAsin, "FAsin", "FF" },
+7 -2
View File
@@ -254,6 +254,9 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions
const IRInst &next = nextOp();
if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1)
return false;
// A load into its own base changes the address the second half reads from.
if (replaceOp == IROp::Load32 && inst.dest == inst.src1)
return false;
if (inst.constant + matchOff != next.constant)
return false;
@@ -1863,9 +1866,11 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o
}
const IRMeta *m = GetIRMeta(inst.op);
if (m->types[0] == 'G' && (m->flags & IRFLAG_SRC3) == 0 && inst.dest == MIPS_REG_SP) {
bool writesSP = m->types[0] == 'G' && (m->flags & IRFLAG_SRC3) == 0 && inst.dest == MIPS_REG_SP;
// A barrier (Interpret, CallReplacement) may write any GPR.
if (writesSP || (m->flags & IRFLAG_BARRIER) != 0) {
// We only care if it changes after we start combining.
spModified = spUpper != -1;
spModified = spModified || spUpper != -1;
}
}
+56 -1
View File
@@ -18,6 +18,7 @@
#include <cstdio>
#include <cstring>
#include "Core/MIPS/IR/IRInst.h"
#include "Core/Config.h"
#include "Core/MIPS/IR/IRPassSimplify.h"
struct IRVerification {
@@ -25,6 +26,10 @@ struct IRVerification {
const std::vector<IRInst> input;
const std::vector<IRInst> expected;
const std::vector<IRPassFunc> passes;
// Leaves lwl/lwr halves alone, among other things.
bool optimizeForInterpreter = false;
// ApplyMemoryValidation only runs without fast memory.
bool slowMemory = false;
};
static void LogInstructions(const std::vector<IRInst> &insts) {
@@ -39,10 +44,15 @@ static bool VerifyPass(const IRVerification &v) {
IRWriter in, out;
IROptions opts{};
opts.unalignedLoadStore = true;
opts.optimizeForInterpreter = v.optimizeForInterpreter;
for (const auto &inst : v.input)
in.Write(inst);
if (IRApplyPasses(v.passes.data(), v.passes.size(), in, out, opts)) {
const bool fastMemory = g_Config.bFastMemory;
g_Config.bFastMemory = !v.slowMemory;
bool logged = IRApplyPasses(v.passes.data(), v.passes.size(), in, out, opts);
g_Config.bFastMemory = fastMemory;
if (logged) {
printf("%s FAILED: Unable to apply passes (or wanted to log)\n", v.name);
return false;
}
@@ -267,6 +277,51 @@ static const IRVerification tests[] = {
},
{ &PurgeTemps },
},
{
"CombineLoadLeftRight",
{
{ IROp::Load32Left, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 3 },
{ IROp::Load32Right, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 0 },
},
{
{ IROp::Load32, { MIPS_REG_A0 }, MIPS_REG_A1, 0, 0 },
},
{ &RemoveLoadStoreLeftRight },
true,
},
{
// The lwl changes a0, so the lwr reads from a different address.
"NoCombineLoadLeftRightIntoBase",
{
{ IROp::Load32Left, { MIPS_REG_A0 }, MIPS_REG_A0, 0, 3 },
{ IROp::Load32Right, { MIPS_REG_A0 }, MIPS_REG_A0, 0, 0 },
},
{
{ IROp::Load32Left, { MIPS_REG_A0 }, MIPS_REG_A0, 0, 3 },
{ IROp::Load32Right, { MIPS_REG_A0 }, MIPS_REG_A0, 0, 0 },
},
{ &RemoveLoadStoreLeftRight },
true,
},
{
// The sp accesses share one validation, but not across something that may change sp.
"ValidateSPAcrossInterpret",
{
{ IROp::Load32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 },
{ IROp::Interpret, { 0 }, 0, 0, 0 },
{ IROp::Load32, { MIPS_REG_A1 }, MIPS_REG_SP, 0, 8 },
},
{
{ IROp::ValidateAddress32, { 0 }, MIPS_REG_SP, 0, 0 },
{ IROp::Load32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 },
{ IROp::Interpret, { 0 }, 0, 0, 0 },
{ IROp::ValidateAddress32, { 0 }, MIPS_REG_SP, 0, 8 },
{ IROp::Load32, { MIPS_REG_A1 }, MIPS_REG_SP, 0, 8 },
},
{ &ApplyMemoryValidation },
false,
true,
},
};
bool TestIRPassSimplify() {