From 8f230252099010dc96f4de755f2731d832c65d13 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Jul 2022 10:37:54 -0700 Subject: [PATCH 1/5] irjit: Add tests for IR passes. --- CMakeLists.txt | 1 + Core/MIPS/IR/IRPassSimplify.cpp | 1 + Core/MIPS/IR/IRRegCache.cpp | 3 +- Core/MIPS/MIPSAnalyst.cpp | 2 +- android/jni/Android.mk | 1 + unittest/TestIRPassSimplify.cpp | 149 +++++++++++++++++++++++++++++ unittest/UnitTest.cpp | 2 + unittest/UnitTests.vcxproj | 1 + unittest/UnitTests.vcxproj.filters | 1 + 9 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 unittest/TestIRPassSimplify.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 22b4b802c3..5be55987dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2321,6 +2321,7 @@ if(UNITTEST) unittest/TestShaderGenerators.cpp unittest/TestArmEmitter.cpp unittest/TestArm64Emitter.cpp + unittest/TestIRPassSimplify.cpp unittest/TestX64Emitter.cpp unittest/TestVertexJit.cpp unittest/TestSoftwareGPUJit.cpp diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 80972d3ff3..0ba252a018 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -738,6 +738,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } } } + gpr.FlushAll(); return logBlocks; } diff --git a/Core/MIPS/IR/IRRegCache.cpp b/Core/MIPS/IR/IRRegCache.cpp index 63ae135878..ca54392be1 100644 --- a/Core/MIPS/IR/IRRegCache.cpp +++ b/Core/MIPS/IR/IRRegCache.cpp @@ -27,8 +27,7 @@ IRRegCache::IRRegCache(IRWriter *ir) : ir_(ir) { void IRRegCache::FlushAll() { for (int i = 0; i < TOTAL_MAPPABLE_MIPSREGS; i++) { - //if (i < IRTEMP_0) - Flush(i); + Flush(i); } } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index f88dde62bf..2c4e11bea4 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -1462,7 +1462,7 @@ skip: } break; case 0x08: // addi - case 0x09: // adiu + case 0x09: // addiu info.hasRelevantAddress = true; info.relevantAddress = cpu->GetRegValue(0,MIPS_GET_RS(op))+((s16)(op & 0xFFFF)); break; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 89cfded9f6..d133898544 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -734,6 +734,7 @@ ifeq ($(UNITTEST),1) LOCAL_MODULE := ppsspp_unittest LOCAL_SRC_FILES := \ $(SRC)/unittest/JitHarness.cpp \ + $(SRC)/unittest/TestIRPassSimplify.cpp \ $(SRC)/unittest/TestShaderGenerators.cpp \ $(SRC)/unittest/TestSoftwareGPUJit.cpp \ $(SRC)/unittest/TestThreadManager.cpp \ diff --git a/unittest/TestIRPassSimplify.cpp b/unittest/TestIRPassSimplify.cpp new file mode 100644 index 0000000000..93b57d4115 --- /dev/null +++ b/unittest/TestIRPassSimplify.cpp @@ -0,0 +1,149 @@ +// 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/MIPS/IR/IRPassSimplify.h" + +struct IRVerification { + const char *name; + const std::vector input; + const std::vector expected; + const std::vector passes; +}; + +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; + + for (const auto &inst : v.input) + in.Write(inst); + if (IRApplyPasses(v.passes.data(), v.passes.size(), in, out, opts)) { + 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'", v.name, (int)i, expectedBuf, actualBuf); + 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 }, + }, + { + "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 }, + }, + { + "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::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 }, + }, + { &PropagateConstants }, + }, +}; + +bool TestIRPassSimplify() { + InitIR(); + + for (const auto &test : tests) { + if (!VerifyPass(test)) + return false; + } + + return true; +} diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 80c0cbcb1c..6bb000418e 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -756,6 +756,7 @@ bool TestArm64Emitter(); bool TestX64Emitter(); bool TestShaderGenerators(); bool TestSoftwareGPUJit(); +bool TestIRPassSimplify(); bool TestThreadManager(); TestItem availableTests[] = { @@ -774,6 +775,7 @@ TestItem availableTests[] = { TEST_ITEM(VFPUSinCos), TEST_ITEM(MathUtil), TEST_ITEM(Parsers), + TEST_ITEM(IRPassSimplify), TEST_ITEM(Jit), TEST_ITEM(MatrixTranspose), TEST_ITEM(ParseLBN), diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index ca158dd1e5..dc50dacf92 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -382,6 +382,7 @@ true true + diff --git a/unittest/UnitTests.vcxproj.filters b/unittest/UnitTests.vcxproj.filters index bfa4b8bef8..d0e70e6021 100644 --- a/unittest/UnitTests.vcxproj.filters +++ b/unittest/UnitTests.vcxproj.filters @@ -14,6 +14,7 @@ + From 2154f747fccdd4eeafb3976213c5737454578d76 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Jul 2022 10:44:59 -0700 Subject: [PATCH 2/5] irjit: Simplify more arithmetic to Movs. Later passes rely on things being Mov, so better to have them more often. --- Core/MIPS/IR/IRPassSimplify.cpp | 13 +++++++++---- unittest/TestIRPassSimplify.cpp | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 0ba252a018..6c6de0c16c 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -416,8 +416,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } else if (gpr.IsImm(inst.src2)) { const u32 imm2 = gpr.GetImm(inst.src2); gpr.MapDirtyIn(inst.dest, inst.src1); - if (imm2 == 0 && (inst.op == IROp::Add || inst.op == IROp::Or)) { - // Add / Or with zero is just a Mov. + if (imm2 == 0 && (inst.op == IROp::Add || inst.op == IROp::Sub || inst.op == IROp::Or || inst.op == IROp::Xor)) { + // Add / Sub / Or / Xor with zero is just a Mov. Add / Or are most common. if (inst.dest != inst.src1) out.Write(IROp::Mov, inst.dest, inst.src1); } else { @@ -426,8 +426,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } else if (symmetric && gpr.IsImm(inst.src1)) { const u32 imm1 = gpr.GetImm(inst.src1); gpr.MapDirtyIn(inst.dest, inst.src2); - if (imm1 == 0 && (inst.op == IROp::Add || inst.op == IROp::Or)) { - // Add / Or with zero is just a Mov. + if (imm1 == 0 && (inst.op == IROp::Add || inst.op == IROp::Or || inst.op == IROp::Xor)) { + // Add / Or / Xor with zero is just a Mov. if (inst.dest != inst.src2) out.Write(IROp::Mov, inst.dest, inst.src2); } else { @@ -467,6 +467,11 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts gpr.SetImm(inst.dest, 0); } else if (gpr.IsImm(inst.src1)) { gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), inst.constant, inst.op)); + } else if (inst.constant == 0 && (inst.op == IROp::AddConst || inst.op == IROp::SubConst || inst.op == IROp::OrConst || inst.op == IROp::XorConst)) { + // Convert an Add/Sub/Or/Xor with a constant zero to a Mov (just like with reg zero.) + gpr.MapDirtyIn(inst.dest, inst.src1); + if (inst.dest != inst.src1) + out.Write(IROp::Mov, inst.dest, inst.src1); } else { gpr.MapDirtyIn(inst.dest, inst.src1); goto doDefault; diff --git a/unittest/TestIRPassSimplify.cpp b/unittest/TestIRPassSimplify.cpp index 93b57d4115..e04dfecbe6 100644 --- a/unittest/TestIRPassSimplify.cpp +++ b/unittest/TestIRPassSimplify.cpp @@ -127,11 +127,15 @@ static const IRVerification tests[] = { { 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 }, }, From 257923294956e716254d417818a5131efd71b2a8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Jul 2022 10:45:41 -0700 Subject: [PATCH 3/5] Headless: Ignore __testcompare.png in tree. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 52b13f29d4..b7653209ae 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ __testoutput.txt __testerror.txt __testfinish.txt __testfailure.bmp +__testcompare.png GameLogNotes.txt screenshots android/assets/lang From 16188fa4376cec288ba654085ff536ad43b1812a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Jul 2022 10:56:43 -0700 Subject: [PATCH 4/5] irjit: Add test for double clobber in #15713. --- unittest/TestIRPassSimplify.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/unittest/TestIRPassSimplify.cpp b/unittest/TestIRPassSimplify.cpp index e04dfecbe6..3e2507cf29 100644 --- a/unittest/TestIRPassSimplify.cpp +++ b/unittest/TestIRPassSimplify.cpp @@ -105,6 +105,20 @@ static const IRVerification tests[] = { }, { &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", { From b620c966e0f9548c984a8750cd8796dee64f6c26 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Jul 2022 11:52:19 -0700 Subject: [PATCH 5/5] irjit: Correct initialization warning. --- unittest/TestIRPassSimplify.cpp | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/unittest/TestIRPassSimplify.cpp b/unittest/TestIRPassSimplify.cpp index 3e2507cf29..2703a3edaf 100644 --- a/unittest/TestIRPassSimplify.cpp +++ b/unittest/TestIRPassSimplify.cpp @@ -81,56 +81,56 @@ 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, { 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 }, + { IROp::Add, { MIPS_REG_V0 }, MIPS_REG_A0, MIPS_REG_A1 }, + { IROp::Add, { MIPS_REG_V1 }, MIPS_REG_A2, MIPS_REG_A3 }, }, { &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_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 }, + { 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_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 }, + { 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, 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 }, + { 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 }, }, @@ -138,18 +138,18 @@ static const IRVerification tests[] = { // 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::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 }, + { 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 }, },