#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Profiler/Profiler.h"
#include "Core/Reporting.h"
#include "Core/Config.h"
#include "Core/MemMap.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/HLETables.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/MIPS/IR/IRFrontend.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#define _RS MIPS_GET_RS(op)
#define _RT MIPS_GET_RT(op)
#define _RD MIPS_GET_RD(op)
#define _FS MIPS_GET_FS(op)
#define _FT MIPS_GET_FT(op)
#define _FD MIPS_GET_FD(op)
#define _SA MIPS_GET_SA(op)
#define _POS ((op>> 6) & 0x1F)
#define _SIZE ((op>>11) & 0x1F)
#define _IMM26 (op & 0x03FFFFFF)
#define TARGET16 ((int)(SignExtend16ToU32(op) << 2))
#define TARGET26 (_IMM26 << 2)
#define LOOPOPTIMIZATION 0
#define MIPS_IS_BREAK(op) (((op) & 0xFC00003F) == 13)
using namespace MIPSAnalyst;
namespace MIPSComp
{
void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rt, rs);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
if (likely && offset == 4 && MIPS_IS_BREAK(branchInfo.delaySlotOp)) {
EatInstruction(branchInfo.delaySlotOp);
js.downcountAmount--;
return;
}
MIPSGPReg lhs = rs;
MIPSGPReg rhs = rt;
if (!branchInfo.delaySlotIsNice && !likely) {
if (rs != 0) {
ir.Write(IROp::Mov, IRTEMP_LHS, rs);
lhs = (MIPSGPReg)IRTEMP_LHS;
}
if (rt != 0) {
ir.Write(IROp::Mov, IRTEMP_RHS, rt);
rhs = (MIPSGPReg)IRTEMP_RHS;
}
}
if (!likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(ResolveNotTakenTarget(branchInfo)), lhs, rhs);
if (likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
if (branchInfo.delaySlotIsBranch) {
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), andLink, likely);
branchInfo.delaySlotIsNice = IsDelaySlotNiceReg(op, branchInfo.delaySlotOp, rs);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
MIPSGPReg lhs = rs;
if (!branchInfo.delaySlotIsNice) {
ir.Write(IROp::Mov, IRTEMP_LHS, rs);
lhs = (MIPSGPReg)IRTEMP_LHS;
}
if (andLink)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 8);
if (!likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(ResolveNotTakenTarget(branchInfo)), lhs);
if (likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
if (branchInfo.delaySlotIsBranch) {
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::Comp_RelBranch(MIPSOpcode op) {
switch (op >> 26) {
case 4: BranchRSRTComp(op, IRComparison::NotEqual, false); break;
case 5: BranchRSRTComp(op, IRComparison::Equal, false); break;
case 6: BranchRSZeroComp(op, IRComparison::Greater, false, false); break;
case 7: BranchRSZeroComp(op, IRComparison::LessEqual, false, false); break;
case 20: BranchRSRTComp(op, IRComparison::NotEqual, true); break;
case 21: BranchRSRTComp(op, IRComparison::Equal, true); break;
case 22: BranchRSZeroComp(op, IRComparison::Greater, false, true); break;
case 23: BranchRSZeroComp(op, IRComparison::LessEqual, false, true); break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
void IRFrontend::Comp_RelBranchRI(MIPSOpcode op) {
switch ((op >> 16) & 0x1F) {
case 0: BranchRSZeroComp(op, IRComparison::GreaterEqual, false, false); break;
case 1: BranchRSZeroComp(op, IRComparison::Less, false, false); break;
case 2: BranchRSZeroComp(op, IRComparison::GreaterEqual, false, true); break;
case 3: BranchRSZeroComp(op, IRComparison::Less, false, true); break;
case 16: BranchRSZeroComp(op, IRComparison::GreaterEqual, true, false); break;
case 17: BranchRSZeroComp(op, IRComparison::Less, true, false); break;
case 18: BranchRSZeroComp(op, IRComparison::GreaterEqual, true, true); break;
case 19: BranchRSZeroComp(op, IRComparison::Less, true, true); break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
}
void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
ir.Write(IROp::FpCondToReg, IRTEMP_LHS);
if (!likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(ResolveNotTakenTarget(branchInfo)), IRTEMP_LHS, 0);
if (likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
if (branchInfo.delaySlotIsBranch) {
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::Comp_FPUBranch(MIPSOpcode op) {
switch((op >> 16) & 0x1f) {
case 0: BranchFPFlag(op, IRComparison::NotEqual, false); break;
case 1: BranchFPFlag(op, IRComparison::Equal, false); break;
case 2: BranchFPFlag(op, IRComparison::NotEqual, true); break;
case 3: BranchFPFlag(op, IRComparison::Equal, true); break;
default:
_dbg_assert_msg_( 0, "Trying to interpret instruction that can't be interpreted");
break;
}
}
void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
ir.Write(IROp::VfpuCtrlToReg, IRTEMP_LHS, VFPU_CTRL_CC);
if (!likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
int imm3 = (op >> 18) & 7;
ir.Write(IROp::AndConst, IRTEMP_LHS, IRTEMP_LHS, ir.AddConstant(1 << imm3));
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(ResolveNotTakenTarget(branchInfo)), IRTEMP_LHS, 0);
if (likely && !branchInfo.delaySlotIsBranch)
CompileDelaySlot();
if (branchInfo.delaySlotIsBranch) {
if ((branchInfo.delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((branchInfo.delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(branchInfo.delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::Comp_VBranch(MIPSOpcode op) {
switch ((op >> 16) & 3) {
case 0: BranchVFPUFlag(op, IRComparison::NotEqual, false); break;
case 1: BranchVFPUFlag(op, IRComparison::Equal, false); break;
case 2: BranchVFPUFlag(op, IRComparison::NotEqual, true); break;
case 3: BranchVFPUFlag(op, IRComparison::Equal, true); break;
}
}
void IRFrontend::Comp_Jump(MIPSOpcode op) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
u32 off = TARGET26;
u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off;
if (!Memory::IsValidAddress(targetAddr)) {
ERROR_LOG(Log::JIT, "Jump to invalid address: %08x", targetAddr);
}
switch (op >> 26) {
case 2:
CompileDelaySlot();
break;
case 3:
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 8);
CompileDelaySlot();
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::Comp_JumpReg(MIPSOpcode op) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(Log::JIT, "Branch in JumpReg delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
return;
}
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
bool andLink = (op & 0x3f) == 9 && rd != MIPS_REG_ZERO;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rs);
if (andLink && rs == rd)
delaySlotIsNice = false;
int destReg;
if (IsSyscall(delaySlotOp)) {
ir.Write(IROp::SetPC, 0, rs);
if (andLink)
ir.WriteSetConstant(rd, GetCompilerPC() + 8);
CompileDelaySlot();
js.compilerPC += 4;
return;
} else if (delaySlotIsNice) {
if (andLink)
ir.WriteSetConstant(rd, GetCompilerPC() + 8);
CompileDelaySlot();
destReg = rs;
FlushAll();
} else {
ir.Write(IROp::Mov, IRTEMP_LHS, rs);
destReg = IRTEMP_LHS;
if (andLink)
ir.WriteSetConstant(rd, GetCompilerPC() + 8);
CompileDelaySlot();
FlushAll();
}
switch (op & 0x3f)
{
case 8:
break;
case 9:
break;
default:
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
break;
}
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
ir.Write(IROp::ExitToReg, 0, destReg, 0);
js.compilerPC += 4;
js.compiling = false;
}
void IRFrontend::Comp_Syscall(MIPSOpcode op) {
int dcAmount = js.downcountAmount + (js.inDelaySlot ? -1 : 0);
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
if (!js.inDelaySlot) {
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC() + 4));
}
FlushAll();
RestoreRoundingMode();
ir.Write(IROp::Syscall, 0, ir.AddConstant(op.encoding));
ApplyRoundingMode();
ir.Write(IROp::ExitToPC);
js.compiling = false;
}
void IRFrontend::Comp_Break(MIPSOpcode op) {
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
ir.Write(IROp::Break);
js.compiling = false;
}
}