ARM64 IR JIT: Look up the block for a jump to a register at the jump

Each jr and jalr now does the dispatcher's lookup itself, so each has its
own indirect branch, which the CPU predicts much better than the one in
the dispatcher that every one of them shared. Out of downcount or with no
block yet, it goes to the dispatcher as before.

Cycles over 10 emulated seconds: -2.3% in ATV, -1.4% Tekken 6 and Ridge
Racer, -1.2% Daxter, -1.1% Killzone, and -0.3% to -0.8% in the rest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-15 11:02:17 +02:00
1 parent f247df5e4d
commit e33f38afa6
2 files changed
+30 -3

No files matched your search

+17 -1
View File
@@ -51,7 +51,23 @@ void Arm64JitBackend::CompIR_Exit(IRInst inst) {
exitReg = regs_.MapGPR(inst.src1);
FlushAll();
MOV(SCRATCH1, exitReg);
B(dispatcherPCInSCRATCH1_);
// The dispatcher's lookup, here: each of these then has its own indirect branch, which the
// CPU predicts far better than one shared by every jr. Anything unusual goes the long way.
MovToPC(SCRATCH1);
{
// Out of the downcount, or no block yet: the dispatcher, with the PC stored.
FixupBranch inTime = TBZ(DOWNCOUNTREG, 31);
B(hooks_.dispatcher);
SetJumpTarget(inTime);
ANDI2R(SCRATCH1, SCRATCH1, Memory::BLOCK_SHADOW_ENTRY_MASK);
MOVP2R(SCRATCH2_64, Memory::blockShadow);
LDR(SCRATCH1, SCRATCH2_64, SCRATCH1_64);
FixupBranch found = CBNZ(SCRATCH1);
B(dispatcherNoCheck_);
SetJumpTarget(found);
ADD(SCRATCH1_64, JITBASEREG, SCRATCH1_64);
BR(SCRATCH1_64);
}
break;
case IROp::ExitToPC:
+13 -2
View File
@@ -75,8 +75,19 @@ bool Arm64JitBackend::CompileBlock(IRBlockCache *irBlockCache, int block_num) {
// An accurate dot product can flush several registers around its call.
size_t sizeEstimate = 0;
const IRInst *estimateInsts = irBlockCache->GetBlockInstructionPtr(*block);
for (int i = 0; i < block->GetNumIRInstructions(); ++i)
sizeEstimate += estimateInsts[i].op == IROp::Vec4VfpuDot || estimateInsts[i].op == IROp::Vec4VfpuMultiDot ? 96 : 32;
for (int i = 0; i < block->GetNumIRInstructions(); ++i) {
switch (estimateInsts[i].op) {
case IROp::Vec4VfpuDot:
case IROp::Vec4VfpuMultiDot:
// The jump to a register looks up the block itself.
case IROp::ExitToReg:
sizeEstimate += 96;
break;
default:
sizeEstimate += 32;
break;
}
}
BeginWrite(std::min(GetSpaceLeft(), sizeEstimate));
u32 startPC = block->GetOriginalStart();