diff --git a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp index f68230f9f2..5f165233f4 100644 --- a/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompLoadStore.cpp @@ -200,9 +200,70 @@ void Arm64JitBackend::CompIR_CondStore(IRInst inst) { } } +bool Arm64JitBackend::TryCompileLoadStorePair(IRInst inst) { + if (compilingIndex_ + 1 >= compilingCount_ || inst.src1 == MIPS_REG_ZERO) + return false; + const IRInst next = compilingInsts_[compilingIndex_ + 1]; + if (next.op != inst.op || next.src1 != inst.src1) + return false; + const int32_t offset = (int32_t)inst.constant; + const int32_t nextOffset = (int32_t)next.constant; + if (nextOffset != offset + 4 && nextOffset != offset - 4) + return false; + // The pair's scaled 7-bit signed offset. + const int32_t low = std::min(offset, nextOffset); + if ((low & 3) != 0 || low < -256 || low > 252) + return false; + // The base must be usable as a pointer. + if (!regs_.IsGPRMappedAsPointer(inst.src1) && (!jo.cachePointers || regs_.IsGPRClobbered(inst.src1))) + return false; + + const bool isLoad = inst.op == IROp::Load32 || inst.op == IROp::LoadFloat; + const bool isFloat = inst.op == IROp::LoadFloat || inst.op == IROp::StoreFloat; + // A load may not change the base, and an LDP can't write one register twice. + if (isLoad && (next.dest == inst.dest || (!isFloat && (inst.dest == inst.src1 || next.dest == inst.src1)))) + return false; + // Nor can a stored value be the base, which is mapped as a pointer. + if (!isLoad && !isFloat && (inst.src3 == inst.src1 || next.src3 == inst.src1)) + return false; + + const IRReg regLow = offset < nextOffset ? inst.dest : next.dest; + const IRReg regHigh = offset < nextOffset ? next.dest : inst.dest; + if (isFloat) { + regs_.SpillLockFPR(regLow, regHigh); + regs_.SpillLockGPR(inst.src1); + } else { + regs_.SpillLockGPR(inst.src1, regLow, regHigh); + } + ARM64Reg base = regs_.MapGPRAsPointer(inst.src1); + + if (isFloat) { + MIPSMap flags = isLoad ? MIPSMap::NOINIT : MIPSMap::INIT; + regs_.MapFPR(regLow, flags); + regs_.MapFPR(regHigh, flags); + if (isLoad) + fp_.LDP(32, INDEX_SIGNED, regs_.F(regLow), regs_.F(regHigh), base, low); + else + fp_.STP(32, INDEX_SIGNED, regs_.F(regLow), regs_.F(regHigh), base, low); + } else if (isLoad) { + regs_.MapGPR(regLow, MIPSMap::NOINIT); + regs_.MapGPR(regHigh, MIPSMap::NOINIT); + LDP(INDEX_SIGNED, regs_.R(regLow), regs_.R(regHigh), base, low); + } else { + ARM64Reg valueLow = regs_.MapGPR(regLow); + ARM64Reg valueHigh = regs_.MapGPR(regHigh); + STP(INDEX_SIGNED, valueLow, valueHigh, base, low); + } + + skipNextInst_ = true; + return true; +} + void Arm64JitBackend::CompIR_FLoad(IRInst inst) { CONDITIONAL_DISABLE; + if (inst.op == IROp::LoadFloat && TryCompileLoadStorePair(inst)) + return; LoadStoreArg addrArg = PrepareSrc1Address(inst); switch (inst.op) { @@ -226,6 +287,8 @@ void Arm64JitBackend::CompIR_FLoad(IRInst inst) { void Arm64JitBackend::CompIR_FStore(IRInst inst) { CONDITIONAL_DISABLE; + if (inst.op == IROp::StoreFloat && TryCompileLoadStorePair(inst)) + return; LoadStoreArg addrArg = PrepareSrc1Address(inst); switch (inst.op) { @@ -249,6 +312,8 @@ void Arm64JitBackend::CompIR_FStore(IRInst inst) { void Arm64JitBackend::CompIR_Load(IRInst inst) { CONDITIONAL_DISABLE; + if (inst.op == IROp::Load32 && TryCompileLoadStorePair(inst)) + return; regs_.SpillLockGPR(inst.dest, inst.src1); LoadStoreArg addrArg = PrepareSrc1Address(inst); // With NOINIT, MapReg won't subtract MEMBASEREG even if dest == src1. @@ -345,6 +410,8 @@ void Arm64JitBackend::CompIR_LoadShift(IRInst inst) { void Arm64JitBackend::CompIR_Store(IRInst inst) { CONDITIONAL_DISABLE; + if (inst.op == IROp::Store32 && TryCompileLoadStorePair(inst)) + return; regs_.SpillLockGPR(inst.src3, inst.src1); LoadStoreArg addrArg = PrepareSrc1Address(inst); diff --git a/Core/MIPS/ARM64/Arm64IRJit.cpp b/Core/MIPS/ARM64/Arm64IRJit.cpp index 6f81f4fdc5..541badc205 100644 --- a/Core/MIPS/ARM64/Arm64IRJit.cpp +++ b/Core/MIPS/ARM64/Arm64IRJit.cpp @@ -99,12 +99,21 @@ bool Arm64JitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) { std::vector addresses; addresses.reserve(block->GetNumIRInstructions()); const IRInst *instructions = irBlockCache->GetBlockInstructionPtr(*block); + compilingInsts_ = instructions; + compilingCount_ = block->GetNumIRInstructions(); for (int i = 0; i < block->GetNumIRInstructions(); ++i) { const IRInst &inst = instructions[i]; regs_.SetIRIndex(i); + compilingIndex_ = i; addresses.push_back(GetCodePtr()); CompileIRInst(inst); + if (skipNextInst_) { + // It was compiled together with this one. + skipNextInst_ = false; + addresses.push_back(GetCodePtr()); + i++; + } if (jo.Disabled(JitDisable::REGALLOC_GPR) || jo.Disabled(JitDisable::REGALLOC_FPR)) regs_.FlushAll(jo.Disabled(JitDisable::REGALLOC_GPR), jo.Disabled(JitDisable::REGALLOC_FPR)); diff --git a/Core/MIPS/ARM64/Arm64IRJit.h b/Core/MIPS/ARM64/Arm64IRJit.h index a087638bbe..d6efefd516 100644 --- a/Core/MIPS/ARM64/Arm64IRJit.h +++ b/Core/MIPS/ARM64/Arm64IRJit.h @@ -123,6 +123,9 @@ private: bool signExtendRegOffset = false; }; LoadStoreArg PrepareSrc1Address(IRInst inst); + // Compiles inst together with the next instruction as one LDP/STP when they access adjacent + // words through the same base. The next instruction is then skipped. + bool TryCompileLoadStorePair(IRInst inst); JitOptions &jo; Arm64IRRegCache regs_; @@ -148,6 +151,11 @@ private: int jitStartOffset_ = 0; int compilingBlockNum_ = -1; + // The block's instructions while compiling, for looking at the next one. + const IRInst *compilingInsts_ = nullptr; + int compilingIndex_ = 0; + int compilingCount_ = 0; + bool skipNextInst_ = false; int logBlocks_ = 0; // Only useful in breakpoints, where it's set immediately prior. uint32_t lastConstPC_ = 0; diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index b44d6704d7..9bf8fee367 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -131,6 +131,12 @@ static const IRMeta irMeta[] = { { IROp::FMovToGPR, "FMovToGPR", "GF" }, { IROp::OptFMovToGPRShr8, "OptFMovToGPRShr8", "GF" }, { IROp::OptFCvtSWFromGPR, "OptFCvtSWFromGPR", "FG" }, + { IROp::OptExitToConstIfEqElse, "OptExitIfEqElse", "CGG", IRFLAG_EXIT }, + { IROp::OptExitToConstIfNeqElse, "OptExitIfNeqElse", "CGG", IRFLAG_EXIT }, + { IROp::OptExitToConstIfGtZElse, "OptExitIfGtZElse", "CG", IRFLAG_EXIT }, + { IROp::OptExitToConstIfGeZElse, "OptExitIfGeZElse", "CG", IRFLAG_EXIT }, + { IROp::OptExitToConstIfLtZElse, "OptExitIfLtZElse", "CG", IRFLAG_EXIT }, + { IROp::OptExitToConstIfLeZElse, "OptExitIfLeZElse", "CG", IRFLAG_EXIT }, { IROp::FpCondFromReg, "FpCondFromReg", "_G" }, { IROp::FpCondToReg, "FpCondToReg", "G" }, { IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" }, diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 55b9bd7473..cc392e2a84 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -141,6 +141,14 @@ enum class IROp : uint8_t { OptFCvtSWFromGPR, FMovToGPR, OptFMovToGPRShr8, + // A conditional exit merged with the ExitToConst after it, which stays behind as the target + // to take otherwise (in its constant). + OptExitToConstIfEqElse, + OptExitToConstIfNeqElse, + OptExitToConstIfGtZElse, + OptExitToConstIfGeZElse, + OptExitToConstIfLtZElse, + OptExitToConstIfLeZElse, FSat0_1, FSatMinus1_1, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 42fea35a5c..4180b820cc 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -80,125 +80,332 @@ u32 IRRunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING_CPU ? 1 : 0; } -u32 IRInterpret(MIPSState *mips, const IRInst *inst) { - while (true) { - switch (inst->op) { - case IROp::SetConst: - mips->r[inst->dest] = inst->constant; - break; - case IROp::SetConstF: - memcpy(&mips->f[inst->dest], &inst->constant, 4); - break; - case IROp::Add: - mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2]; - break; - case IROp::Sub: - mips->r[inst->dest] = mips->r[inst->src1] - mips->r[inst->src2]; - break; - case IROp::And: - mips->r[inst->dest] = mips->r[inst->src1] & mips->r[inst->src2]; - break; - case IROp::Or: - mips->r[inst->dest] = mips->r[inst->src1] | mips->r[inst->src2]; - break; - case IROp::Xor: - mips->r[inst->dest] = mips->r[inst->src1] ^ mips->r[inst->src2]; - break; - case IROp::Mov: - mips->r[inst->dest] = mips->r[inst->src1]; - break; - case IROp::AddConst: - mips->r[inst->dest] = mips->r[inst->src1] + inst->constant; - break; - case IROp::OptAddConst: // For this one, it's worth having a "unary" variant of the above that only needs to read one register param. - mips->r[inst->dest] += inst->constant; - break; - case IROp::SubConst: - mips->r[inst->dest] = mips->r[inst->src1] - inst->constant; - break; - case IROp::AndConst: - mips->r[inst->dest] = mips->r[inst->src1] & inst->constant; - break; - case IROp::OptAndConst: // For this one, it's worth having a "unary" variant of the above that only needs to read one register param. - mips->r[inst->dest] &= inst->constant; - break; - case IROp::OrConst: - mips->r[inst->dest] = mips->r[inst->src1] | inst->constant; - break; - case IROp::OptOrConst: - mips->r[inst->dest] |= inst->constant; - break; - case IROp::XorConst: - mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant; - break; - case IROp::Neg: - mips->r[inst->dest] = (u32)(-(s32)mips->r[inst->src1]); - break; - case IROp::Not: - mips->r[inst->dest] = ~mips->r[inst->src1]; - break; - case IROp::Ext8to32: - mips->r[inst->dest] = SignExtend8ToU32(mips->r[inst->src1]); - break; - case IROp::Ext16to32: - mips->r[inst->dest] = SignExtend16ToU32(mips->r[inst->src1]); - break; - case IROp::ReverseBits: - mips->r[inst->dest] = ReverseBits32(mips->r[inst->src1]); - break; +// With GCC and Clang, each op jumps straight to the next op's handler through a table ("threaded" +// dispatch), which predicts much better than the one shared indirect jump of a switch. Other +// compilers get the switch. Ops missing from the table fall back to the switch, so they still work. +#if defined(__GNUC__) || defined(__clang__) +#define IR_THREADED_DISPATCH 1 +#define IR_CASE(op) case IROp::op: L_##op: +#else +#define IR_CASE(op) case IROp::op: +#endif - case IROp::Load8: +#ifdef _DEBUG +#define IR_CHECK_ZERO_REG() if (mips->r[0] != 0) Crash(); +#else +#define IR_CHECK_ZERO_REG() +#endif + +#if IR_THREADED_DISPATCH +#define IR_NEXT do { IR_CHECK_ZERO_REG(); inst++; goto *dispatch[(int)inst->op]; } while (false) +#else +#define IR_NEXT { IR_CHECK_ZERO_REG(); inst++; continue; } +#endif + +u32 IRInterpret(MIPSState *mips, const IRInst *inst) { +#if IR_THREADED_DISPATCH + static const void *dispatch[256]; + static bool dispatchReady = false; + if (!dispatchReady) { + for (const void *&target : dispatch) + target = &&L_switch; + dispatch[(int)IROp::SetConst] = &&L_SetConst; + dispatch[(int)IROp::SetConstF] = &&L_SetConstF; + dispatch[(int)IROp::Add] = &&L_Add; + dispatch[(int)IROp::Sub] = &&L_Sub; + dispatch[(int)IROp::And] = &&L_And; + dispatch[(int)IROp::Or] = &&L_Or; + dispatch[(int)IROp::Xor] = &&L_Xor; + dispatch[(int)IROp::Mov] = &&L_Mov; + dispatch[(int)IROp::AddConst] = &&L_AddConst; + dispatch[(int)IROp::OptAddConst] = &&L_OptAddConst; + dispatch[(int)IROp::SubConst] = &&L_SubConst; + dispatch[(int)IROp::AndConst] = &&L_AndConst; + dispatch[(int)IROp::OptAndConst] = &&L_OptAndConst; + dispatch[(int)IROp::OrConst] = &&L_OrConst; + dispatch[(int)IROp::OptOrConst] = &&L_OptOrConst; + dispatch[(int)IROp::XorConst] = &&L_XorConst; + dispatch[(int)IROp::Neg] = &&L_Neg; + dispatch[(int)IROp::Not] = &&L_Not; + dispatch[(int)IROp::Ext8to32] = &&L_Ext8to32; + dispatch[(int)IROp::Ext16to32] = &&L_Ext16to32; + dispatch[(int)IROp::ReverseBits] = &&L_ReverseBits; + dispatch[(int)IROp::Load8] = &&L_Load8; + dispatch[(int)IROp::Load8Ext] = &&L_Load8Ext; + dispatch[(int)IROp::Load16] = &&L_Load16; + dispatch[(int)IROp::Load16Ext] = &&L_Load16Ext; + dispatch[(int)IROp::Load32] = &&L_Load32; + dispatch[(int)IROp::Load32Left] = &&L_Load32Left; + dispatch[(int)IROp::Load32Right] = &&L_Load32Right; + dispatch[(int)IROp::Load32Linked] = &&L_Load32Linked; + dispatch[(int)IROp::LoadFloat] = &&L_LoadFloat; + dispatch[(int)IROp::Store8] = &&L_Store8; + dispatch[(int)IROp::Store16] = &&L_Store16; + dispatch[(int)IROp::Store32] = &&L_Store32; + dispatch[(int)IROp::Store32Left] = &&L_Store32Left; + dispatch[(int)IROp::Store32Right] = &&L_Store32Right; + dispatch[(int)IROp::Store32Conditional] = &&L_Store32Conditional; + dispatch[(int)IROp::StoreFloat] = &&L_StoreFloat; + dispatch[(int)IROp::LoadVec4] = &&L_LoadVec4; + dispatch[(int)IROp::StoreVec4] = &&L_StoreVec4; + dispatch[(int)IROp::Vec4Init] = &&L_Vec4Init; + dispatch[(int)IROp::Vec4Shuffle] = &&L_Vec4Shuffle; + dispatch[(int)IROp::Vec4Blend] = &&L_Vec4Blend; + dispatch[(int)IROp::Vec4Mov] = &&L_Vec4Mov; + dispatch[(int)IROp::Vec4Add] = &&L_Vec4Add; + dispatch[(int)IROp::Vec4Sub] = &&L_Vec4Sub; + dispatch[(int)IROp::Vec4Mul] = &&L_Vec4Mul; + dispatch[(int)IROp::Vec4Div] = &&L_Vec4Div; + dispatch[(int)IROp::Vec4Scale] = &&L_Vec4Scale; + dispatch[(int)IROp::Vec4Neg] = &&L_Vec4Neg; + dispatch[(int)IROp::Vec4Abs] = &&L_Vec4Abs; + dispatch[(int)IROp::Vec2Unpack16To31] = &&L_Vec2Unpack16To31; + dispatch[(int)IROp::Vec2Unpack16To32] = &&L_Vec2Unpack16To32; + dispatch[(int)IROp::Vec4Unpack8To32] = &&L_Vec4Unpack8To32; + dispatch[(int)IROp::Vec2Pack32To16] = &&L_Vec2Pack32To16; + dispatch[(int)IROp::Vec2Pack31To16] = &&L_Vec2Pack31To16; + dispatch[(int)IROp::Vec4Pack32To8] = &&L_Vec4Pack32To8; + dispatch[(int)IROp::Vec4Pack31To8] = &&L_Vec4Pack31To8; + dispatch[(int)IROp::Vec4DuplicateUpperBitsAndShift1] = &&L_Vec4DuplicateUpperBitsAndShift1; + dispatch[(int)IROp::FCmpVfpuBit] = &&L_FCmpVfpuBit; + dispatch[(int)IROp::FCmpVfpuAggregate] = &&L_FCmpVfpuAggregate; + dispatch[(int)IROp::FCmovVfpuCC] = &&L_FCmovVfpuCC; + dispatch[(int)IROp::Vec4Dot] = &&L_Vec4Dot; + dispatch[(int)IROp::FSin] = &&L_FSin; + dispatch[(int)IROp::FCos] = &&L_FCos; + dispatch[(int)IROp::FRSqrt] = &&L_FRSqrt; + dispatch[(int)IROp::FRecip] = &&L_FRecip; + dispatch[(int)IROp::FAsin] = &&L_FAsin; + dispatch[(int)IROp::FVSqrt] = &&L_FVSqrt; + dispatch[(int)IROp::FExp2] = &&L_FExp2; + dispatch[(int)IROp::FLog2] = &&L_FLog2; + dispatch[(int)IROp::FSinCos] = &&L_FSinCos; + dispatch[(int)IROp::FHalfToFloat] = &&L_FHalfToFloat; + dispatch[(int)IROp::ShlImm] = &&L_ShlImm; + dispatch[(int)IROp::ShrImm] = &&L_ShrImm; + dispatch[(int)IROp::SarImm] = &&L_SarImm; + dispatch[(int)IROp::RorImm] = &&L_RorImm; + dispatch[(int)IROp::Shl] = &&L_Shl; + dispatch[(int)IROp::Shr] = &&L_Shr; + dispatch[(int)IROp::Sar] = &&L_Sar; + dispatch[(int)IROp::Ror] = &&L_Ror; + dispatch[(int)IROp::Clz] = &&L_Clz; + dispatch[(int)IROp::Slt] = &&L_Slt; + dispatch[(int)IROp::SltU] = &&L_SltU; + dispatch[(int)IROp::SltConst] = &&L_SltConst; + dispatch[(int)IROp::SltUConst] = &&L_SltUConst; + dispatch[(int)IROp::MovZ] = &&L_MovZ; + dispatch[(int)IROp::MovNZ] = &&L_MovNZ; + dispatch[(int)IROp::Max] = &&L_Max; + dispatch[(int)IROp::Min] = &&L_Min; + dispatch[(int)IROp::MtLo] = &&L_MtLo; + dispatch[(int)IROp::MtHi] = &&L_MtHi; + dispatch[(int)IROp::MfLo] = &&L_MfLo; + dispatch[(int)IROp::MfHi] = &&L_MfHi; + dispatch[(int)IROp::Mult] = &&L_Mult; + dispatch[(int)IROp::MultU] = &&L_MultU; + dispatch[(int)IROp::Madd] = &&L_Madd; + dispatch[(int)IROp::MaddU] = &&L_MaddU; + dispatch[(int)IROp::Msub] = &&L_Msub; + dispatch[(int)IROp::MsubU] = &&L_MsubU; + dispatch[(int)IROp::Div] = &&L_Div; + dispatch[(int)IROp::DivU] = &&L_DivU; + dispatch[(int)IROp::BSwap16] = &&L_BSwap16; + dispatch[(int)IROp::BSwap32] = &&L_BSwap32; + dispatch[(int)IROp::FAdd] = &&L_FAdd; + dispatch[(int)IROp::FSub] = &&L_FSub; + dispatch[(int)IROp::FMul] = &&L_FMul; + dispatch[(int)IROp::FDiv] = &&L_FDiv; + dispatch[(int)IROp::FMin] = &&L_FMin; + dispatch[(int)IROp::FMax] = &&L_FMax; + dispatch[(int)IROp::FMov] = &&L_FMov; + dispatch[(int)IROp::FAbs] = &&L_FAbs; + dispatch[(int)IROp::FSqrt] = &&L_FSqrt; + dispatch[(int)IROp::FNeg] = &&L_FNeg; + dispatch[(int)IROp::FSat0_1] = &&L_FSat0_1; + dispatch[(int)IROp::FSatMinus1_1] = &&L_FSatMinus1_1; + dispatch[(int)IROp::FSign] = &&L_FSign; + dispatch[(int)IROp::FpCondFromReg] = &&L_FpCondFromReg; + dispatch[(int)IROp::FpCondToReg] = &&L_FpCondToReg; + dispatch[(int)IROp::FpCtrlFromReg] = &&L_FpCtrlFromReg; + dispatch[(int)IROp::FpCtrlToReg] = &&L_FpCtrlToReg; + dispatch[(int)IROp::VfpuCtrlToReg] = &&L_VfpuCtrlToReg; + dispatch[(int)IROp::FRound] = &&L_FRound; + dispatch[(int)IROp::FTrunc] = &&L_FTrunc; + dispatch[(int)IROp::FCeil] = &&L_FCeil; + dispatch[(int)IROp::FFloor] = &&L_FFloor; + dispatch[(int)IROp::FCmp] = &&L_FCmp; + dispatch[(int)IROp::FCvtSW] = &&L_FCvtSW; + dispatch[(int)IROp::FCvtWS] = &&L_FCvtWS; + dispatch[(int)IROp::FCvtScaledSW] = &&L_FCvtScaledSW; + dispatch[(int)IROp::FCvtScaledWS] = &&L_FCvtScaledWS; + dispatch[(int)IROp::FMovFromGPR] = &&L_FMovFromGPR; + dispatch[(int)IROp::OptFCvtSWFromGPR] = &&L_OptFCvtSWFromGPR; + dispatch[(int)IROp::FMovToGPR] = &&L_FMovToGPR; + dispatch[(int)IROp::OptFMovToGPRShr8] = &&L_OptFMovToGPRShr8; + dispatch[(int)IROp::ExitToConst] = &&L_ExitToConst; + dispatch[(int)IROp::ExitToReg] = &&L_ExitToReg; + dispatch[(int)IROp::OptExitToConstIfEqElse] = &&L_OptExitToConstIfEqElse; + dispatch[(int)IROp::OptExitToConstIfNeqElse] = &&L_OptExitToConstIfNeqElse; + dispatch[(int)IROp::OptExitToConstIfGtZElse] = &&L_OptExitToConstIfGtZElse; + dispatch[(int)IROp::OptExitToConstIfGeZElse] = &&L_OptExitToConstIfGeZElse; + dispatch[(int)IROp::OptExitToConstIfLtZElse] = &&L_OptExitToConstIfLtZElse; + dispatch[(int)IROp::OptExitToConstIfLeZElse] = &&L_OptExitToConstIfLeZElse; + dispatch[(int)IROp::ExitToConstIfEq] = &&L_ExitToConstIfEq; + dispatch[(int)IROp::ExitToConstIfNeq] = &&L_ExitToConstIfNeq; + dispatch[(int)IROp::ExitToConstIfGtZ] = &&L_ExitToConstIfGtZ; + dispatch[(int)IROp::ExitToConstIfGeZ] = &&L_ExitToConstIfGeZ; + dispatch[(int)IROp::ExitToConstIfLtZ] = &&L_ExitToConstIfLtZ; + dispatch[(int)IROp::ExitToConstIfLeZ] = &&L_ExitToConstIfLeZ; + dispatch[(int)IROp::Downcount] = &&L_Downcount; + dispatch[(int)IROp::SetPC] = &&L_SetPC; + dispatch[(int)IROp::SetPCConst] = &&L_SetPCConst; + dispatch[(int)IROp::Syscall] = &&L_Syscall; + dispatch[(int)IROp::SyscallUnresolved] = &&L_SyscallUnresolved; + dispatch[(int)IROp::ExitToPC] = &&L_ExitToPC; + dispatch[(int)IROp::Interpret] = &&L_Interpret; + dispatch[(int)IROp::CallReplacement] = &&L_CallReplacement; + dispatch[(int)IROp::SetCtrlVFPU] = &&L_SetCtrlVFPU; + dispatch[(int)IROp::SetCtrlVFPUReg] = &&L_SetCtrlVFPUReg; + dispatch[(int)IROp::SetCtrlVFPUFReg] = &&L_SetCtrlVFPUFReg; + dispatch[(int)IROp::ApplyRoundingMode] = &&L_ApplyRoundingMode; + dispatch[(int)IROp::RestoreRoundingMode] = &&L_RestoreRoundingMode; + dispatch[(int)IROp::UpdateRoundingMode] = &&L_UpdateRoundingMode; + dispatch[(int)IROp::Break] = &&L_Break; + dispatch[(int)IROp::Breakpoint] = &&L_Breakpoint; + dispatch[(int)IROp::MemoryCheck] = &&L_MemoryCheck; + dispatch[(int)IROp::ValidateAddress8] = &&L_ValidateAddress8; + dispatch[(int)IROp::ValidateAddress16] = &&L_ValidateAddress16; + dispatch[(int)IROp::ValidateAddress32] = &&L_ValidateAddress32; + dispatch[(int)IROp::ValidateAddress128] = &&L_ValidateAddress128; + dispatch[(int)IROp::LogIRBlock] = &&L_LogIRBlock; + dispatch[(int)IROp::Nop] = &&L_Nop; + dispatch[(int)IROp::Bad] = &&L_Bad; + dispatchReady = true; + } +#endif + + while (true) { +#if IR_THREADED_DISPATCH + L_switch: +#endif + switch (inst->op) { + IR_CASE(SetConst) + mips->r[inst->dest] = inst->constant; + IR_NEXT; + IR_CASE(SetConstF) + memcpy(&mips->f[inst->dest], &inst->constant, 4); + IR_NEXT; + IR_CASE(Add) + mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2]; + IR_NEXT; + IR_CASE(Sub) + mips->r[inst->dest] = mips->r[inst->src1] - mips->r[inst->src2]; + IR_NEXT; + IR_CASE(And) + mips->r[inst->dest] = mips->r[inst->src1] & mips->r[inst->src2]; + IR_NEXT; + IR_CASE(Or) + mips->r[inst->dest] = mips->r[inst->src1] | mips->r[inst->src2]; + IR_NEXT; + IR_CASE(Xor) + mips->r[inst->dest] = mips->r[inst->src1] ^ mips->r[inst->src2]; + IR_NEXT; + IR_CASE(Mov) + mips->r[inst->dest] = mips->r[inst->src1]; + IR_NEXT; + IR_CASE(AddConst) + mips->r[inst->dest] = mips->r[inst->src1] + inst->constant; + IR_NEXT; + IR_CASE(OptAddConst) // For this one, it's worth having a "unary" variant of the above that only needs to read one register param. + mips->r[inst->dest] += inst->constant; + IR_NEXT; + IR_CASE(SubConst) + mips->r[inst->dest] = mips->r[inst->src1] - inst->constant; + IR_NEXT; + IR_CASE(AndConst) + mips->r[inst->dest] = mips->r[inst->src1] & inst->constant; + IR_NEXT; + IR_CASE(OptAndConst) // For this one, it's worth having a "unary" variant of the above that only needs to read one register param. + mips->r[inst->dest] &= inst->constant; + IR_NEXT; + IR_CASE(OrConst) + mips->r[inst->dest] = mips->r[inst->src1] | inst->constant; + IR_NEXT; + IR_CASE(OptOrConst) + mips->r[inst->dest] |= inst->constant; + IR_NEXT; + IR_CASE(XorConst) + mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant; + IR_NEXT; + IR_CASE(Neg) + mips->r[inst->dest] = (u32)(-(s32)mips->r[inst->src1]); + IR_NEXT; + IR_CASE(Not) + mips->r[inst->dest] = ~mips->r[inst->src1]; + IR_NEXT; + IR_CASE(Ext8to32) + mips->r[inst->dest] = SignExtend8ToU32(mips->r[inst->src1]); + IR_NEXT; + IR_CASE(Ext16to32) + mips->r[inst->dest] = SignExtend16ToU32(mips->r[inst->src1]); + IR_NEXT; + IR_CASE(ReverseBits) + mips->r[inst->dest] = ReverseBits32(mips->r[inst->src1]); + IR_NEXT; + + IR_CASE(Load8) mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); - break; - case IROp::Load8Ext: + IR_NEXT; + IR_CASE(Load8Ext) mips->r[inst->dest] = SignExtend8ToU32(Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant)); - break; - case IROp::Load16: + IR_NEXT; + IR_CASE(Load16) mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant); - break; - case IROp::Load16Ext: + IR_NEXT; + IR_CASE(Load16Ext) mips->r[inst->dest] = SignExtend16ToU32(Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant)); - break; - case IROp::Load32: + IR_NEXT; + IR_CASE(Load32) mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); - break; - case IROp::Load32Left: + IR_NEXT; + IR_CASE(Load32Left) { u32 addr = mips->r[inst->src1] + inst->constant; u32 shift = (addr & 3) * 8; u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); u32 destMask = 0x00ffffff >> shift; mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem << (24 - shift)); - break; + IR_NEXT; } - case IROp::Load32Right: + IR_CASE(Load32Right) { u32 addr = mips->r[inst->src1] + inst->constant; u32 shift = (addr & 3) * 8; u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); u32 destMask = 0xffffff00 << (24 - shift); mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift); - break; + IR_NEXT; } - case IROp::Load32Linked: + IR_CASE(Load32Linked) if (inst->dest != MIPS_REG_ZERO) mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); mips->llBit = 1; - break; - case IROp::LoadFloat: + IR_NEXT; + IR_CASE(LoadFloat) mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant); - break; + IR_NEXT; - case IROp::Store8: + IR_CASE(Store8) Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); - break; - case IROp::Store16: + IR_NEXT; + IR_CASE(Store16) Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); - break; - case IROp::Store32: + IR_NEXT; + IR_CASE(Store32) Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); - break; - case IROp::Store32Left: + IR_NEXT; + IR_CASE(Store32Left) { u32 addr = mips->r[inst->src1] + inst->constant; u32 shift = (addr & 3) * 8; @@ -206,9 +413,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { u32 memMask = 0xffffff00 << shift; u32 result = (mips->r[inst->src3] >> (24 - shift)) | (mem & memMask); Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); - break; + IR_NEXT; } - case IROp::Store32Right: + IR_CASE(Store32Right) { u32 addr = mips->r[inst->src1] + inst->constant; u32 shift = (addr & 3) * 8; @@ -216,9 +423,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { u32 memMask = 0x00ffffff >> (24 - shift); u32 result = (mips->r[inst->src3] << shift) | (mem & memMask); Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); - break; + IR_NEXT; } - case IROp::Store32Conditional: + IR_CASE(Store32Conditional) if (mips->llBit) { Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); if (inst->dest != MIPS_REG_ZERO) { @@ -227,32 +434,32 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } else if (inst->dest != MIPS_REG_ZERO) { mips->r[inst->dest] = 0; } - break; - case IROp::StoreFloat: + IR_NEXT; + IR_CASE(StoreFloat) Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant); - break; + IR_NEXT; - case IROp::LoadVec4: + IR_CASE(LoadVec4) { u32 base = mips->r[inst->src1] + inst->constant; // This compiles to a nice SSE load/store on x86, and hopefully similar on ARM. memcpy(&mips->f[inst->dest], Memory::GetPointerUnchecked(base), 4 * 4); - break; + IR_NEXT; } - case IROp::StoreVec4: + IR_CASE(StoreVec4) { u32 base = mips->r[inst->src1] + inst->constant; memcpy((float *)Memory::GetPointerUnchecked(base), &mips->f[inst->dest], 4 * 4); - break; + IR_NEXT; } - case IROp::Vec4Init: + IR_CASE(Vec4Init) { memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float)); - break; + IR_NEXT; } - case IROp::Vec4Shuffle: + IR_CASE(Vec4Shuffle) { // Can't use the SSE shuffle here because it takes an immediate. pshufb with a table would work though, // or a big switch - there are only 256 shuffles possible (4^4) @@ -262,10 +469,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { const u32 dest = inst->dest; for (u32 i = 0; i < 4; i++) mips->f[dest + i] = temp[i]; - break; + IR_NEXT; } - case IROp::Vec4Blend: + IR_CASE(Vec4Blend) { const u32 dest = inst->dest; const u32 src1 = inst->src1; @@ -278,10 +485,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { temp[i] = ((constant >> i) & 1) ? mips->f[src2 + i] : mips->f[src1 + i]; for (u32 i = 0; i < 4; i++) mips->f[dest + i] = temp[i]; - break; + IR_NEXT; } - case IROp::Vec4Mov: + IR_CASE(Vec4Mov) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(&mips->f[inst->src1])); @@ -290,10 +497,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { #else memcpy(&mips->f[inst->dest], &mips->f[inst->src1], 4 * sizeof(float)); #endif - break; + IR_NEXT; } - case IROp::Vec4Add: + IR_CASE(Vec4Add) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_add_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); @@ -303,10 +510,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] + mips->f[inst->src2 + i]; #endif - break; + IR_NEXT; } - case IROp::Vec4Sub: + IR_CASE(Vec4Sub) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_sub_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); @@ -316,10 +523,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] - mips->f[inst->src2 + i]; #endif - break; + IR_NEXT; } - case IROp::Vec4Mul: + IR_CASE(Vec4Mul) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); @@ -329,10 +536,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2 + i]; #endif - break; + IR_NEXT; } - case IROp::Vec4Div: + IR_CASE(Vec4Div) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_div_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); @@ -342,10 +549,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] / mips->f[inst->src2 + i]; #endif - break; + IR_NEXT; } - case IROp::Vec4Scale: + IR_CASE(Vec4Scale) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_set1_ps(mips->f[inst->src2]))); @@ -356,10 +563,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] * factor; #endif - break; + IR_NEXT; } - case IROp::Vec4Neg: + IR_CASE(Vec4Neg) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_xor_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)signBits))); @@ -369,10 +576,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = -mips->f[inst->src1 + i]; #endif - break; + IR_NEXT; } - case IROp::Vec4Abs: + IR_CASE(Vec4Abs) { #if PPSSPP_ARCH(SSE2) _mm_store_ps(&mips->f[inst->dest], _mm_and_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)noSignMask))); @@ -382,10 +589,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = fabsf(mips->f[inst->src1 + i]); #endif - break; + IR_NEXT; } - case IROp::Vec2Unpack16To31: + IR_CASE(Vec2Unpack16To31) { const u32 dest = inst->dest; const u32 src1 = inst->src1; @@ -393,10 +600,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { const u32 temp1 = (mips->fi[src1] & 0xFFFF0000) >> 1; mips->fi[dest] = temp0; mips->fi[dest + 1] = temp1; - break; + IR_NEXT; } - case IROp::Vec2Unpack16To32: + IR_CASE(Vec2Unpack16To32) { const u32 dest = inst->dest; const u32 src1 = inst->src1; @@ -404,10 +611,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { const u32 temp1 = (mips->fi[src1] & 0xFFFF0000); mips->fi[dest] = temp0; mips->fi[dest + 1] = temp1; - break; + IR_NEXT; } - case IROp::Vec4Unpack8To32: + IR_CASE(Vec4Unpack8To32) { // Used in Gran Turismo #if PPSSPP_ARCH(SSE2) @@ -426,26 +633,26 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->fi[inst->dest + 2] = (mips->fi[inst->src1] << 8) & 0xFF000000; mips->fi[inst->dest + 3] = (mips->fi[inst->src1]) & 0xFF000000; #endif - break; + IR_NEXT; } - case IROp::Vec2Pack32To16: + IR_CASE(Vec2Pack32To16) { u32 val = mips->fi[inst->src1] >> 16; mips->fi[inst->dest] = val | (mips->fi[(u32)inst->src1 + 1] & 0xFFFF0000); - break; + IR_NEXT; } - case IROp::Vec2Pack31To16: + IR_CASE(Vec2Pack31To16) { // Used in Tekken 6. Negative lanes clamp to zero. const u32 s0 = (s32)mips->fi[inst->src1] < 0 ? 0 : mips->fi[inst->src1]; const u32 s1 = (s32)mips->fi[(u32)inst->src1 + 1] < 0 ? 0 : mips->fi[(u32)inst->src1 + 1]; mips->fi[inst->dest] = ((s0 >> 15) & 0xFFFF) | ((s1 << 1) & 0xFFFF0000); - break; + IR_NEXT; } - case IROp::Vec4Pack32To8: + IR_CASE(Vec4Pack32To8) { #if PPSSPP_ARCH(SSE2) __m128i src = _mm_loadu_si128((__m128i *)&mips->fi[inst->src1]); @@ -478,10 +685,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { val |= (mips->fi[(u32)inst->src1 + 3]) & 0xFF000000; mips->fi[inst->dest] = val; #endif - break; + IR_NEXT; } - case IROp::Vec4Pack31To8: + IR_CASE(Vec4Pack31To8) { // Used in Tekken 6, Gran Turismo @@ -509,10 +716,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } mips->fi[(u32)inst->dest] = val; #endif - break; + IR_NEXT; } - case IROp::Vec4DuplicateUpperBitsAndShift1: // For vuc2i, the weird one. + IR_CASE(Vec4DuplicateUpperBitsAndShift1) // For vuc2i, the weird one. { const int src1 = inst->src1; const int dest = inst->dest; @@ -526,10 +733,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { for (int i = 0; i < 4; i++) { mips->fi[dest + i] = temp[i]; } - break; + IR_NEXT; } - case IROp::FCmpVfpuBit: + IR_CASE(FCmpVfpuBit) { const int op = inst->dest & 0xF; const int bit = inst->dest >> 4; @@ -559,26 +766,26 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } else { mips->vfpuCtrl[VFPU_CTRL_CC] &= ~(1 << bit); } - break; + IR_NEXT; } - case IROp::FCmpVfpuAggregate: + IR_CASE(FCmpVfpuAggregate) { const u32 mask = inst->dest; const u32 cc = mips->vfpuCtrl[VFPU_CTRL_CC]; int anyBit = (cc & mask) ? 0x10 : 0x00; int allBit = (cc & mask) == mask ? 0x20 : 0x00; mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | anyBit | allBit; - break; + IR_NEXT; } - case IROp::FCmovVfpuCC: + IR_CASE(FCmovVfpuCC) if (((mips->vfpuCtrl[VFPU_CTRL_CC] >> (inst->src2 & 0xf)) & 1) == ((u32)inst->src2 >> 7)) { mips->f[inst->dest] = mips->f[inst->src1]; } - break; + IR_NEXT; - case IROp::Vec4Dot: + IR_CASE(Vec4Dot) { // Not quickly implementable on all platforms, unfortunately. // Though, this is still pretty fast compared to one split into multiple IR instructions. @@ -586,176 +793,176 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { const float *a = &mips->f[(u32)inst->src1]; const float *b = &mips->f[(u32)inst->src2]; mips->f[inst->dest] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; - break; + IR_NEXT; } - case IROp::FSin: + IR_CASE(FSin) mips->f[inst->dest] = vfpu_sin(mips->f[inst->src1]); - break; - case IROp::FCos: + IR_NEXT; + IR_CASE(FCos) mips->f[inst->dest] = vfpu_cos(mips->f[inst->src1]); - break; - case IROp::FRSqrt: + IR_NEXT; + IR_CASE(FRSqrt) mips->f[inst->dest] = vfpu_rsqrt(mips->f[inst->src1]); - break; - case IROp::FRecip: + IR_NEXT; + IR_CASE(FRecip) mips->f[inst->dest] = vfpu_rcp(mips->f[inst->src1]); - break; - case IROp::FAsin: + IR_NEXT; + IR_CASE(FAsin) mips->f[inst->dest] = vfpu_asin(mips->f[inst->src1]); - break; - case IROp::FVSqrt: + IR_NEXT; + IR_CASE(FVSqrt) mips->f[inst->dest] = vfpu_sqrt(mips->f[inst->src1]); - break; - case IROp::FExp2: + IR_NEXT; + IR_CASE(FExp2) mips->f[inst->dest] = vfpu_exp2(mips->f[inst->src1]); - break; - case IROp::FLog2: + IR_NEXT; + IR_CASE(FLog2) mips->f[inst->dest] = vfpu_log2(mips->f[inst->src1]); - break; - case IROp::FSinCos: + IR_NEXT; + IR_CASE(FSinCos) { float s, c; vfpu_sincos(mips->f[inst->src1], s, c); mips->f[inst->dest] = s; mips->f[inst->dest + 1] = c; - break; + IR_NEXT; } - case IROp::FHalfToFloat: + IR_CASE(FHalfToFloat) mips->fi[inst->dest] = vfpu_h2f((u16)(inst->src2 ? mips->fi[inst->src1] >> 16 : mips->fi[inst->src1] & 0xFFFF)); - break; + IR_NEXT; - case IROp::ShlImm: + IR_CASE(ShlImm) mips->r[inst->dest] = mips->r[inst->src1] << (int)inst->src2; - break; - case IROp::ShrImm: + IR_NEXT; + IR_CASE(ShrImm) mips->r[inst->dest] = mips->r[inst->src1] >> (int)inst->src2; - break; - case IROp::SarImm: + IR_NEXT; + IR_CASE(SarImm) mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (int)inst->src2; - break; - case IROp::RorImm: + IR_NEXT; + IR_CASE(RorImm) { u32 x = mips->r[inst->src1]; int sa = inst->src2; mips->r[inst->dest] = (x >> sa) | (x << (32 - sa)); } - break; + IR_NEXT; - case IROp::Shl: + IR_CASE(Shl) mips->r[inst->dest] = mips->r[inst->src1] << (mips->r[inst->src2] & 31); - break; - case IROp::Shr: + IR_NEXT; + IR_CASE(Shr) mips->r[inst->dest] = mips->r[inst->src1] >> (mips->r[inst->src2] & 31); - break; - case IROp::Sar: + IR_NEXT; + IR_CASE(Sar) mips->r[inst->dest] = (s32)mips->r[inst->src1] >> (mips->r[inst->src2] & 31); - break; - case IROp::Ror: + IR_NEXT; + IR_CASE(Ror) { u32 x = mips->r[inst->src1]; int sa = mips->r[inst->src2] & 31; mips->r[inst->dest] = (x >> sa) | (x << (32 - sa)); - break; + IR_NEXT; } - case IROp::Clz: + IR_CASE(Clz) { mips->r[inst->dest] = clz32(mips->r[inst->src1]); - break; + IR_NEXT; } - case IROp::Slt: + IR_CASE(Slt) mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2]; - break; + IR_NEXT; - case IROp::SltU: + IR_CASE(SltU) mips->r[inst->dest] = mips->r[inst->src1] < mips->r[inst->src2]; - break; + IR_NEXT; - case IROp::SltConst: + IR_CASE(SltConst) mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)inst->constant; - break; + IR_NEXT; - case IROp::SltUConst: + IR_CASE(SltUConst) mips->r[inst->dest] = mips->r[inst->src1] < inst->constant; - break; + IR_NEXT; - case IROp::MovZ: + IR_CASE(MovZ) if (mips->r[inst->src1] == 0) mips->r[inst->dest] = mips->r[inst->src2]; - break; - case IROp::MovNZ: + IR_NEXT; + IR_CASE(MovNZ) if (mips->r[inst->src1] != 0) mips->r[inst->dest] = mips->r[inst->src2]; - break; + IR_NEXT; - case IROp::Max: + IR_CASE(Max) mips->r[inst->dest] = (s32)mips->r[inst->src1] > (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2]; - break; - case IROp::Min: + IR_NEXT; + IR_CASE(Min) mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)mips->r[inst->src2] ? mips->r[inst->src1] : mips->r[inst->src2]; - break; + IR_NEXT; - case IROp::MtLo: + IR_CASE(MtLo) mips->lo = mips->r[inst->src1]; - break; - case IROp::MtHi: + IR_NEXT; + IR_CASE(MtHi) mips->hi = mips->r[inst->src1]; - break; - case IROp::MfLo: + IR_NEXT; + IR_CASE(MfLo) mips->r[inst->dest] = mips->lo; - break; - case IROp::MfHi: + IR_NEXT; + IR_CASE(MfHi) mips->r[inst->dest] = mips->hi; - break; + IR_NEXT; - case IROp::Mult: + IR_CASE(Mult) { s64 result = (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); // note: lo is followed by hi, so this is ok (little-endian). - break; + IR_NEXT; } - case IROp::MultU: + IR_CASE(MultU) { u64 result = (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); - break; + IR_NEXT; } - case IROp::Madd: + IR_CASE(Madd) { s64 result; memcpy(&result, &mips->lo, 8); result += (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); - break; + IR_NEXT; } - case IROp::MaddU: + IR_CASE(MaddU) { s64 result; memcpy(&result, &mips->lo, 8); result += (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); - break; + IR_NEXT; } - case IROp::Msub: + IR_CASE(Msub) { s64 result; memcpy(&result, &mips->lo, 8); result -= (s64)(s32)mips->r[inst->src1] * (s64)(s32)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); - break; + IR_NEXT; } - case IROp::MsubU: + IR_CASE(MsubU) { s64 result; memcpy(&result, &mips->lo, 8); result -= (u64)mips->r[inst->src1] * (u64)mips->r[inst->src2]; memcpy(&mips->lo, &result, 8); - break; + IR_NEXT; } - case IROp::Div: + IR_CASE(Div) { s32 numerator = (s32)mips->r[inst->src1]; s32 denominator = (s32)mips->r[inst->src2]; @@ -770,9 +977,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->lo = numerator < 0 ? 1 : -1; mips->hi = numerator; } - break; + IR_NEXT; } - case IROp::DivU: + IR_CASE(DivU) { u32 numerator = mips->r[inst->src1]; u32 denominator = mips->r[inst->src2]; @@ -783,29 +990,29 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->lo = numerator <= 0xFFFF ? 0xFFFF : -1; mips->hi = numerator; } - break; + IR_NEXT; } - case IROp::BSwap16: + IR_CASE(BSwap16) { u32 x = mips->r[inst->src1]; // Don't think we can beat this with intrinsics. mips->r[inst->dest] = ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8); - break; + IR_NEXT; } - case IROp::BSwap32: + IR_CASE(BSwap32) { mips->r[inst->dest] = swap32(mips->r[inst->src1]); - break; + IR_NEXT; } - case IROp::FAdd: + IR_CASE(FAdd) mips->f[inst->dest] = mips->f[inst->src1] + mips->f[inst->src2]; - break; - case IROp::FSub: + IR_NEXT; + IR_CASE(FSub) mips->f[inst->dest] = mips->f[inst->src1] - mips->f[inst->src2]; - break; - case IROp::FMul: + IR_NEXT; + IR_CASE(FMul) #if 1 { float a = mips->f[inst->src1]; @@ -816,7 +1023,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->f[inst->dest] = a * b; } } - break; + IR_NEXT; #else // Not sure if faster since it needs to load the operands twice? But the code is simpler. { @@ -828,13 +1035,13 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } else { mips->f[inst->dest] = mips->f[inst->src1] * mips->f[inst->src2]; } - break; + IR_NEXT; } #endif - case IROp::FDiv: + IR_CASE(FDiv) mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2]; - break; - case IROp::FMin: + IR_NEXT; + IR_CASE(FMin) if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) { // See interpreter for this logic: this is for vmin, we're comparing mantissa+exp. if (mips->fs[inst->src1] < 0 && mips->fs[inst->src2] < 0) { @@ -845,8 +1052,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } else { mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]); } - break; - case IROp::FMax: + IR_NEXT; + IR_CASE(FMax) if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) { // See interpreter for this logic: this is for vmax, we're comparing mantissa+exp. if (mips->fs[inst->src1] < 0 && mips->fs[inst->src2] < 0) { @@ -857,15 +1064,15 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { } else { mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]); } - break; + IR_NEXT; - case IROp::FMov: + IR_CASE(FMov) mips->f[inst->dest] = mips->f[inst->src1]; - break; - case IROp::FAbs: + IR_NEXT; + IR_CASE(FAbs) mips->f[inst->dest] = fabsf(mips->f[inst->src1]); - break; - case IROp::FSqrt: + IR_NEXT; + IR_CASE(FSqrt) { float src = mips->f[inst->src1]; mips->f[inst->dest] = sqrtf(src); @@ -873,20 +1080,20 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { if (src < 0.0f) { mips->fi[inst->dest] = 0x7FC00000; } - break; + IR_NEXT; } - case IROp::FNeg: + IR_CASE(FNeg) mips->f[inst->dest] = -mips->f[inst->src1]; - break; - case IROp::FSat0_1: + IR_NEXT; + IR_CASE(FSat0_1) // We have to do this carefully to handle NAN and -0.0f. mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], 0.0f, 1.0f); - break; - case IROp::FSatMinus1_1: + IR_NEXT; + IR_CASE(FSatMinus1_1) mips->f[inst->dest] = vfpu_clamp(mips->f[inst->src1], -1.0f, 1.0f); - break; + IR_NEXT; - case IROp::FSign: + IR_CASE(FSign) { // Bitwise trickery. Denormals give zero, as on the hardware. u32 val; @@ -897,43 +1104,43 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->f[inst->dest] = 1.0f; else mips->f[inst->dest] = -1.0f; - break; + IR_NEXT; } - case IROp::FpCondFromReg: + IR_CASE(FpCondFromReg) // Note: the register is in src1, see the "_G" meta - the native backends read it there. mips->fpcond = mips->r[inst->src1]; - break; - case IROp::FpCondToReg: + IR_NEXT; + IR_CASE(FpCondToReg) mips->r[inst->dest] = mips->fpcond; - break; - case IROp::FpCtrlFromReg: + IR_NEXT; + IR_CASE(FpCtrlFromReg) mips->fcr31 = mips->r[inst->src1] & 0x0181FFFF; // Extract the new fpcond value. // TODO: Is it really helping us to keep it separate? mips->fpcond = (mips->fcr31 >> 23) & 1; - break; - case IROp::FpCtrlToReg: + IR_NEXT; + IR_CASE(FpCtrlToReg) // Update the fpcond bit first. mips->fcr31 = (mips->fcr31 & ~(1 << 23)) | ((mips->fpcond & 1) << 23); mips->r[inst->dest] = mips->fcr31; - break; - case IROp::VfpuCtrlToReg: + IR_NEXT; + IR_CASE(VfpuCtrlToReg) mips->r[inst->dest] = mips->vfpuCtrl[inst->src1]; - break; - case IROp::FRound: + IR_NEXT; + IR_CASE(FRound) mips->fs[inst->dest] = SaturatedFloatToInt(round_ieee_754(mips->f[inst->src1])); - break; - case IROp::FTrunc: + IR_NEXT; + IR_CASE(FTrunc) mips->fs[inst->dest] = SaturatedFloatToInt(truncf(mips->f[inst->src1])); - break; - case IROp::FCeil: + IR_NEXT; + IR_CASE(FCeil) mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(mips->f[inst->src1])); - break; - case IROp::FFloor: + IR_NEXT; + IR_CASE(FFloor) mips->fs[inst->dest] = SaturatedFloatToInt(floorf(mips->f[inst->src1])); - break; - case IROp::FCmp: + IR_NEXT; + IR_CASE(FCmp) switch (inst->dest) { case IRFpCompareMode::False: mips->fpcond = 0; @@ -964,12 +1171,12 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { mips->fpcond = !(mips->f[inst->src1] >= mips->f[inst->src2]); break; } - break; + IR_NEXT; - case IROp::FCvtSW: + IR_CASE(FCvtSW) mips->f[inst->dest] = (float)mips->fs[inst->src1]; - break; - case IROp::FCvtWS: + IR_NEXT; + IR_CASE(FCvtWS) { float src = mips->f[inst->src1]; // TODO: Inline assembly to use here would be better. @@ -979,18 +1186,18 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { case IRRoundMode::CEIL_2: mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(src)); break; case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = SaturatedFloatToInt(floorf(src)); break; } - break; //cvt.w.s + IR_NEXT; //cvt.w.s } - case IROp::FCvtScaledSW: + IR_CASE(FCvtScaledSW) mips->f[inst->dest] = (float)mips->fs[inst->src1] * (1.0f / (1UL << (inst->src2 & 0x1F))); - break; - case IROp::FCvtScaledWS: + IR_NEXT; + IR_CASE(FCvtScaledWS) { float src = mips->f[inst->src1]; if (my_isnan(src)) { // TODO: True for negatives too? mips->fs[inst->dest] = 2147483647L; - break; + IR_NEXT; } float mult = (float)(1UL << (inst->src2 & 0x1F)); @@ -1008,70 +1215,83 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = (int)floor(sv); break; } } - break; + IR_NEXT; } - case IROp::FMovFromGPR: + IR_CASE(FMovFromGPR) memcpy(&mips->f[inst->dest], &mips->r[inst->src1], 4); - break; - case IROp::OptFCvtSWFromGPR: + IR_NEXT; + IR_CASE(OptFCvtSWFromGPR) mips->f[inst->dest] = (float)(int)mips->r[inst->src1]; - break; - case IROp::FMovToGPR: + IR_NEXT; + IR_CASE(FMovToGPR) memcpy(&mips->r[inst->dest], &mips->f[inst->src1], 4); - break; - case IROp::OptFMovToGPRShr8: + IR_NEXT; + IR_CASE(OptFMovToGPRShr8) { u32 temp; memcpy(&temp, &mips->f[inst->src1], 4); mips->r[inst->dest] = temp >> 8; - break; + IR_NEXT; } - case IROp::ExitToConst: + IR_CASE(ExitToConst) return inst->constant; - case IROp::ExitToReg: + IR_CASE(ExitToReg) return mips->r[inst->src1]; - case IROp::ExitToConstIfEq: + // The next instruction is the ExitToConst to take otherwise, never run itself. + IR_CASE(OptExitToConstIfEqElse) + return mips->r[inst->src1] == mips->r[inst->src2] ? inst->constant : inst[1].constant; + IR_CASE(OptExitToConstIfNeqElse) + return mips->r[inst->src1] != mips->r[inst->src2] ? inst->constant : inst[1].constant; + IR_CASE(OptExitToConstIfGtZElse) + return (s32)mips->r[inst->src1] > 0 ? inst->constant : inst[1].constant; + IR_CASE(OptExitToConstIfGeZElse) + return (s32)mips->r[inst->src1] >= 0 ? inst->constant : inst[1].constant; + IR_CASE(OptExitToConstIfLtZElse) + return (s32)mips->r[inst->src1] < 0 ? inst->constant : inst[1].constant; + IR_CASE(OptExitToConstIfLeZElse) + return (s32)mips->r[inst->src1] <= 0 ? inst->constant : inst[1].constant; + IR_CASE(ExitToConstIfEq) if (mips->r[inst->src1] == mips->r[inst->src2]) return inst->constant; - break; - case IROp::ExitToConstIfNeq: + IR_NEXT; + IR_CASE(ExitToConstIfNeq) if (mips->r[inst->src1] != mips->r[inst->src2]) return inst->constant; - break; - case IROp::ExitToConstIfGtZ: + IR_NEXT; + IR_CASE(ExitToConstIfGtZ) if ((s32)mips->r[inst->src1] > 0) return inst->constant; - break; - case IROp::ExitToConstIfGeZ: + IR_NEXT; + IR_CASE(ExitToConstIfGeZ) if ((s32)mips->r[inst->src1] >= 0) return inst->constant; - break; - case IROp::ExitToConstIfLtZ: + IR_NEXT; + IR_CASE(ExitToConstIfLtZ) if ((s32)mips->r[inst->src1] < 0) return inst->constant; - break; - case IROp::ExitToConstIfLeZ: + IR_NEXT; + IR_CASE(ExitToConstIfLeZ) if ((s32)mips->r[inst->src1] <= 0) return inst->constant; - break; + IR_NEXT; - case IROp::Downcount: + IR_CASE(Downcount) mips->downcount -= (int)inst->constant; - break; + IR_NEXT; - case IROp::SetPC: + IR_CASE(SetPC) mips->pc = mips->r[inst->src1]; - break; + IR_NEXT; - case IROp::SetPCConst: + IR_CASE(SetPCConst) mips->pc = inst->constant; - break; + IR_NEXT; - case IROp::Syscall: + IR_CASE(Syscall) // IROp::SetPC was (hopefully) executed before. { // If we get here, the syscall is valid. @@ -1080,10 +1300,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { if (coreState != CORE_RUNNING_CPU) { CoreTiming::ForceCheck(mips); } - break; + IR_NEXT; } - case IROp::SyscallUnresolved: + IR_CASE(SyscallUnresolved) { // If we get here, the syscall is invalid. u32 pc = inst->constant; @@ -1092,114 +1312,112 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) { // hm, what's this for? CoreTiming::ForceCheck(mips); } - break; + IR_NEXT; } - case IROp::ExitToPC: + IR_CASE(ExitToPC) return mips->pc; - case IROp::Interpret: // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging. + IR_CASE(Interpret) // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging. { MIPSOpcode op(inst->constant); MIPSInterpret(mips, op); - break; + IR_NEXT; } - case IROp::CallReplacement: + IR_CASE(CallReplacement) { int funcIndex = inst->constant; const ReplacementTableEntry *f = GetReplacementFunc(funcIndex); int cycles = f->replaceFunc(); mips->r[inst->dest] = cycles < 0 ? -1 : 0; mips->downcount -= cycles < 0 ? -cycles : cycles; - break; + IR_NEXT; } - case IROp::SetCtrlVFPU: + IR_CASE(SetCtrlVFPU) mips->vfpuCtrl[inst->dest] = inst->constant; - break; + IR_NEXT; - case IROp::SetCtrlVFPUReg: + IR_CASE(SetCtrlVFPUReg) mips->vfpuCtrl[inst->dest] = mips->r[inst->src1]; - break; + IR_NEXT; - case IROp::SetCtrlVFPUFReg: + IR_CASE(SetCtrlVFPUFReg) memcpy(&mips->vfpuCtrl[inst->dest], &mips->f[inst->src1], 4); - break; + IR_NEXT; - case IROp::ApplyRoundingMode: + IR_CASE(ApplyRoundingMode) ApplyHostRoundingMode(mips); - break; - case IROp::RestoreRoundingMode: + IR_NEXT; + IR_CASE(RestoreRoundingMode) RestoreHostRoundingMode(); - break; - case IROp::UpdateRoundingMode: + IR_NEXT; + IR_CASE(UpdateRoundingMode) // TODO: Implement - break; + IR_NEXT; - case IROp::Break: + IR_CASE(Break) Core_BreakException(mips->pc); return mips->pc + 4; - case IROp::Breakpoint: + IR_CASE(Breakpoint) if (IRRunBreakpoint(inst->constant)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; + IR_NEXT; - case IROp::MemoryCheck: + IR_CASE(MemoryCheck) if (IRRunMemCheck(mips->pc + inst->dest, mips->r[inst->src1] + inst->constant)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; + IR_NEXT; - case IROp::ValidateAddress8: + IR_CASE(ValidateAddress8) if (RunValidateAddress<1>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; - case IROp::ValidateAddress16: + IR_NEXT; + IR_CASE(ValidateAddress16) if (RunValidateAddress<2>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; - case IROp::ValidateAddress32: + IR_NEXT; + IR_CASE(ValidateAddress32) if (RunValidateAddress<4>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; - case IROp::ValidateAddress128: + IR_NEXT; + IR_CASE(ValidateAddress128) if (RunValidateAddress<16>(mips->pc, mips->r[inst->src1] + inst->constant, inst->src2)) { CoreTiming::ForceCheck(mips); return mips->pc; } - break; - case IROp::LogIRBlock: + IR_NEXT; + IR_CASE(LogIRBlock) if (mipsTracer.tracing_enabled) { mipsTracer.executed_blocks.push_back(inst->constant); } - break; + IR_NEXT; - case IROp::Nop: // Unused, add a break if we start using it to avoid UNREACHABLE. - case IROp::Bad: + IR_CASE(Nop) // Unused, add a break if we start using it to avoid UNREACHABLE. + IR_CASE(Bad) default: // Unimplemented IR op. Bad. We define it as unreachable so the compiler can optimize better (remove the range check). UNREACHABLE(); - break; + IR_NEXT; } - -#ifdef _DEBUG - if (mips->r[0] != 0) - Crash(); -#endif - inst++; } // We should not reach here anymore. return 0; } + +#undef IR_NEXT +#undef IR_CASE +#undef IR_CHECK_ZERO_REG diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index ad9d556c2a..11d3059dde 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -1955,8 +1955,9 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o bool ReduceVec4Flush(const IRWriter &in, IRWriter &out, const IROptions &opts) { CONDITIONAL_DISABLE; - // Only do this when using a SIMD backend. - if (!opts.preferVec4) { + // Only do this when using a SIMD backend. The interpreter has no flushes to avoid, and would + // only get more instructions to dispatch. + if (!opts.preferVec4 || opts.optimizeForInterpreter) { DISABLE; } @@ -2406,6 +2407,19 @@ bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions & } out.Write(inst); break; + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + // With an ExitToConst right after, one op can pick either target. The ExitToConst stays + // as the second target's holder. + static_assert((int)IROp::OptExitToConstIfLeZElse - (int)IROp::OptExitToConstIfEqElse == (int)IROp::ExitToConstIfLeZ - (int)IROp::ExitToConstIfEq, "Else exits must match the exits' order"); + if (!last && in.GetInstructions()[i + 1].op == IROp::ExitToConst) + inst.op = (IROp)((int)IROp::OptExitToConstIfEqElse + ((int)inst.op - (int)IROp::ExitToConstIfEq)); + out.Write(inst); + break; default: out.Write(inst); break; diff --git a/unittest/TestIRPassSimplify.cpp b/unittest/TestIRPassSimplify.cpp index b40e3468d8..43669d3c63 100644 --- a/unittest/TestIRPassSimplify.cpp +++ b/unittest/TestIRPassSimplify.cpp @@ -510,6 +510,19 @@ static const IRVerification tests[] = { }, { &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() {