Handle branches in VFPU delay slots better.

Based on tests on a PSP, all branches are attempted.  The behavior is
technically undefined.

It seems to take the delay slot's target if they differ and both pass.
This is the behavior the interpreter has, but it's more work in jit.

Since only a couple games seem to do this, and clearly expect this
behavior, this fixes all known cases of #1926.
This commit is contained in:
Unknown W. Brackets 2013-08-14 22:34:32 -07:00
parent 9aafdeafcf
commit e639f8d15f
4 changed files with 28 additions and 11 deletions

View File

@ -273,8 +273,12 @@ void Jit::BranchVFPUFlag(u32 op, ArmGen::CCFlags cc, bool likely)
u32 targetAddr = js.compilerPC + offset + 4;
u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4);
bool delaySlotIsNice = IsDelaySlotNiceVFPU(op, delaySlotOp);
// 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);
bool delaySlotIsNice = !delaySlotIsBranch && IsDelaySlotNiceVFPU(op, delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
if (!likely && delaySlotIsNice)
CompileDelaySlot(DELAYSLOT_NICE);
@ -291,14 +295,15 @@ void Jit::BranchVFPUFlag(u32 op, ArmGen::CCFlags cc, bool likely)
js.inDelaySlot = true;
if (!likely)
{
if (!delaySlotIsNice)
if (!delaySlotIsNice && !delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
ptr = B_CC(cc);
}
else
{
ptr = B_CC(cc);
CompileDelaySlot(DELAYSLOT_FLUSH);
if (!delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_FLUSH);
}
js.inDelaySlot = false;
@ -307,7 +312,8 @@ void Jit::BranchVFPUFlag(u32 op, ArmGen::CCFlags cc, bool likely)
SetJumpTarget(ptr);
// Not taken
WriteExit(js.compilerPC + 8, 1);
u32 notTakenTarget = js.compilerPC + (delaySlotIsBranch ? 4 : 8);
WriteExit(notTakenTarget, 1);
js.compiling = false;
}

View File

@ -132,4 +132,8 @@ namespace MIPSCodeUtils
else
return INVALIDTARGET;
}
bool IsVFPUBranch(u32 op) {
return (MIPSGetInfo(op) & (IS_VFPU | IS_CONDBRANCH)) == (IS_VFPU | IS_CONDBRANCH);
}
}

View File

@ -57,5 +57,5 @@ namespace MIPSCodeUtils
u32 GetBranchTargetNoRA(u32 addr);
u32 GetJumpTarget(u32 addr);
u32 GetSureBranchTarget(u32 addr);
void RewriteSysCalls(u32 startAddr, u32 endAddr);
bool IsVFPUBranch(u32 op);
}

View File

@ -348,7 +348,12 @@ void Jit::BranchVFPUFlag(u32 op, Gen::CCFlags cc, bool likely)
u32 targetAddr = js.compilerPC + offset + 4;
u32 delaySlotOp = Memory::Read_Instruction(js.compilerPC + 4);
bool delaySlotIsNice = IsDelaySlotNiceVFPU(op, delaySlotOp);
// 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);
bool delaySlotIsNice = !delaySlotIsBranch && IsDelaySlotNiceVFPU(op, delaySlotOp);
CONDITIONAL_NICE_DELAYSLOT;
if (!likely && delaySlotIsNice)
CompileDelaySlot(DELAYSLOT_NICE);
@ -363,14 +368,15 @@ void Jit::BranchVFPUFlag(u32 op, Gen::CCFlags cc, bool likely)
Gen::FixupBranch ptr;
if (!likely)
{
if (!delaySlotIsNice)
if (!delaySlotIsNice && !delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_SAFE_FLUSH);
ptr = J_CC(cc, true);
}
else
{
ptr = J_CC(cc, true);
CompileDelaySlot(DELAYSLOT_FLUSH);
if (!delaySlotIsBranch)
CompileDelaySlot(DELAYSLOT_FLUSH);
}
// Take the branch
@ -379,8 +385,9 @@ void Jit::BranchVFPUFlag(u32 op, Gen::CCFlags cc, bool likely)
SetJumpTarget(ptr);
// Not taken
CONDITIONAL_LOG_EXIT(js.compilerPC + 8);
WriteExit(js.compilerPC + 8, 1);
u32 notTakenTarget = js.compilerPC + (delaySlotIsBranch ? 4 : 8);
CONDITIONAL_LOG_EXIT(notTakenTarget);
WriteExit(notTakenTarget, 1);
js.compiling = false;
}