Files
ppsspp/Core/MIPS/ARM64/Arm64IRCompBranch.cpp
T
Henrik RydgårdandClaude Opus 5.5 e33f38afa6 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>
2026-10-15 11:02:17 +02:00

183 lines
4.7 KiB
C++

// Copyright (c) 2023- 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 "ppsspp_config.h"
// In other words, PPSSPP_ARCH(ARM64) || DISASM_ALL.
#if PPSSPP_ARCH(ARM64) || (PPSSPP_PLATFORM(WINDOWS) && !defined(__LIBRETRO__))
#include "Core/MIPS/ARM64/Arm64IRJit.h"
#include "Core/MIPS/ARM64/Arm64IRRegCache.h"
// This file contains compilation for exits.
//
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
#define CONDITIONAL_DISABLE {}
#define DISABLE { CompIR_Generic(inst); return; }
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
namespace MIPSComp {
using namespace Arm64Gen;
using namespace Arm64IRJitConstants;
void Arm64JitBackend::CompIR_Exit(IRInst inst) {
CONDITIONAL_DISABLE;
ARM64Reg exitReg = INVALID_REG;
switch (inst.op) {
case IROp::ExitToConst:
FlushAll();
WriteConstExit(inst.constant);
break;
case IROp::ExitToReg:
exitReg = regs_.MapGPR(inst.src1);
FlushAll();
MOV(SCRATCH1, exitReg);
// 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:
FlushAll();
B(dispatcherCheckCoreState_);
break;
default:
INVALIDOP;
break;
}
}
void Arm64JitBackend::CompIR_ExitIf(IRInst inst) {
CONDITIONAL_DISABLE;
ARM64Reg lhs = INVALID_REG;
ARM64Reg rhs = INVALID_REG;
FixupBranch fixup;
switch (inst.op) {
case IROp::ExitToConstIfEq:
case IROp::ExitToConstIfNeq:
if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) {
lhs = regs_.MapGPR(inst.src2);
FlushAll();
if (inst.op == IROp::ExitToConstIfEq)
fixup = CBNZ(lhs);
else if (inst.op == IROp::ExitToConstIfNeq)
fixup = CBZ(lhs);
else
_assert_(false);
} else if (regs_.IsGPRImm(inst.src2) && regs_.GetGPRImm(inst.src2) == 0) {
lhs = regs_.MapGPR(inst.src1);
FlushAll();
if (inst.op == IROp::ExitToConstIfEq)
fixup = CBNZ(lhs);
else if (inst.op == IROp::ExitToConstIfNeq)
fixup = CBZ(lhs);
else
_assert_(false);
} else {
regs_.Map(inst);
lhs = regs_.R(inst.src1);
rhs = regs_.R(inst.src2);
FlushAll();
CMP(lhs, rhs);
if (inst.op == IROp::ExitToConstIfEq)
fixup = B(CC_NEQ);
else if (inst.op == IROp::ExitToConstIfNeq)
fixup = B(CC_EQ);
else
_assert_(false);
}
WriteConstExit(inst.constant);
SetJumpTarget(fixup);
break;
case IROp::ExitToConstIfGtZ:
lhs = regs_.MapGPR(inst.src1);
FlushAll();
CMP(lhs, 0);
fixup = B(CC_LE);
WriteConstExit(inst.constant);
SetJumpTarget(fixup);
break;
case IROp::ExitToConstIfGeZ:
// In other words, exit if sign bit is 0.
lhs = regs_.MapGPR(inst.src1);
FlushAll();
fixup = TBNZ(lhs, 31);
WriteConstExit(inst.constant);
SetJumpTarget(fixup);
break;
case IROp::ExitToConstIfLtZ:
// In other words, exit if sign bit is 1.
lhs = regs_.MapGPR(inst.src1);
FlushAll();
fixup = TBZ(lhs, 31);
WriteConstExit(inst.constant);
SetJumpTarget(fixup);
break;
case IROp::ExitToConstIfLeZ:
lhs = regs_.MapGPR(inst.src1);
FlushAll();
CMP(lhs, 0);
fixup = B(CC_GT);
WriteConstExit(inst.constant);
SetJumpTarget(fixup);
break;
case IROp::ExitToConstIfFpTrue:
case IROp::ExitToConstIfFpFalse:
// Note: not used.
DISABLE;
break;
default:
INVALIDOP;
break;
}
}
} // namespace MIPSComp
#endif