IR: Leave branches with a branch or jump in the delay slot to the interpreter

The IR frontend dropped a branch or jump in a delay slot (and reported it),
or approximated it with a link to +12 and an exit into the delay slot. Such
a pair now ends the block with the new BranchPair pseudo-op, which the
interpreter runs until it's out of the delay slot, so every core does what
tests/cpu/delayslot/branch recorded. It's an ordinary Interpret, so the
native backends need nothing new, and other branches compile as before.

This removes the delay-slot special cases from the branch compilers and
ResolveNotTakenTarget(), with the eight reports about them. The CPU fuzzer
now runs its d class (branches in delay slots) on every core, and they
agree, cycles included. cpu/delayslot/branch moves to tests_good.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-10 11:47:35 +01:00
1 parent 86958037ae
commit e933af2080
12 files changed
+118 -116

No files matched your search

+69 -71
View File
@@ -54,11 +54,48 @@ 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;
// A branch or jump with another in its delay slot is left to the interpreter, which does what the
// hardware does (tests/cpu/delayslot/branch). It's rare, and mostly data run as code. Ends the block.
bool IRFrontend::CompileBranchPair(MIPSOpcode op) {
const MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
const MIPSInfo delaySlotInfo = MIPSGetInfo(delaySlotOp);
if ((delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) == 0) {
return false;
}
// A jump skipped by a likely branch runs the next instruction as its delay slot, which may be
// anything. Handled as Comp_Generic does.
MIPSInfo afterInfo;
u32 afterOp = 0;
if ((MIPSGetInfo(op) & LIKELY) && (delaySlotInfo & IS_JUMP)) {
afterOp = GetOffsetInstruction(2).encoding;
afterInfo = MIPSGetInfo(MIPSOpcode(afterOp));
}
const bool isSyscall = (afterInfo & IS_SYSCALL) != 0;
const bool setsMode = MIPS_IS_CTC1_FCR31(afterOp);
// The interpreter charges the branch as it runs it.
ir.Write(IROp::Downcount, 0, 0, 0, js.downcountAmount - MIPSGetInstructionCycleEstimate(op));
js.downcountAmount = 0;
FlushAll();
if (isSyscall || setsMode)
RestoreRoundingMode(setsMode);
ir.Write(IROp::SetPCConst, 0, 0, 0, GetCompilerPC());
ir.Write(IROp::Interpret, 0, 0, 0, MIPS_BRANCH_PAIR_OP);
if (setsMode)
UpdateRoundingMode();
if (isSyscall || setsMode)
ApplyRoundingMode(setsMode);
if (afterInfo & OUT_VFPU_PREFIX)
js.PrefixUnknown();
ir.Write(IROp::ExitToPC);
js.compiling = false;
return true;
}
void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
_dbg_assert_(!js.inDelaySlot);
if (CompileBranchPair(op))
return;
int offset = TARGET16;
MIPSGPReg rt = _RT;
MIPSGPReg rs = _RS;
@@ -92,7 +129,7 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
}
}
if (!likely && !branchInfo.delaySlotIsBranch)
if (!likely)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -105,18 +142,10 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
js.compilerPC += 4;
return;
}
ir.Write(ComparisonToExit(cc), 0, lhs, rhs, ResolveNotTakenTarget(branchInfo));
ir.Write(ComparisonToExit(cc), 0, lhs, rhs, GetCompilerPC() + 8);
// This makes the block "impure" :(
if (likely && !branchInfo.delaySlotIsBranch)
if (likely)
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);
@@ -127,10 +156,9 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) {
}
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);
_dbg_assert_(!js.inDelaySlot);
if (CompileBranchPair(op))
return;
}
int offset = TARGET16;
MIPSGPReg rs = _RS;
u32 targetAddr = GetCompilerPC() + offset + 4;
@@ -149,7 +177,7 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
if (andLink)
ir.WriteSetConstant(MIPS_REG_RA, GetCompilerPC() + 8);
if (!likely && !branchInfo.delaySlotIsBranch)
if (!likely)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -162,17 +190,9 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
js.compilerPC += 4;
return;
}
ir.Write(ComparisonToExit(cc), 0, lhs, 0, ResolveNotTakenTarget(branchInfo));
if (likely && !branchInfo.delaySlotIsBranch)
ir.Write(ComparisonToExit(cc), 0, lhs, 0, GetCompilerPC() + 8);
if (likely)
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();
@@ -190,7 +210,7 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
// 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())
if (!opts.continueBranches || branchInfo.likely || constantOperands || js.MayHavePrefix())
return false;
if (js.numInstructions >= IR_MAX_CONTINUED_INSTRUCTIONS || js.continuedBranches >= IR_MAX_CONTINUED_BRANCHES)
return false;
@@ -237,18 +257,17 @@ void IRFrontend::Comp_RelBranchRI(MIPSOpcode op) {
// 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);
_dbg_assert_(!js.inDelaySlot);
if (CompileBranchPair(op))
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
js.downcountAmount += MIPSGetInstructionCycleEstimate(delaySlotOp);
ir.Write(IROp::FpCondToReg, IRTEMP_LHS);
if (!likely && !branchInfo.delaySlotIsBranch)
if (!likely)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -257,18 +276,10 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) {
FlushAll();
// Not taken
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, ResolveNotTakenTarget(branchInfo));
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, GetCompilerPC() + 8);
// Taken
if (likely && !branchInfo.delaySlotIsBranch)
if (likely)
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);
@@ -292,22 +303,19 @@ void IRFrontend::Comp_FPUBranch(MIPSOpcode op) {
// 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);
_dbg_assert_(!js.inDelaySlot);
// Disgaea 2: Dark Hero Days, Zettai Hero Project and La Pucelle have VFPU branches in delay slots.
if (CompileBranchPair(op))
return;
}
int offset = TARGET16;
u32 targetAddr = GetCompilerPC() + offset + 4;
BranchInfo branchInfo(GetCompilerPC(), op, GetOffsetInstruction(1), false, likely);
MIPSOpcode delaySlotOp = GetOffsetInstruction(1);
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.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.
if (!likely && !branchInfo.delaySlotIsBranch)
if (!likely)
CompileDelaySlot();
int dcAmount = js.downcountAmount;
@@ -318,18 +326,10 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) {
ir.Write(IROp::AndConst, IRTEMP_LHS, IRTEMP_LHS, 0, 1 << imm3);
FlushAll();
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, ResolveNotTakenTarget(branchInfo));
ir.Write(ComparisonToExit(cc), 0, IRTEMP_LHS, 0, GetCompilerPC() + 8);
if (likely && !branchInfo.delaySlotIsBranch)
if (likely)
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();
@@ -350,10 +350,9 @@ void IRFrontend::Comp_VBranch(MIPSOpcode op) {
}
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);
_dbg_assert_(!js.inDelaySlot);
if (CompileBranchPair(op))
return;
}
u32 off = TARGET26;
u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off;
@@ -397,10 +396,9 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) {
}
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);
_dbg_assert_(!js.inDelaySlot);
if (CompileBranchPair(op))
return;
}
MIPSGPReg rs = _RS;
MIPSGPReg rd = _RD;
bool andLink = (op & 0x3f) == 9 && rd != MIPS_REG_ZERO;
+1
View File
@@ -140,6 +140,7 @@ private:
void BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, bool likely);
void BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely);
bool CanContinuePastBranch(const BranchInfo &branchInfo, bool constantOperands);
bool CompileBranchPair(MIPSOpcode op);
// Utilities to reduce duplicated code
void CompShiftImm(MIPSOpcode op, IROp shiftType, int sa);
+25
View File
@@ -1380,4 +1380,29 @@ namespace MIPSInt {
MIPSInterpret(mips, Memory::Read_Instruction(PC));
}
}
// Runs the branch at PC until it's out of its delay slot, charging as the run loop does. The IR
// frontend leaves a branch with a branch or jump in its delay slot to this.
void Int_BranchPair(MIPSState *mips, MIPSOpcode op) {
// Not where the IR frontend puts it: the game is executing data.
if (mips->inDelaySlot || !(MIPSGetInfo(ReadOpAt(mips, PC)) & DELAYSLOT)) {
Core_ExecException(PC, PC, ExecExceptionType::ILLEGAL);
return;
}
do {
if (!mips->space->IsValid4AlignedAddress(PC)) {
Core_ExecException(PC, PC, ExecExceptionType::JUMP);
return;
}
const MIPSOpcode cur = ReadExecutedOp(*mips->space, PC);
const bool wasInDelaySlot = mips->inDelaySlot;
MIPSInterpret(mips, cur);
mips->downcount -= MIPSGetInstructionCycleEstimate(cur);
// A syscall in the delay slot leaves it itself.
if (mips->inDelaySlot && wasInDelaySlot) {
PC = mips->nextPC;
mips->inDelaySlot = false;
}
} while (mips->inDelaySlot);
}
}
+1
View File
@@ -47,6 +47,7 @@ namespace MIPSInt
void Int_FPUComp(MIPSState *mips, MIPSOpcode op);
void Int_FPUBranch(MIPSState *mips, MIPSOpcode op);
void Int_CallReplacement(MIPSState *mips, MIPSOpcode op);
void Int_BranchPair(MIPSState *mips, MIPSOpcode op);
void Int_Special2(MIPSState *mips, MIPSOpcode op);
void Int_Halt(MIPSState *mips, MIPSOpcode op);
void Int_Me2C(MIPSState *mips, MIPSOpcode op);
+4
View File
@@ -352,6 +352,10 @@ int ExecInstruction(MIPSState *mips, MIPSOpcode op) {
case 26:
{
switch ((op.encoding >> 24) & 0x3) {
// BranchPair
case 1:
MIPSInt::Int_BranchPair(mips, op);
return 1;
// CallRepl
case 2:
MIPSInt::Int_CallReplacement(mips, op);
-22
View File
@@ -75,30 +75,8 @@ namespace MIPSComp {
BranchInfo::BranchInfo(u32 pc, MIPSOpcode o, MIPSOpcode delayO, bool al, bool l)
: compilerPC(pc), op(o), delaySlotOp(delayO), likely(l), andLink(al) {
delaySlotInfo = MIPSGetInfo(delaySlotOp).value;
delaySlotIsBranch = (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0;
}
u32 ResolveNotTakenTarget(const BranchInfo &branchInfo) {
u32 notTakenTarget = branchInfo.compilerPC + 8;
if ((branchInfo.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 = (branchInfo.delaySlotInfo & IS_JUMP) != 0;
// If the delay slot is a branch, likely skips it.
if (isJump || !branchInfo.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(branchInfo.delaySlotOp) != SignExtend16ToU32(branchInfo.op) - 1)
ERROR_LOG_REPORT(Log::JIT, "Branch in branch delay slot at %08x with different target", branchInfo.compilerPC);
if (isJump && branchInfo.likely && (branchInfo.delaySlotInfo & (OUT_RA | OUT_RD)) != 0)
ERROR_LOG_REPORT(Log::JIT, "Jump in likely branch delay slot with link at %08x", branchInfo.compilerPC);
}
return notTakenTarget;
}
bool NativeJitRunsMediaEngine() {
#if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(AMD64)
return true;
-5
View File
@@ -176,17 +176,12 @@ namespace MIPSComp {
u32 compilerPC;
MIPSOpcode op;
MIPSOpcode delaySlotOp;
u64 delaySlotInfo;
bool likely;
bool andLink;
// Update manually if it's not always nice (rs/rt, rs/zero, etc.)
bool delaySlotIsNice = true;
bool delaySlotIsBranch;
};
// This seems to be the same for all branch types.
u32 ResolveNotTakenTarget(const BranchInfo &branchInfo);
void DoDummyJitState(PointerWrap &p, const MIPSState *mips);
JitInterface *CreateNativeJit(MIPSState *mipsState);
+3
View File
@@ -42,6 +42,9 @@ typedef Memory::Opcode MIPSOpcode;
// 26, which the Allegrex doesn't have, decodes it as CallRepl.
#define MIPS_CALL_REPLACEMENT_OP 0x6A000000
#define MIPS_REPLACEMENT_INDEX_MASK 0x00FFFFFF
// Another (BranchPair): the IR frontend's way to run a branch with a branch or jump in its delay
// slot, which it leaves to the interpreter. Also never in memory.
#define MIPS_BRANCH_PAIR_OP 0x69000000
enum MIPSGPReg {
MIPS_REG_ZERO=0,
+8 -4
View File
@@ -820,7 +820,7 @@ static const MIPSInstruction tableALLEGREX0[32] = // 011111 ..... ..... ..... x
static const MIPSInstruction tableEMU[4] = {
INVALID,
INVALID,
INSTR("BranchPair", JITFUNC(Comp_Generic), Dis_Generic, Int_BranchPair, 0),
INSTR("CallRepl", JITFUNC(Comp_ReplacementFunc), Dis_CallReplacement, Int_CallReplacement, 0),
INVALID,
};
@@ -1239,7 +1239,7 @@ static inline bool StepIRBlock(IRBlockState &b, const MIPSState *mips, MIPSOpcod
return false;
}
if (mips->inDelaySlot) {
// Taken, or a jump.
// Taken, or a jump (or one a likely branch skipped).
b.endAfterNext = true;
return false;
}
@@ -1247,9 +1247,13 @@ static inline bool StepIRBlock(IRBlockState &b, const MIPSState *mips, MIPSOpcod
// A likely branch that wasn't taken skipped its delay slot.
return true;
}
// Not taken: the delay slot runs next, as an ordinary instruction.
// Not taken: the delay slot runs next, as an ordinary instruction. If it's a branch or jump, the
// IR left the two to the interpreter (CompileBranchPair), and the block ends here.
const MIPSInfo delaySlotInfo = MIPSGetInfo(ReadDelaySlot(mips, pc));
if (IR_CONTINUE_BRANCHES && IsContinuableBranch(op) && (delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) == 0) {
if ((delaySlotInfo & (IS_JUMP | IS_CONDBRANCH)) != 0) {
return true;
}
if (IR_CONTINUE_BRANCHES && IsContinuableBranch(op)) {
b.continueAfterNext = true;
} else {
b.endAfterNext = true;
+4 -2
View File
@@ -25,10 +25,9 @@ Environment variables:
|---|---|
| `PPSSPP_CPUFUZZ_COUNT` | number of cases (default 2000) |
| `PPSSPP_CPUFUZZ_SEED` | seed of the first case; case i uses seed + i |
| `PPSSPP_CPUFUZZ_CLASSES` | weighted string of `a` (ALU), `i` (immediate), `f` (FPU), `v` (VFPU), `m` (memory), `r` (a run of vdots the IR batches into one `Vec4VfpuMultiDot`); `b` anywhere adds forward conditional branches; `d` adds those, `j`/`jal`, the likely links and the FPU and VFPU branches, sometimes in each other's delay slots (only the interpreter runs these cases until the IR frontend handles them, see `HandlesBranchesInDelaySlots()`) |
| `PPSSPP_CPUFUZZ_CLASSES` | weighted string of `a` (ALU), `i` (immediate), `f` (FPU), `v` (VFPU), `m` (memory), `r` (a run of vdots the IR batches into one `Vec4VfpuMultiDot`); `b` anywhere adds forward conditional branches; `d` adds those, `j`/`jal`, the likely links and the FPU and VFPU branches, sometimes in each other's delay slots |
| `PPSSPP_CPUFUZZ_CODE` | comma-separated hex instructions to run instead of the generated code |
| `PPSSPP_CPUFUZZ_CODE_SEED` | apply `PPSSPP_CPUFUZZ_CODE` to this seed only |
| `PPSSPP_CPUFUZZ_ALLCORES` | run every core on the `d` cases anyway |
| `PPSSPP_CPUFUZZ_SKIP` | cores to skip, by digit: 1 IR interpreter, 2 JIT |
| `PPSSPP_CPUFUZZ_REPORTS` | how many differences to minimize and print (default 20) |
| `PPSSPP_CPUFUZZ_VERBOSE` | print every case before running it, to find one that crashes |
@@ -127,6 +126,9 @@ IR-based (the "IR JIT" and "native IR backend" entries).
- The IR frontend continues past conditional branches, so a block can now leave with a prefix
pending at a mid-block exit. It doesn't continue then, as only the end of a block checks that.
- Hardware answers 1, 2 (host agreement), 5, 7 (the interpreter and `vcmov`), 8, 9 and 10 below.
- Branches and jumps in delay slots, which neither core ran as the hardware does
(`tests/cpu/delayslot/branch`). The IR frontend now leaves such a pair to the interpreter
(`CompileBranchPair()`, the `BranchPair` pseudo-op), so the cores share one implementation.
Timing, which decides when interrupts, thread switches and other events happen, so a difference
sends a game down a different path on each core:
+1 -1
View File
@@ -96,6 +96,7 @@ tests_good = [
"cpu/cpu_alu/shift_encodings",
"cpu/cpu_alu/allegrex_encodings",
"cpu/jump/unaligned",
"cpu/delayslot/branch",
"cpu/vfpu/callout",
"cpu/vfpu/colors",
"cpu/vfpu/convert",
@@ -697,7 +698,6 @@ tests_next = [
"cpu/fpu/fpu_branch_hazard", # a bc1x right after c.xx.s sees the old condition; the compiler pads for it, not emulated
"cpu/fpu/fpu_nan", # 0/0 and inf-inf give 0x7fc00000; x86 hosts make 0xffc00000, and a check per op isn't worth it
"cpu/lsu/cacheop", # the data cache is write-back and the uncached mirror shows it; not emulated
"cpu/delayslot/branch", # branches and jumps in a delay slot: the interpreter passes, the IR frontend doesn't handle them yet
"cpu/vfpu/prefixes",
"cpu/vfpu/vector",
"cpu/vfpu/vregs",
+2 -11
View File
@@ -1096,9 +1096,7 @@ bool TestMeDmacplus() {
// runs on the instructions being worked on. "dot" is the dot products
// and the prefixes. Without PPSSPP_CPUFUZZ_CLASSES, the class is "v"
// ("vr" for "dot": r is a run of vdots the IR batches). b adds forward
// conditional branches, d those and jumps, also in delay slots, which
// only the cores in HandlesBranchesInDelaySlots() run.
// PPSSPP_CPUFUZZ_ALLCORES run every core on the d cases anyway.
// conditional branches, d those and jumps, also in delay slots.
namespace {
@@ -1349,12 +1347,6 @@ static void AddFuzzBranches(FuzzRng &rng, std::vector<u32> &code, bool delaySlot
}
}
// The cores that run branches and jumps in delay slots as the hardware does (tests/cpu/delayslot/
// branch). The others sit out the cases with them (PPSSPP_CPUFUZZ_CLASSES with d).
static bool HandlesBranchesInDelaySlots(CPUCore core) {
return core == CPUCore::INTERPRETER;
}
// A run of 2-4 vdots of one size against one vector, the others rows or columns of one matrix: the
// shape the IR frontend turns into a Vec4VfpuMultiDot. The results go to lanes of one vector or
// anywhere, sometimes over the operands.
@@ -1846,11 +1838,10 @@ bool TestCPUEquivalence() {
};
const int numCores = (int)ARRAY_SIZE(cores);
const bool delaySlots = strchr(classes, 'd') != nullptr && !getenv("PPSSPP_CPUFUZZ_ALLCORES");
bool skipped[ARRAY_SIZE(cores)]{};
std::vector<FuzzState> results((size_t)count * numCores);
for (int c = 0; c < numCores; ++c) {
skipped[c] = c > 0 && ((skipCores && strchr(skipCores, '0' + c)) || (delaySlots && !HandlesBranchesInDelaySlots(cores[c])));
skipped[c] = c > 0 && skipCores && strchr(skipCores, '0' + c);
if (skipped[c]) {
for (int i = 0; i < count; ++i) {
results[(size_t)i * numCores + c] = results[(size_t)i * numCores];