Files
ppsspp/Core/MIPS/x86/X64IRAsm.cpp
T
Henrik RydgårdandClaude Opus 5.5 11296d32b1 CPU: Give each MIPSState an address space for its memory accesses
Memory::AddressSpace is where a CPU's addresses point (base) and its block
shadow, with the unchecked accessors and block shadow helpers as methods. The
main CPU's is Memory::g_mainSpace, kept in step with Memory::base and
blockShadow, and MIPSState::space points at it.

The CPU cores now go through their state's space instead of the globals: the
interpreter's and IR interpreter's loads and stores (which also saves the IR
interpreter reloading the global base), the IR interpreter's block lookup,
the block table's shadow entries, and the native JITs' memory base register,
block shadow lookups and pointerified registers. Address validity and
everything outside the cores still use the globals.

No change for the main CPU; this is so another CPU (the Media Engine) can see
memory differently. Benchmarked against master (God of War: Chains of
Olympus, Tekken 6): JIT instructions retired within noise, IR interpreter
2% fewer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 18:41:28 +02:00

336 lines
12 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"
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include "Common/Log.h"
#include "Core/CoreTiming.h"
#include "Core/MemMap.h"
#include "Core/MIPS/x86/X64IRJit.h"
#include "Core/MIPS/x86/X64IRRegCache.h"
#include "Core/MIPS/x86/X64VfpuDot.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/MIPS/JitCommon/JitState.h"
#include "Core/Core.h"
namespace MIPSComp {
using namespace Gen;
using namespace X64IRJitConstants;
static const bool enableDebug = false;
static const bool enableDisasm = false;
static void ShowPC(void *membase, void *jitbase) {
static int count = 0;
if (currentMIPS) {
u32 downcount = currentMIPS->downcount;
ERROR_LOG(Log::JIT, "[%08x] ShowPC Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase);
} else {
ERROR_LOG(Log::JIT, "Universe corrupt?");
}
//if (count > 2000)
// exit(0);
count++;
}
void X64JitBackend::GenerateFixedCode(MIPSState *mipsState) {
regs_.SetMemoryBase(mipsState->space->base);
// This will be used as a writable scratch area, always 32-bit accessible.
const u8 *start = AlignCodePage();
if (DebugProfilerEnabled()) {
ProtectMemoryPages(start, GetMemoryProtectPageSize(), MEM_PROT_READ | MEM_PROT_WRITE);
hooks_.profilerPC = (uint32_t *)GetWritableCodePtr();
Write32(0);
hooks_.profilerStatus = (IRProfilerStatus *)GetWritableCodePtr();
Write32(0);
}
EmitFPUConstants();
EmitVecConstants();
const u8 *disasmStart = AlignCodePage();
BeginWrite(GetMemoryProtectPageSize());
jo.downcountInRegister = false;
#if PPSSPP_ARCH(AMD64)
bool jitbaseInR15 = false;
int jitbaseCtxDisp = 0;
intptr_t jitbase = (intptr_t)GetBasePtr();
if ((jitbase < -0x80000000LL || jitbase > 0x7FFFFFFFLL) && !Accessible((const u8 *)&mipsState->f[0], (const u8 *)jitbase)) {
jo.reserveR15ForAsm = true;
jitbaseInR15 = true;
} else {
jo.downcountInRegister = true;
jo.reserveR15ForAsm = true;
if (jitbase < -0x80000000LL || jitbase > 0x7FFFFFFFLL) {
jitbaseCtxDisp = (int)(jitbase - (intptr_t)&mipsState->f[0]);
}
}
#endif
if (jo.useStaticAlloc && false) {
saveStaticRegisters_ = AlignCode16();
if (jo.downcountInRegister)
MOV(32, MDisp(CTXREG, downcountOffset), R(DOWNCOUNTREG));
//regs_.EmitSaveStaticRegisters();
RET();
// Note: needs to not modify EAX, or to save it if it does.
loadStaticRegisters_ = AlignCode16();
//regs_.EmitLoadStaticRegisters();
if (jo.downcountInRegister)
MOV(32, R(DOWNCOUNTREG), MDisp(CTXREG, downcountOffset));
RET();
} else {
saveStaticRegisters_ = nullptr;
loadStaticRegisters_ = nullptr;
}
restoreRoundingMode_ = AlignCode16();
{
STMXCSR(MDisp(CTXREG, tempOffset));
// Clear the rounding mode, flush-to-zero and denormals-are-zero bits back to 0.
AND(32, MDisp(CTXREG, tempOffset), Imm32(~((7 << 13) | (1 << 6))));
LDMXCSR(MDisp(CTXREG, tempOffset));
RET();
}
applyRoundingMode_ = AlignCode16();
{
MOV(32, R(SCRATCH1), MDisp(CTXREG, fcr31Offset));
AND(32, R(SCRATCH1), Imm32(0x01000003));
// If it's 0 (nearest + no flush0), we don't actually bother setting - we cleared the rounding
// mode out in restoreRoundingMode anyway. This is the most common.
FixupBranch skip = J_CC(CC_Z);
STMXCSR(MDisp(CTXREG, tempOffset));
// The MIPS bits don't correspond exactly, so we have to adjust.
// 0 -> 0 (skip2), 1 -> 3, 2 -> 2 (skip2), 3 -> 1
TEST(8, R(AL), Imm8(1));
FixupBranch skip2 = J_CC(CC_Z);
XOR(32, R(SCRATCH1), Imm8(2));
SetJumpTarget(skip2);
// Adjustment complete, now reconstruct MXCSR
SHL(32, R(SCRATCH1), Imm8(13));
// Before setting new bits, we must clear the old ones.
// Clearing bits 13-14 (rounding mode), 15 (flush to zero) and 6 (denormals are zero.)
AND(32, MDisp(CTXREG, tempOffset), Imm32(~((7 << 13) | (1 << 6))));
OR(32, MDisp(CTXREG, tempOffset), R(SCRATCH1));
TEST(32, MDisp(CTXREG, fcr31Offset), Imm32(1 << 24));
FixupBranch skip3 = J_CC(CC_Z);
// FTZ and DAZ together, which flushes like ARM64's FZ: inputs as well as results.
OR(32, MDisp(CTXREG, tempOffset), Imm32((1 << 15) | (1 << 6)));
SetJumpTarget(skip3);
LDMXCSR(MDisp(CTXREG, tempOffset));
SetJumpTarget(skip);
RET();
}
hooks_.enterDispatcher = (IRNativeFuncNoArg)AlignCode16();
ABI_PushAllCalleeSavedRegsAndAdjustStack();
#if PPSSPP_ARCH(AMD64)
// Two x64-specific statically allocated registers.
MOV(64, R(MEMBASEREG), ImmPtr(mipsState->space->base));
if (jitbaseInR15)
MOV(64, R(JITBASEREG), ImmPtr((const void *)jitbase));
#endif
// From the start of the FP reg, a single byte offset can reach all GPR + all FPR (but not VFPR.)
MOV(PTRBITS, R(CTXREG), ImmPtr(&mipsState->f[0]));
LoadStaticRegisters();
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
MovFromPC(SCRATCH1);
WriteDebugPC(SCRATCH1);
outerLoopPCInSCRATCH1_ = GetCodePtr();
MovToPC(SCRATCH1);
outerLoop_ = GetCodePtr();
// Advance can change the downcount (or thread), so must save/restore around it.
SaveStaticRegisters();
RestoreRoundingMode(true);
WriteDebugProfilerStatus(IRProfilerStatus::TIMER_ADVANCE);
LEA(PTRBITS, ECX, MDisp(CTXREG, -(s32)offsetof(MIPSState, f[0]))); // Adjust to get the real pointer.
ABI_CallFunctionR(reinterpret_cast<void *>(&CoreTiming::Advance), ECX);
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
ApplyRoundingMode(true);
LoadStaticRegisters();
dispatcherCheckCoreState_ = GetCodePtr();
// TODO: See if we can get the slice decrement to line up with IR.
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm8(0)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm8(0));
}
FixupBranch badCoreState = J_CC(CC_NZ, true);
if (jo.downcountInRegister) {
TEST(32, R(DOWNCOUNTREG), R(DOWNCOUNTREG));
} else {
CMP(32, MDisp(CTXREG, downcountOffset), Imm8(0));
}
J_CC(CC_S, outerLoop_);
FixupBranch skipToRealDispatch = J();
dispatcherPCInSCRATCH1_ = GetCodePtr();
MovToPC(SCRATCH1);
hooks_.dispatcher = GetCodePtr();
// TODO: See if we can get the slice decrement to line up with IR.
if (jo.downcountInRegister) {
TEST(32, R(DOWNCOUNTREG), R(DOWNCOUNTREG));
} else {
CMP(32, MDisp(CTXREG, downcountOffset), Imm8(0));
}
FixupBranch bail = J_CC(CC_S, true);
SetJumpTarget(skipToRealDispatch);
dispatcherNoCheck_ = GetCodePtr();
// Debug
if (enableDebug) {
#if PPSSPP_ARCH(AMD64)
if (jitbaseInR15) {
ABI_CallFunctionAA(reinterpret_cast<void *>(&ShowPC), R(MEMBASEREG), R(JITBASEREG));
} else if (jitbaseCtxDisp != 0) {
LEA(64, SCRATCH1, MDisp(CTXREG, jitbaseCtxDisp));
ABI_CallFunctionAA(reinterpret_cast<void *>(&ShowPC), R(MEMBASEREG), R(SCRATCH1));
} else {
ABI_CallFunctionAC(reinterpret_cast<void *>(&ShowPC), R(MEMBASEREG), (u32)jitbase);
}
#else
ABI_CallFunctionCC(reinterpret_cast<void *>(&ShowPC), (u32)mipsState->space->base, (u32)GetBasePtr());
#endif
}
MovFromPC(SCRATCH1);
WriteDebugPC(SCRATCH1);
// Look the block up in the shadow. Its entry is the offset into the code space, or 0 for none.
AND(32, R(SCRATCH1), Imm32(Memory::BLOCK_SHADOW_ENTRY_MASK));
#if PPSSPP_ARCH(X86)
_assert_msg_(mipsState->space->blockShadow != 0, "Block shadow bogus");
hooks_.dispatchFetch = GetCodePtr();
MOV(32, R(SCRATCH1), MDisp(SCRATCH1, (u32)mipsState->space->blockShadow));
#elif PPSSPP_ARCH(AMD64)
MOV(64, R(RDX), ImmPtr(mipsState->space->blockShadow));
hooks_.dispatchFetch = GetCodePtr();
MOV(32, R(SCRATCH1), MComplex(RDX, SCRATCH1, SCALE_1, 0));
#endif
TEST(32, R(SCRATCH1), R(SCRATCH1));
FixupBranch needsCompile = J_CC(CC_Z);
#if PPSSPP_ARCH(X86)
LEA(32, SCRATCH1, MDisp(SCRATCH1, (u32)GetBasePtr()));
#elif PPSSPP_ARCH(AMD64)
if (jitbaseInR15) {
ADD(64, R(SCRATCH1), R(JITBASEREG));
} else if (jitbaseCtxDisp) {
LEA(64, SCRATCH1, MComplex(CTXREG, SCRATCH1, SCALE_1, jitbaseCtxDisp));
} else {
// See above, reserveR15ForAsm is used when above 0x7FFFFFFF.
LEA(64, SCRATCH1, MDisp(SCRATCH1, (s32)jitbase));
}
#endif
JMPptr(R(SCRATCH1));
SetJumpTarget(needsCompile);
// No block found, let's jit. We don't need to save static regs, they're all callee saved.
// A replacement hook's block shadow entry jumps here too (GetBlockShadowHookValue()). JitAt
// replaces it with the new block's entry, and destroying that block puts the hook value back.
hooks_.blockShadowHook = GetCodePtr();
RestoreRoundingMode(true);
WriteDebugProfilerStatus(IRProfilerStatus::COMPILING);
LEA(PTRBITS, ECX, MDisp(CTXREG, -(s32)offsetof(MIPSState, f[0])));
ABI_CallFunctionR(reinterpret_cast<void *>(&MIPSComp::JitAt), ECX);
WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT);
ApplyRoundingMode(true);
// Let's just dispatch again, we'll enter the block since we know it's there.
JMP(dispatcherNoCheck_, true);
SetJumpTarget(bail);
if (RipAccessible((const void *)&coreState)) {
CMP(32, M(&coreState), Imm8(0)); // rip accessible
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
CMP(32, MatR(RAX), Imm8(0));
}
J_CC(CC_Z, outerLoop_, true);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState);
WriteDebugProfilerStatus(IRProfilerStatus::NOT_RUNNING);
SaveStaticRegisters();
RestoreRoundingMode(true);
ABI_PopAllCalleeSavedRegsAndAdjustStack();
RET();
hooks_.crashHandler = GetCodePtr();
if (RipAccessible((const void *)&coreState)) {
MOV(32, M(&coreState), Imm32(CORE_RUNTIME_ERROR));
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
MOV(32, MatR(RAX), Imm32(CORE_RUNTIME_ERROR));
}
JMP(quitLoop, true);
#if PPSSPP_ARCH(AMD64)
vfpuDot_ = GenerateX64VfpuDot(this, CTXREG);
#endif
// Leave this at the end, add more stuff above.
if (enableDisasm) {
#if PPSSPP_ARCH(AMD64)
std::vector<std::string> lines = DisassembleX86(disasmStart, (int)(GetCodePtr() - disasmStart));
for (const auto &s : lines) {
INFO_LOG(Log::JIT, "%s", s.c_str());
}
#endif
}
// Let's spare the pre-generated code from unprotect-reprotect.
AlignCodePage();
jitStartOffset_ = (int)(GetCodePtr() - start);
EndWrite();
}
void X64JitBackend::GetFixedCodeLabels(std::vector<IRNativeFixedCodeLabel> &labels) const {
labels.push_back({ outerLoop_, "outerLoop", false });
labels.push_back({ outerLoopPCInSCRATCH1_, "outerLoopPCInSCRATCH1", false });
labels.push_back({ dispatcherCheckCoreState_, "dispatcherCheckCoreState", false });
labels.push_back({ dispatcherPCInSCRATCH1_, "dispatcherPCInSCRATCH1", false });
labels.push_back({ dispatcherNoCheck_, "dispatcherNoCheck", false });
labels.push_back({ restoreRoundingMode_, "restoreRoundingMode", true });
labels.push_back({ applyRoundingMode_, "applyRoundingMode", true });
labels.push_back({ vfpuDot_, "vfpuDot", true });
labels.push_back({ saveStaticRegisters_, "saveStaticRegisters", true });
labels.push_back({ loadStaticRegisters_, "loadStaticRegisters", true });
}
} // namespace MIPSComp
#endif