// Copyright (c) 2022- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include #include "Core/MIPS/IR/IRInst.h" #include "Core/Config.h" #include "Core/MIPS/IR/IRPassSimplify.h" struct IRVerification { const char *name; const std::vector input; const std::vector expected; const std::vector 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 &insts) { for (size_t i = 0; i < insts.size(); ++i) { char buf[256]; DisassembleIR(buf, sizeof(buf), insts[i]); printf(" %s\n", buf); } } 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); 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; } const std::vector actual = out.GetInstructions(); if (actual.size() != v.expected.size()) { printf("%s FAILED: produced %d instructions, expected %d\n", v.name, (int)actual.size(), (int)v.expected.size()); printf("Actual:\n"); LogInstructions(actual); printf("Expected:\n"); LogInstructions(v.expected); return false; } for (size_t i = 0; i < actual.size(); ++i) { if (memcmp(&v.expected[i], &actual[i], sizeof(IRInst)) != 0) { char actualBuf[256]; DisassembleIR(actualBuf, sizeof(actualBuf), actual[i]); char expectedBuf[256]; DisassembleIR(expectedBuf, sizeof(expectedBuf), v.expected[i]); if (strcmp(expectedBuf, actualBuf) == 0) { // This means a field (like src2) was left set but isn't relevant. Ignore. continue; } printf("%s FAILED: #%d expected '%s' but was '%s'\n", v.name, (int)i, expectedBuf, actualBuf); printf("Actual:\n"); LogInstructions(actual); return false; } } return true; } static const IRVerification tests[] = { { "SimplePurgeTemps", { { IROp::Add, { IRTEMP_0 }, MIPS_REG_A0, MIPS_REG_A1 }, { IROp::Mov, { MIPS_REG_V0 }, IRTEMP_0 }, { IROp::Add, { IRTEMP_0 }, MIPS_REG_A2, MIPS_REG_A3 }, { IROp::Mov, { MIPS_REG_V1 }, IRTEMP_0 }, }, { { IROp::Add, { MIPS_REG_V0 }, MIPS_REG_A0, MIPS_REG_A1 }, { IROp::Add, { MIPS_REG_V1 }, MIPS_REG_A2, MIPS_REG_A3 }, }, { &PurgeTemps }, }, { "Load32LeftPurgeTemps", { { IROp::Mov, { IRTEMP_LR_ADDR }, MIPS_REG_A0 }, { IROp::AndConst, { IRTEMP_LR_ADDR }, IRTEMP_LR_ADDR, 0, 0xFFFFFFFC }, { IROp::Load32, { MIPS_REG_V0 }, IRTEMP_LR_ADDR, 0, 0 }, }, { { IROp::AndConst, { IRTEMP_LR_ADDR }, MIPS_REG_A0, 0, 0xFFFFFFFC }, { IROp::Load32, { MIPS_REG_V0 }, IRTEMP_LR_ADDR, 0, 0 }, }, { &PurgeTemps }, }, { "SwapClobberTemp", { { IROp::Sub, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Mov, { MIPS_REG_S0 }, MIPS_REG_A0 }, { IROp::Slt, { MIPS_REG_A0 }, MIPS_REG_V0, MIPS_REG_V1 }, }, { { IROp::Sub, { MIPS_REG_S0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Slt, { MIPS_REG_A0 }, MIPS_REG_V0, MIPS_REG_V1 }, }, { &PurgeTemps }, }, { "DoubleClobberTemp", { { IROp::Add, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Mov, { MIPS_REG_S0 }, MIPS_REG_A0 }, { IROp::Mov, { MIPS_REG_S0 }, MIPS_REG_A1 }, }, { { IROp::Add, { MIPS_REG_S0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Mov, { MIPS_REG_A0 }, MIPS_REG_S0 }, { IROp::Mov, { MIPS_REG_S0 }, MIPS_REG_A1 }, }, { &PurgeTemps }, }, { "SimplePropagateConstants", { { IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0x12340000 }, { IROp::OrConst, { MIPS_REG_A0 }, MIPS_REG_A0, 0, 0x00005678 }, { IROp::AddConst, { MIPS_REG_A1 }, MIPS_REG_A0, 0, 0 }, { IROp::AddConst, { MIPS_REG_A2 }, MIPS_REG_A0, 0, 0x00001111 }, }, { { IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0x12345678 }, { IROp::SetConst, { MIPS_REG_A1 }, 0, 0, 0x12345678 }, { IROp::SetConst, { MIPS_REG_A2 }, 0, 0, 0x12346789 }, }, { &PropagateConstants }, }, { // Needed for PurgeTemps optimizations to work. "OrToMov", { { IROp::Sub, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Or, { MIPS_REG_S0 }, MIPS_REG_A0, MIPS_REG_ZERO }, { IROp::Add, { MIPS_REG_S1 }, MIPS_REG_A0, MIPS_REG_ZERO }, { IROp::OrConst, { MIPS_REG_S2 }, MIPS_REG_A0, 0, 0 }, { IROp::AddConst, { MIPS_REG_S3 }, MIPS_REG_A0, 0, 0 }, }, { { IROp::Sub, { MIPS_REG_A0 }, MIPS_REG_A1, MIPS_REG_A2 }, { IROp::Mov, { MIPS_REG_S0 }, MIPS_REG_A0 }, { IROp::Mov, { MIPS_REG_S1 }, MIPS_REG_A0 }, { IROp::Mov, { MIPS_REG_S2 }, MIPS_REG_A0 }, { IROp::Mov, { MIPS_REG_S3 }, MIPS_REG_A0 }, }, { &PropagateConstants }, }, { // The FNeg reads the temp and overwrites it, so the FAdd must see the negation. "PurgeTempsFPRRewrittenInPlace", { { IROp::FMov, { IRVTEMP_PFX_S }, 5 }, { IROp::FNeg, { IRVTEMP_PFX_S }, IRVTEMP_PFX_S }, { IROp::FAdd, { 0 }, IRVTEMP_PFX_S, 1 }, }, { { IROp::FNeg, { IRVTEMP_PFX_S }, 5 }, { IROp::FAdd, { 0 }, IRVTEMP_PFX_S, 1 }, }, { &PurgeTemps }, }, { "PurgeTempsFPRReadTwice", { { IROp::FMov, { IRVTEMP_PFX_S }, 5 }, { IROp::FMul, { 0 }, IRVTEMP_PFX_S, IRVTEMP_PFX_S }, }, { { IROp::FMul, { 0 }, 5, 5 }, }, { &PurgeTemps }, }, { // The FPR temp has the same number as IRTEMP_0, which is the address here. "PurgeTempsFPRStoreSrc3", { { IROp::FMov, { IRVTEMP_PFX_S }, 5 }, { IROp::StoreFloat, { IRVTEMP_PFX_S }, IRTEMP_0, 0, 0x10 }, }, { { IROp::StoreFloat, { 5 }, IRTEMP_0, 0, 0x10 }, }, { &PurgeTemps }, }, { // The FMov writes lane 1 of the temp between its write and the Vec4Mov. "PurgeTempsVec4LaneWrite", { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::FMov, { IRVTEMP_0 + 1 }, 20 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::FMov, { IRVTEMP_0 + 1 }, 20 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { &PurgeTemps }, }, { // The Vec4Scale reads 48 before the Vec4Mov writes it. "PurgeTempsVec4ScaleRead", { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::Vec4Scale, { 40 }, 48, 1 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::Vec4Scale, { 40 }, 48, 1 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { &PurgeTemps }, }, { // Writing 48 before the exit would change it on the path that exits. "PurgeTempsSwapAcrossExit", { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::ExitToConstIfEq, { 0 }, MIPS_REG_A0, MIPS_REG_A1, 0x08804000 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { { IROp::Vec4Add, { IRVTEMP_0 }, 32, 36 }, { IROp::ExitToConstIfEq, { 0 }, MIPS_REG_A0, MIPS_REG_A1, 0x08804000 }, { IROp::Vec4Mov, { 48 }, IRVTEMP_0 }, }, { &PurgeTemps }, }, { // sc stores and ll sets LLBIT, so neither goes away when the reg is overwritten. "PurgeTempsKeepsLLSC", { { IROp::Load32Linked, { MIPS_REG_V1 }, MIPS_REG_A0, 0, 0 }, { IROp::SetConst, { MIPS_REG_V1 }, 0, 0, 1 }, { IROp::Store32Conditional, { MIPS_REG_V0 }, MIPS_REG_A0, 0, 0 }, { IROp::SetConst, { MIPS_REG_V0 }, 0, 0, 1 }, }, { { IROp::Load32Linked, { MIPS_REG_V1 }, MIPS_REG_A0, 0, 0 }, { IROp::SetConst, { MIPS_REG_V1 }, 0, 0, 1 }, { IROp::Store32Conditional, { MIPS_REG_V0 }, MIPS_REG_A0, 0, 0 }, { IROp::SetConst, { MIPS_REG_V0 }, 0, 0, 1 }, }, { &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, }, { "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 }, }, { // The exit is always taken, so nothing after it runs. "PropagateConstantsTakenExit", { { IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0 }, { IROp::ExitToConstIfEq, { 0 }, MIPS_REG_A0, MIPS_REG_ZERO, 0x08804000 }, { IROp::Downcount, { 0 }, 0, 0, 4 }, { IROp::ExitToConst, { 0 }, 0, 0, 0x08804100 }, }, { { IROp::SetConst, { MIPS_REG_A0 }, 0, 0, 0 }, { IROp::ExitToConst, { 0 }, 0, 0, 0x08804000 }, }, { &PropagateConstants }, }, { "ForwardStoresToLoads", { { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0x10 }, { IROp::Add, { MIPS_REG_T0 }, MIPS_REG_T1, MIPS_REG_T2 }, { IROp::Load32, { MIPS_REG_A1 }, MIPS_REG_SP, 0, 0x10 }, { IROp::StoreFloat, { 1 }, MIPS_REG_SP, 0, 0x20 }, { IROp::LoadFloat, { 2 }, MIPS_REG_SP, 0, 0x20 }, { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0x30 }, { IROp::LoadFloat, { 3 }, MIPS_REG_SP, 0, 0x30 }, { IROp::Store8, { MIPS_REG_A2 }, MIPS_REG_SP, 0, 0x40 }, { IROp::Load8Ext, { MIPS_REG_A3 }, MIPS_REG_SP, 0, 0x40 }, { IROp::StoreVec4, { 32 }, MIPS_REG_SP, 0, 0x50 }, { IROp::LoadVec4, { 36 }, MIPS_REG_SP, 0, 0x50 }, { IROp::Load32, { MIPS_REG_T3 }, MIPS_REG_GP, 0, 0x100 }, { IROp::Load32, { MIPS_REG_T4 }, MIPS_REG_GP, 0, 0x100 }, // Disjoint on the same base, so the first is still known. { IROp::Store32, { MIPS_REG_S0 }, MIPS_REG_S1, 0, 0x20 }, { IROp::Store32, { MIPS_REG_S2 }, MIPS_REG_S1, 0, 0x24 }, { IROp::Load32, { MIPS_REG_S3 }, MIPS_REG_S1, 0, 0x20 }, // Already in the reg. { IROp::Load32, { MIPS_REG_S2 }, MIPS_REG_S1, 0, 0x24 }, }, { { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0x10 }, { IROp::Add, { MIPS_REG_T0 }, MIPS_REG_T1, MIPS_REG_T2 }, { IROp::Mov, { MIPS_REG_A1 }, MIPS_REG_A0 }, { IROp::StoreFloat, { 1 }, MIPS_REG_SP, 0, 0x20 }, { IROp::FMov, { 2 }, 1 }, { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0x30 }, { IROp::FMovFromGPR, { 3 }, MIPS_REG_A0 }, { IROp::Store8, { MIPS_REG_A2 }, MIPS_REG_SP, 0, 0x40 }, { IROp::Ext8to32, { MIPS_REG_A3 }, MIPS_REG_A2 }, { IROp::StoreVec4, { 32 }, MIPS_REG_SP, 0, 0x50 }, { IROp::Vec4Mov, { 36 }, 32 }, { IROp::Load32, { MIPS_REG_T3 }, MIPS_REG_GP, 0, 0x100 }, { IROp::Mov, { MIPS_REG_T4 }, MIPS_REG_T3 }, { IROp::Store32, { MIPS_REG_S0 }, MIPS_REG_S1, 0, 0x20 }, { IROp::Store32, { MIPS_REG_S2 }, MIPS_REG_S1, 0, 0x24 }, { IROp::Mov, { MIPS_REG_S3 }, MIPS_REG_S0 }, }, { &OptimizeLoadsAfterStores }, }, { "NoForwardStoresToLoads", { // A store through another base may alias. { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 }, { IROp::Store32, { MIPS_REG_A1 }, MIPS_REG_A2, 0, 0 }, { IROp::Load32, { MIPS_REG_A3 }, MIPS_REG_SP, 0, 0 }, // The value reg changed. { IROp::Store32, { MIPS_REG_T0 }, MIPS_REG_SP, 0, 4 }, { IROp::AddConst, { MIPS_REG_T0 }, MIPS_REG_T0, 0, 1 }, { IROp::Load32, { MIPS_REG_T1 }, MIPS_REG_SP, 0, 4 }, // An overlapping narrow store. { IROp::Store32, { MIPS_REG_T2 }, MIPS_REG_SP, 0, 0x10 }, { IROp::Store16, { MIPS_REG_T3 }, MIPS_REG_SP, 0, 0x12 }, { IROp::Load32, { MIPS_REG_T4 }, MIPS_REG_SP, 0, 0x10 }, // The Interpret may do anything. { IROp::Store32, { MIPS_REG_S0 }, MIPS_REG_SP, 0, 0x20 }, { IROp::Interpret, { 0 }, 0, 0, 0 }, { IROp::Load32, { MIPS_REG_S1 }, MIPS_REG_SP, 0, 0x20 }, // Not RAM, so maybe a hardware register. { IROp::Store32, { MIPS_REG_S2 }, MIPS_REG_ZERO, 0, 0xBC100000 }, { IROp::Load32, { MIPS_REG_S3 }, MIPS_REG_ZERO, 0, 0xBC100000 }, // The base reg changed. { IROp::Store32, { MIPS_REG_S4 }, MIPS_REG_SP, 0, 0x30 }, { IROp::AddConst, { MIPS_REG_SP }, MIPS_REG_SP, 0, 0xFFFFFFF0 }, { IROp::Load32, { MIPS_REG_S5 }, MIPS_REG_SP, 0, 0x30 }, }, { { IROp::Store32, { MIPS_REG_A0 }, MIPS_REG_SP, 0, 0 }, { IROp::Store32, { MIPS_REG_A1 }, MIPS_REG_A2, 0, 0 }, { IROp::Load32, { MIPS_REG_A3 }, MIPS_REG_SP, 0, 0 }, { IROp::Store32, { MIPS_REG_T0 }, MIPS_REG_SP, 0, 4 }, { IROp::AddConst, { MIPS_REG_T0 }, MIPS_REG_T0, 0, 1 }, { IROp::Load32, { MIPS_REG_T1 }, MIPS_REG_SP, 0, 4 }, { IROp::Store32, { MIPS_REG_T2 }, MIPS_REG_SP, 0, 0x10 }, { IROp::Store16, { MIPS_REG_T3 }, MIPS_REG_SP, 0, 0x12 }, { IROp::Load32, { MIPS_REG_T4 }, MIPS_REG_SP, 0, 0x10 }, { IROp::Store32, { MIPS_REG_S0 }, MIPS_REG_SP, 0, 0x20 }, { IROp::Interpret, { 0 }, 0, 0, 0 }, { IROp::Load32, { MIPS_REG_S1 }, MIPS_REG_SP, 0, 0x20 }, { IROp::Store32, { MIPS_REG_S2 }, MIPS_REG_ZERO, 0, 0xBC100000 }, { IROp::Load32, { MIPS_REG_S3 }, MIPS_REG_ZERO, 0, 0xBC100000 }, { IROp::Store32, { MIPS_REG_S4 }, MIPS_REG_SP, 0, 0x30 }, { IROp::AddConst, { MIPS_REG_SP }, MIPS_REG_SP, 0, 0xFFFFFFF0 }, { IROp::Load32, { MIPS_REG_S5 }, MIPS_REG_SP, 0, 0x30 }, }, { &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() { InitIR(); bool success = true; for (const auto &test : tests) { if (!VerifyPass(test)) success = false; } return success; }