irjit: Handle branch/jump in branch delay slots.

See #15952 for more detail.
This commit is contained in:
Unknown W. Brackets
2022-09-03 19:05:31 -07:00
parent 0fc3619d1d
commit d08ee44cf4
+83 -16
View File
@@ -54,6 +54,27 @@ using namespace MIPSAnalyst;
namespace MIPSComp
{
// This seems to be the same for all branch types.
static u32 ResolveNotTakenTarget(u32 pc, const MIPSOpcode &op, bool likely, const MIPSOpcode &delaySlotOp, const MIPSInfo &delaySlotInfo) {
u32 notTakenTarget = pc + 8;
if ((delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0) {
// If a branch has a j/jr/jal/jalr as a delay slot, that is run if the branch is not taken.
// TODO: Technically, in the likely case, we should somehow suppress andLink on this exit.
bool isJump = (delaySlotInfo & IS_JUMP) != 0;
// If the delay slot is a branch, likely skips it.
if (isJump || !likely)
notTakenTarget -= 4;
// For a branch (not a jump), it actually should try the delay slot and take its target potentially.
// This is similar to the VFPU case and has not been seen, so just report it.
if (!isJump && SignExtend16ToU32(delaySlotOp) != SignExtend16ToU32(op) - 1)
ERROR_LOG_REPORT(JIT, "Branch in branch delay slot at %08x with different target", pc);
if (isJump && likely && (delaySlotInfo & (OUT_RA | OUT_RD)) != 0)
ERROR_LOG_REPORT(JIT, "Jump in likely branch delay slot with link at %08x", pc);
}
return notTakenTarget;
}
void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
if (js.inDelaySlot) {
ERROR_LOG_REPORT(JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart);
@@ -65,7 +86,9 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
u32 targetAddr = GetCompilerPC() + offset + 4;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
MIPSInfo delaySlotInfo = MIPSGetInfo(delaySlotOp);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
bool delaySlotIsBranch = (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0;
bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rt, rs);
// Often, div/divu are followed by a likely "break" if the divisor was zero.
@@ -91,7 +114,9 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
}
}
if (!likely)
u32 notTakenTarget = ResolveNotTakenTarget(GetCompilerPC(), op, likely, delaySlotOp, delaySlotInfo);
if (!likely && !delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -99,10 +124,18 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
js.downcountAmount = 0;
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(GetCompilerPC() + 8), lhs, rhs);
ir.Write(ComparisonToExit(cc), ir.AddConstant(notTakenTarget), lhs, rhs);
// This makes the block "impure" :(
if (likely)
if (likely && !delaySlotIsBranch)
CompileDelaySlot();
if (delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
@@ -122,7 +155,9 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
u32 targetAddr = GetCompilerPC() + offset + 4;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
MIPSInfo delaySlotInfo = MIPSGetInfo(delaySlotOp);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
bool delaySlotIsBranch = (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0;
bool delaySlotIsNice = IsDelaySlotNiceReg(op, delaySlotOp, rs);
MIPSGPReg lhs = rs;
@@ -133,7 +168,9 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
if (andLink)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 8);
if (!likely)
u32 notTakenTarget = ResolveNotTakenTarget(GetCompilerPC(), op, likely, delaySlotOp, delaySlotInfo);
if (!likely && !delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -141,9 +178,18 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
js.downcountAmount = 0;
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(GetCompilerPC() + 8), lhs);
if (likely)
ir.Write(ComparisonToExit(cc), ir.AddConstant(notTakenTarget), lhs);
if (likely && !delaySlotIsBranch)
CompileDelaySlot();
if (delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(delaySlotOp), GetCompilerPC() + 12);
}
// Taken
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
@@ -199,20 +245,35 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) {
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
MIPSInfo delaySlotInfo = MIPSGetInfo(delaySlotOp);
bool delaySlotIsBranch = (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0;
ir.Write(IROp::FpCondToReg, IRTEMP_LHS);
if (!likely)
if (!likely && !delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
u32 notTakenTarget = ResolveNotTakenTarget(GetCompilerPC(), op, likely, delaySlotOp, delaySlotInfo);
FlushAll();
// Not taken
ir.Write(ComparisonToExit(cc), ir.AddConstant(GetCompilerPC() + 8), IRTEMP_LHS, 0);
ir.Write(ComparisonToExit(cc), ir.AddConstant(notTakenTarget), IRTEMP_LHS, 0);
// Taken
if (likely)
if (likely && !delaySlotIsBranch)
CompileDelaySlot();
if (delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(delaySlotOp), GetCompilerPC() + 12);
}
FlushAll();
ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr));
@@ -243,33 +304,39 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) {
u32 targetAddr = GetCompilerPC() + offset + 4;
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
MIPSInfo delaySlotInfo = MIPSGetInfo(delaySlotOp);
js.downcountAmount += MIPSGetInstructionCycleEstimate(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.
bool delaySlotIsBranch = MIPSCodeUtils::IsVFPUBranch(delaySlotOp);
if (!likely)
bool delaySlotIsBranch = (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0;
if (!likely && !delaySlotIsBranch)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount));
js.downcountAmount = 0;
if (delaySlotIsBranch && (signed short)(delaySlotOp & 0xFFFF) != (signed short)(op & 0xFFFF) - 1)
ERROR_LOG_REPORT(JIT, "VFPU branch in VFPU delay slot at %08x with different target", GetCompilerPC());
u32 notTakenTarget = ResolveNotTakenTarget(GetCompilerPC(), op, likely, delaySlotOp, delaySlotInfo);
int imm3 = (op >> 18) & 7;
u32 notTakenTarget = GetCompilerPC() + (delaySlotIsBranch ? 4 : 8);
ir.Write(IROp::AndConst, IRTEMP_LHS, IRTEMP_LHS, ir.AddConstant(1 << imm3));
FlushAll();
ir.Write(ComparisonToExit(cc), ir.AddConstant(notTakenTarget), IRTEMP_LHS, 0);
if (likely)
if (likely && !delaySlotIsBranch)
CompileDelaySlot();
if (delaySlotIsBranch) {
// We still link when the branch is taken (targetAddr case.)
// Remember, it's from the perspective of the delay slot, so +12.
if ((delaySlotInfo & OUT_RA) != 0)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 12);
if ((delaySlotInfo & OUT_RD) != 0)
ir.WriteSetConstant(MIPS_GET_RD(delaySlotOp), GetCompilerPC() + 12);
}
// Taken
FlushAll();