mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 09:49:03 +02:00
The IR frontend compiles on past a conditional branch that isn't taken, so the IR cores don't check downcount there, while the interpreter ended its block at every branch. Events then landed on different instructions, and a game's trace left the interpreter's within a few hundred syscalls. The interpreter now applies the same rule at run time: not likely, the operands not decided by their registers, no branch in the delay slot, no prefix pending after the delay slot, and the same instruction and branch limits, counted the same way. It also goes on past a beql/bnel over a break, which the IR frontend drops. The limits move to IRInst.h so both use them. With it, the interpreter, IR interpreter and ARM64 JIT produce the same trace of syscalls and thread switches over 5 emulated seconds of God of War, Tekken 6 and Daxter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
498 lines
17 KiB
C++
498 lines
17 KiB
C++
// Copyright (c) 2012- 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 "Common/Data/Convert/SmallDataConvert.h"
|
|
#include "Common/Profiler/Profiler.h"
|
|
|
|
#include "Core/Reporting.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);
|
|
|
|
// Often, div/divu are followed by a likely "break" if the divisor was zero.
|
|
// Stalling is not really useful for us, so we optimize this out.
|
|
if (likely && offset == 4 && MIPS_IS_BREAK(branchInfo.delaySlotOp)) {
|
|
// Okay, let's not actually branch at all. We're done here.
|
|
EatInstruction(branchInfo.delaySlotOp);
|
|
// Let's not double-count the downcount, though.
|
|
js.downcountAmount--;
|
|
return;
|
|
}
|
|
|
|
MIPSGPReg lhs = rs;
|
|
MIPSGPReg rhs = rt;
|
|
if (!branchInfo.delaySlotIsNice && !likely) { // if likely, we don't need this
|
|
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, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
FlushAll();
|
|
if (CanContinuePastBranch(branchInfo, rs == rt)) {
|
|
ir.Write(ComparisonToExit(Invert(cc)), 0, lhs, rhs, targetAddr);
|
|
js.compilerPC += 4;
|
|
return;
|
|
}
|
|
ir.Write(ComparisonToExit(cc), 0, lhs, rhs, ResolveNotTakenTarget(branchInfo));
|
|
// This makes the block "impure" :(
|
|
if (likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
if (branchInfo.delaySlotIsBranch) {
|
|
// We still link when the branch is taken (targetAddr case.)
|
|
// Remember, it's from the perspective of the delay slot, so +12.
|
|
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, 0, 0, 0, targetAddr);
|
|
|
|
// Account for the delay slot.
|
|
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;
|
|
// The link happens after rs is read, which matters for ra (cpu/cpu_alu/branch_link_ra).
|
|
if (!branchInfo.delaySlotIsNice || (andLink && rs == MIPS_REG_RA)) {
|
|
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, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
FlushAll();
|
|
if (CanContinuePastBranch(branchInfo, rs == MIPS_REG_ZERO)) {
|
|
ir.Write(ComparisonToExit(Invert(cc)), 0, lhs, 0, targetAddr);
|
|
js.compilerPC += 4;
|
|
return;
|
|
}
|
|
ir.Write(ComparisonToExit(cc), 0, lhs, 0, ResolveNotTakenTarget(branchInfo));
|
|
if (likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
if (branchInfo.delaySlotIsBranch) {
|
|
// We still link when the branch is taken (targetAddr case.)
|
|
// Remember, it's from the perspective of the delay slot, so +12.
|
|
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);
|
|
}
|
|
|
|
// Taken
|
|
FlushAll();
|
|
ir.Write(IROp::ExitToConst, 0, 0, 0, targetAddr);
|
|
|
|
// Account for the delay slot.
|
|
js.compilerPC += 4;
|
|
js.compiling = false;
|
|
}
|
|
|
|
// Whether to go on compiling the not-taken path after a conditional branch, exiting to the target
|
|
// when it's taken, rather than ending the block. A loop then needs one exit per iteration, and the
|
|
// code around the branch gets compiled together. Not for likely branches, whose delay slot belongs to
|
|
// the taken path, or for branches decided by their operands (b, an always-taken beq zero, zero, is
|
|
// usually followed by something else entirely). Nor with a prefix pending: blocks assume default
|
|
// prefixes on entry, and only the end of a block checks that it leaves them so (CheckRounding()).
|
|
bool IRFrontend::CanContinuePastBranch(const BranchInfo &branchInfo, bool constantOperands) {
|
|
if (!opts.continueBranches || branchInfo.likely || branchInfo.delaySlotIsBranch || constantOperands || js.MayHavePrefix())
|
|
return false;
|
|
if (js.numInstructions >= IR_MAX_CONTINUED_INSTRUCTIONS || js.continuedBranches >= IR_MAX_CONTINUED_BRANCHES)
|
|
return false;
|
|
js.continuedBranches++;
|
|
return true;
|
|
}
|
|
|
|
void IRFrontend::Comp_RelBranch(MIPSOpcode op) {
|
|
// The CC flags here should be opposite of the actual branch because they skip the branching action.
|
|
switch (op >> 26) {
|
|
case 4: BranchRSRTComp(op, IRComparison::NotEqual, false); break;//beq
|
|
case 5: BranchRSRTComp(op, IRComparison::Equal, false); break;//bne
|
|
|
|
case 6: BranchRSZeroComp(op, IRComparison::Greater, false, false); break;//blez
|
|
case 7: BranchRSZeroComp(op, IRComparison::LessEqual, false, false); break;//bgtz
|
|
|
|
case 20: BranchRSRTComp(op, IRComparison::NotEqual, true); break;//beql
|
|
case 21: BranchRSRTComp(op, IRComparison::Equal, true); break;//bnel
|
|
|
|
case 22: BranchRSZeroComp(op, IRComparison::Greater, false, true); break;//blezl
|
|
case 23: BranchRSZeroComp(op, IRComparison::LessEqual, false, true); break;//bgtzl
|
|
|
|
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; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
|
|
case 1: BranchRSZeroComp(op, IRComparison::Less, false, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
|
|
case 2: BranchRSZeroComp(op, IRComparison::GreaterEqual, false, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
|
|
case 3: BranchRSZeroComp(op, IRComparison::Less, false, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
|
|
case 16: BranchRSZeroComp(op, IRComparison::GreaterEqual, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltzal
|
|
case 17: BranchRSZeroComp(op, IRComparison::Less, true, false); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgezal
|
|
case 18: BranchRSZeroComp(op, IRComparison::GreaterEqual, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else SkipLikely(); break;//bltzall
|
|
case 19: BranchRSZeroComp(op, IRComparison::Less, true, true); break; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else SkipLikely(); break;//bgezall
|
|
default:
|
|
_dbg_assert_msg_(false,"Trying to compile instruction that can't be compiled");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If likely is set, discard the branch slot if NOT taken.
|
|
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);
|
|
|
|
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
|
|
ir.Write(IROp::FpCondToReg, IRTEMP_LHS);
|
|
if (!likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
|
|
int dcAmount = js.downcountAmount;
|
|
ir.Write(IROp::Downcount, 0, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
FlushAll();
|
|
// Not taken
|
|
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, ResolveNotTakenTarget(branchInfo));
|
|
// Taken
|
|
if (likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
if (branchInfo.delaySlotIsBranch) {
|
|
// We still link when the branch is taken (targetAddr case.)
|
|
// Remember, it's from the perspective of the delay slot, so +12.
|
|
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, 0, 0, 0, targetAddr);
|
|
|
|
// Account for the delay slot.
|
|
js.compilerPC += 4;
|
|
js.compiling = false;
|
|
}
|
|
|
|
void IRFrontend::Comp_FPUBranch(MIPSOpcode op) {
|
|
switch((op >> 16) & 0x1f) {
|
|
case 0: BranchFPFlag(op, IRComparison::NotEqual, false); break; // bc1f
|
|
case 1: BranchFPFlag(op, IRComparison::Equal, false); break; // bc1t
|
|
case 2: BranchFPFlag(op, IRComparison::NotEqual, true); break; // bc1fl
|
|
case 3: BranchFPFlag(op, IRComparison::Equal, true); break; // bc1tl
|
|
default:
|
|
_dbg_assert_msg_( 0, "Trying to interpret instruction that can't be interpreted");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If likely is set, discard the branch slot if NOT taken.
|
|
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);
|
|
|
|
// Sometimes there's a VFPU branch in a delay slot (Disgaea 2: Dark Hero Days, Zettai Hero Project, La Pucelle)
|
|
// The behavior is undefined - the CPU may take the second branch even if the first one passes.
|
|
// However, it does consistently try each branch, which these games seem to expect.
|
|
if (!likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
|
|
int dcAmount = js.downcountAmount;
|
|
ir.Write(IROp::Downcount, 0, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
int imm3 = (op >> 18) & 7;
|
|
|
|
ir.Write(IROp::AndConst, IRTEMP_LHS, IRTEMP_LHS, 0, 1 << imm3);
|
|
FlushAll();
|
|
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, ResolveNotTakenTarget(branchInfo));
|
|
|
|
if (likely && !branchInfo.delaySlotIsBranch)
|
|
CompileDelaySlot();
|
|
if (branchInfo.delaySlotIsBranch) {
|
|
// We still link when the branch is taken (targetAddr case.)
|
|
// Remember, it's from the perspective of the delay slot, so +12.
|
|
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);
|
|
}
|
|
|
|
// Taken
|
|
FlushAll();
|
|
ir.Write(IROp::ExitToConst, 0, 0, 0, targetAddr);
|
|
|
|
// Account for the delay slot.
|
|
js.compilerPC += 4;
|
|
js.compiling = false;
|
|
}
|
|
|
|
void IRFrontend::Comp_VBranch(MIPSOpcode op) {
|
|
switch ((op >> 16) & 3) {
|
|
case 0: BranchVFPUFlag(op, IRComparison::NotEqual, false); break; // bvf
|
|
case 1: BranchVFPUFlag(op, IRComparison::Equal, false); break; // bvt
|
|
case 2: BranchVFPUFlag(op, IRComparison::NotEqual, true); break; // bvfl
|
|
case 3: BranchVFPUFlag(op, IRComparison::Equal, true); break; // bvtl
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Might be a stubbed address or something?
|
|
if (!Memory::IsValidAddress(targetAddr)) {
|
|
// If preloading, flush - this block will likely be fixed later.
|
|
ERROR_LOG(Log::JIT, "Jump to invalid address: %08x", targetAddr);
|
|
// TODO: Mark this block dirty or something? May be indication it will be changed by imports.
|
|
// Continue so the block gets completed and crashes properly.
|
|
}
|
|
|
|
js.downcountAmount += MIPSGetInstructionCycleEstimate(GetOffsetInstruction(1));
|
|
|
|
switch (op >> 26) {
|
|
case 2: //j
|
|
CompileDelaySlot();
|
|
break;
|
|
|
|
case 3: //jal
|
|
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, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
FlushAll();
|
|
ir.Write(IROp::ExitToConst, 0, 0, 0, targetAddr);
|
|
|
|
// Account for the delay slot.
|
|
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();
|
|
// Syscall (the delay slot) does FlushAll.
|
|
|
|
// Account for the delay slot itself in total bytes.
|
|
js.compilerPC += 4;
|
|
return; // Syscall (delay slot) wrote exit code.
|
|
} else if (delaySlotIsNice) {
|
|
if (andLink)
|
|
ir.WriteSetConstant(rd, GetCompilerPC() + 8);
|
|
CompileDelaySlot();
|
|
destReg = rs; // Safe because FlushAll doesn't change any regs
|
|
FlushAll();
|
|
} else {
|
|
// Bad delay slot.
|
|
ir.Write(IROp::Mov, IRTEMP_LHS, rs);
|
|
destReg = IRTEMP_LHS;
|
|
if (andLink)
|
|
ir.WriteSetConstant(rd, GetCompilerPC() + 8);
|
|
CompileDelaySlot();
|
|
FlushAll();
|
|
}
|
|
|
|
switch (op & 0x3f)
|
|
{
|
|
case 8: //jr
|
|
break;
|
|
case 9: //jalr
|
|
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, 0, 0, dcAmount);
|
|
js.downcountAmount = 0;
|
|
|
|
ir.Write(IROp::ExitToReg, 0, destReg, 0);
|
|
|
|
// Account for the delay slot.
|
|
js.compilerPC += 4;
|
|
js.compiling = false;
|
|
}
|
|
|
|
void IRFrontend::Comp_Syscall(MIPSOpcode op) {
|
|
// Like the interpreter, the syscall sees every earlier instruction charged (including a branch it's
|
|
// the delay slot of) but not itself, which is charged once it returns.
|
|
const int syscallCycles = MIPSGetInstructionCycleEstimate(op);
|
|
ir.Write(IROp::Downcount, 0, 0, 0, js.downcountAmount - syscallCycles);
|
|
js.downcountAmount = 0;
|
|
|
|
// If not in a delay slot, we need to update PC.
|
|
// However we also need the PC for some diagnostics so let's just always do it.
|
|
// Not exactly a bottleneck.
|
|
if (!js.inDelaySlot) {
|
|
ir.Write(IROp::SetPCConst, 0, 0, 0, GetCompilerPC() + 4);
|
|
}
|
|
|
|
FlushAll();
|
|
|
|
RestoreRoundingMode();
|
|
const HLEFunction *func = GetSyscallFunctionData(op, js.compilerPC);
|
|
if (func) {
|
|
ir.Write(IROp::Syscall, 0, 0, 0, op.encoding);
|
|
} else {
|
|
ir.Write(IROp::SyscallUnresolved, 0, 0, 0, js.compilerPC);
|
|
}
|
|
ApplyRoundingMode();
|
|
ir.Write(IROp::Downcount, 0, 0, 0, syscallCycles);
|
|
ir.Write(IROp::ExitToPC);
|
|
|
|
js.compiling = false;
|
|
}
|
|
|
|
void IRFrontend::Comp_Break(MIPSOpcode op) {
|
|
ir.Write(IROp::SetPCConst, 0, 0, 0, GetCompilerPC());
|
|
ir.Write(IROp::Break);
|
|
js.compiling = false;
|
|
}
|
|
|
|
} // namespace Mipscomp
|