Files
ppsspp/Core/MIPS/JitCommon/JitCommon.h
T
Henrik RydgårdandClaude Opus 5.5 6e475258da ME: Run the Media Engine on the ARM64 and x64 JITs
The native dispatcher generated for the ME leaves when its downcount
runs out instead of calling CoreTiming::Advance, and IRNativeJit gets
RunDowncount. Pointerify is decided from the CPU's own memory base. ARM32,
RISC-V and LoongArch64 still give the ME the IR interpreter.

LoadKernel/StoreKernel are now compiled on ARM64 and x64 rather than
going through the IR interpreter one op at a time: memory directly, or a
call to fixed code (which saves the caller-saved registers guest values
live in) for 0xBC000000-0xBFFFFFFF. ARM64's block size estimate counts
them as such. The 16-bit ones stay generic while the software renderer's
depth mirrors are active, as Load16/Store16 do. That matters for the main
CPU's kernel code too, and for all code while the ME is emulated, which
runs in kernel mode everywhere. mcidclan's me-soft-interrupt went from
476 billion to 6.4 billion host instructions for 3 emulated seconds,
ending with the same ME counter as the IR interpreter.

The ME unit tests run on the native JIT too.

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

194 lines
7.5 KiB
C++

// Copyright (c) 2012- 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/.
#pragma once
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
// TODO: Find a better place for these.
std::vector<std::string> DisassembleArm2(const u8 *data, int size);
std::vector<std::string> DisassembleArm64(const u8 *data, int size);
// offsets, if given, receives each line's offset from data.
std::vector<std::string> DisassembleX86(const u8 *data, int size, std::vector<int> *offsets = nullptr);
std::vector<std::string> DisassembleRV64(const u8 *data, int size);
std::vector<std::string> DisassembleLA64(const u8 *data, int size);
class JitBlockCacheDebugInterface;
class PointerWrap;
class MIPSState;
namespace MIPSComp {
void JitAt(MIPSState *mips);
class MIPSFrontendInterface {
public:
virtual ~MIPSFrontendInterface() {}
virtual void EatPrefix() = 0;
virtual void Comp_Generic(MIPSOpcode op) = 0;
virtual void Comp_ReplacementFunc(MIPSOpcode op) = 0;
virtual void Comp_ITypeMem(MIPSOpcode op) = 0;
virtual void Comp_StoreSync(MIPSOpcode op) = 0;
virtual void Comp_Cache(MIPSOpcode op) = 0;
virtual void Comp_RelBranch(MIPSOpcode op) = 0;
virtual void Comp_RelBranchRI(MIPSOpcode op) = 0;
virtual void Comp_FPUBranch(MIPSOpcode op) = 0;
virtual void Comp_FPULS(MIPSOpcode op) = 0;
virtual void Comp_FPUComp(MIPSOpcode op) = 0;
virtual void Comp_Jump(MIPSOpcode op) = 0;
virtual void Comp_JumpReg(MIPSOpcode op) = 0;
virtual void Comp_Syscall(MIPSOpcode op) = 0;
virtual void Comp_Break(MIPSOpcode op) = 0;
virtual void Comp_IType(MIPSOpcode op) = 0;
virtual void Comp_RType2(MIPSOpcode op) = 0;
virtual void Comp_RType3(MIPSOpcode op) = 0;
virtual void Comp_ShiftType(MIPSOpcode op) = 0;
virtual void Comp_Allegrex(MIPSOpcode op) = 0;
virtual void Comp_Allegrex2(MIPSOpcode op) = 0;
virtual void Comp_VBranch(MIPSOpcode op) = 0;
virtual void Comp_MulDivType(MIPSOpcode op) = 0;
virtual void Comp_Special3(MIPSOpcode op) = 0;
virtual void Comp_FPU3op(MIPSOpcode op) = 0;
virtual void Comp_FPU2op(MIPSOpcode op) = 0;
virtual void Comp_mxc1(MIPSOpcode op) = 0;
virtual void Comp_SV(MIPSOpcode op) = 0;
virtual void Comp_SVQ(MIPSOpcode op) = 0;
virtual void Comp_VPFX(MIPSOpcode op) = 0;
virtual void Comp_VVectorInit(MIPSOpcode op) = 0;
virtual void Comp_VMatrixInit(MIPSOpcode op) = 0;
virtual void Comp_VDot(MIPSOpcode op) = 0;
virtual void Comp_VecDo3(MIPSOpcode op) = 0;
virtual void Comp_VV2Op(MIPSOpcode op) = 0;
virtual void Comp_Mftv(MIPSOpcode op) = 0;
virtual void Comp_Vmfvc(MIPSOpcode op) = 0;
virtual void Comp_Vmtvc(MIPSOpcode op) = 0;
virtual void Comp_Vmmov(MIPSOpcode op) = 0;
virtual void Comp_VScl(MIPSOpcode op) = 0;
virtual void Comp_Vmmul(MIPSOpcode op) = 0;
virtual void Comp_Vmscl(MIPSOpcode op) = 0;
virtual void Comp_Vtfm(MIPSOpcode op) = 0;
virtual void Comp_VHdp(MIPSOpcode op) = 0;
virtual void Comp_VCrs(MIPSOpcode op) = 0;
virtual void Comp_VDet(MIPSOpcode op) = 0;
virtual void Comp_Vi2x(MIPSOpcode op) = 0;
virtual void Comp_Vx2i(MIPSOpcode op) = 0;
virtual void Comp_Vf2i(MIPSOpcode op) = 0;
virtual void Comp_Vi2f(MIPSOpcode op) = 0;
virtual void Comp_Vh2f(MIPSOpcode op) = 0;
virtual void Comp_Vcst(MIPSOpcode op) = 0;
virtual void Comp_Vhoriz(MIPSOpcode op) = 0;
virtual void Comp_VRot(MIPSOpcode op) = 0;
virtual void Comp_VIdt(MIPSOpcode op) = 0;
virtual void Comp_Vcmp(MIPSOpcode op) = 0;
virtual void Comp_Vcmov(MIPSOpcode op) = 0;
virtual void Comp_Viim(MIPSOpcode op) = 0;
virtual void Comp_Vfim(MIPSOpcode op) = 0;
virtual void Comp_VCrossQuat(MIPSOpcode op) = 0;
virtual void Comp_Vsgn(MIPSOpcode op) = 0;
virtual void Comp_Vocp(MIPSOpcode op) = 0;
virtual void Comp_ColorConv(MIPSOpcode op) = 0;
virtual void Comp_Vbfy(MIPSOpcode op) = 0;
virtual void Comp_DoNothing(MIPSOpcode op) = 0;
virtual int Replace_fabsf() = 0;
};
// For the JIT stats debug overlay.
struct JitStats {
int numBlocks = 0;
int numValidBlocks = 0;
// In IR instructions. Invalidated blocks keep their space until the cache is cleared.
size_t irArenaUsed = 0;
size_t irArenaSize = 0;
// In bytes. Zero when not compiling to native code.
size_t codeUsed = 0;
size_t codeSize = 0;
// How many times the cache was cleared because one of them filled up.
int irArenaFullClears = 0;
int codeFullClears = 0;
// Calls to InvalidateCacheAt that hit at least one block, and the blocks they hit.
int invalidations = 0;
int invalidatedBlocks = 0;
};
class JitInterface {
public:
virtual ~JitInterface() {}
virtual void GetStats(JitStats *stats) const {}
virtual const u8 *GetCodeBase() const = 0; // Just for displaying in the debugger.
virtual bool CodeInRange(const u8 *ptr) const = 0;
virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0;
virtual bool IsAtDispatchFetch(const u8 *ptr) const {
return false;
}
virtual const u8 *GetDispatcher() const = 0;
virtual const u8 *GetCrashHandler() const = 0;
virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0;
virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0;
// Invalidates every block not entirely inside one of the (address, size) ranges.
virtual void InvalidateCacheExcept(const std::vector<std::pair<u32, u32>> &keep) = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void RunLoopUntil(u64 globalticks) = 0;
// Runs until the downcount runs out, without CoreTiming. For the Media Engine, which the IR
// interpreter runs, and the native JITs that NativeJitRunsMediaEngine().
virtual void RunDowncount() {}
virtual void Compile(u32 em_address) = 0;
virtual void ClearCache() = 0;
virtual void UpdateFCR31() = 0;
// Writes out what --jit-profile collected, before the JIT goes away.
virtual void FlushProfile() {}
// What the block shadow holds for a replacement hook without a compiled block. The
// dispatcher has to send this to the compiler.
virtual u32 GetBlockShadowHookValue() const { return Memory::BLOCK_SHADOW_HOOK_NO_JIT; }
};
typedef void (MIPSFrontendInterface::*MIPSCompileFunc)(MIPSOpcode opcode);
typedef int (MIPSFrontendInterface::*MIPSReplaceFunc)();
struct BranchInfo {
BranchInfo(u32 pc, MIPSOpcode op, MIPSOpcode delaySlotOp, bool andLink, bool likely);
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);
// Whether the native JIT's dispatcher can run the Media Engine.
bool NativeJitRunsMediaEngine();
}