Files
ppsspp/Core/MIPS/IR/IRNativeCommon.h
T
Henrik RydgårdandClaude Opus 5.5 b175bc859a IR JIT: Grow the native code space to 24 MB
Big games fill 16 MB in normal play. It's now one constant shared by
every backend. ARM32's 32 MB branch range is the limit on going higher
there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 15:11:47 +02:00

272 lines
9.5 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/.
#pragma once
#include <map>
#include <unordered_map>
#include "Common/CodeBlock.h"
#include "Core/MIPS/IR/IRJit.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
namespace MIPSComp {
typedef void (*IRNativeFuncNoArg)();
enum class IRProfilerStatus : int32_t {
NOT_RUNNING,
IN_JIT,
TIMER_ADVANCE,
COMPILING,
MATH_HELPER,
REPLACEMENT,
SYSCALL,
INTERPRET,
IR_INTERPRET,
};
struct IRNativeHooks {
IRNativeFuncNoArg enterDispatcher = nullptr;
const uint8_t *dispatcher = nullptr;
const uint8_t *dispatchFetch = nullptr;
const uint8_t *crashHandler = nullptr;
// The dispatcher's compile path, where a replacement hook's block shadow entry goes.
const uint8_t *blockShadowHook = nullptr;
uint32_t *profilerPC = nullptr;
IRProfilerStatus *profilerStatus = nullptr;
};
struct IRNativeBlockExit {
int offset;
int len;
uint32_t dest;
};
// Names a piece of the fixed code, for the profiler.
struct IRNativeFixedCodeLabel {
const u8 *ptr;
const char *name;
// A leaf routine blocks call, so the return address (LR, [rsp]) says which block.
bool calledFromBlocks;
};
struct IRNativeBlock {
int checkedOffset = 0;
std::vector<IRNativeBlockExit> exits;
// Code offset of each IR instruction, only kept when profiling.
std::vector<int> irOffsets;
};
// The native code space of every backend. Blocks have to reach the dispatcher at its start, and
// ARM32 branches reach 32 MB.
constexpr int NATIVE_CODE_SPACE_SIZE = 24 * 1024 * 1024;
class IRNativeBackend {
public:
IRNativeBackend(IRBlockCache &blocks);
virtual ~IRNativeBackend();
void CompileIRInst(IRInst inst);
virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) const;
bool CodeInRange(const u8 *ptr) const;
int OffsetFromCodePtr(const u8 *ptr);
virtual void GenerateFixedCode(MIPSState *mipsState) = 0;
virtual bool CompileBlock(IRBlockCache *irBlockCache, int block_num) = 0;
virtual void ClearAllBlocks() = 0;
virtual void InvalidateBlock(IRBlockCache *irBlockCache, int block_num) = 0;
void FinalizeBlock(IRBlockCache *irBlockCache, int block_num, const JitOptions &jo);
virtual void UpdateFCR31(MIPSState *mipsState) {}
const IRNativeHooks &GetNativeHooks() const {
return hooks_;
}
const IRNativeBlock *GetNativeBlock(int block_num) const;
void SetBlockCheckedOffset(int block_num, int offset);
void SetProfiling(bool profiling) {
profiling_ = profiling;
}
virtual const CodeBlockCommon &CodeBlock() const = 0;
// The backend's own routines, without the dispatcher hooks.
virtual void GetFixedCodeLabels(std::vector<IRNativeFixedCodeLabel> &labels) const {}
// Those and the dispatcher hooks, by address.
void GetAllFixedCodeLabels(std::vector<IRNativeFixedCodeLabel> &labels) const;
// The name of the fixed code routine ptr is in, with an offset if not at its start, for the
// disassembly and profilers (so no spaces).
std::string DescribeFixedCode(const u8 *ptr) const;
protected:
virtual void CompIR_Arith(IRInst inst) = 0;
virtual void CompIR_Assign(IRInst inst) = 0;
virtual void CompIR_Basic(IRInst inst) = 0;
virtual void CompIR_Bits(IRInst inst) = 0;
virtual void CompIR_Breakpoint(IRInst inst) = 0;
virtual void CompIR_Compare(IRInst inst) = 0;
virtual void CompIR_CondAssign(IRInst inst) = 0;
virtual void CompIR_CondStore(IRInst inst) = 0;
virtual void CompIR_Div(IRInst inst) = 0;
virtual void CompIR_Exit(IRInst inst) = 0;
virtual void CompIR_ExitIf(IRInst inst) = 0;
virtual void CompIR_FArith(IRInst inst) = 0;
virtual void CompIR_FAssign(IRInst inst) = 0;
virtual void CompIR_FCompare(IRInst inst) = 0;
virtual void CompIR_FCondAssign(IRInst inst) = 0;
virtual void CompIR_FCvt(IRInst inst) = 0;
virtual void CompIR_FLoad(IRInst inst) = 0;
virtual void CompIR_FRound(IRInst inst) = 0;
virtual void CompIR_FSat(IRInst inst) = 0;
virtual void CompIR_FSpecial(IRInst inst) = 0;
virtual void CompIR_FStore(IRInst inst) = 0;
virtual void CompIR_Generic(IRInst inst) = 0;
virtual void CompIR_HiLo(IRInst inst) = 0;
virtual void CompIR_Interpret(IRInst inst) = 0;
virtual void CompIR_Load(IRInst inst) = 0;
virtual void CompIR_LoadShift(IRInst inst) = 0;
virtual void CompIR_Logic(IRInst inst) = 0;
virtual void CompIR_Mult(IRInst inst) = 0;
virtual void CompIR_RoundingMode(IRInst inst) = 0;
virtual void CompIR_Shift(IRInst inst) = 0;
virtual void CompIR_Store(IRInst inst) = 0;
virtual void CompIR_StoreShift(IRInst inst) = 0;
virtual void CompIR_System(IRInst inst) = 0;
virtual void CompIR_Transfer(IRInst inst) = 0;
virtual void CompIR_VecArith(IRInst inst) = 0;
virtual void CompIR_VecAssign(IRInst inst) = 0;
virtual void CompIR_VecHoriz(IRInst inst) = 0;
virtual void CompIR_VecLoad(IRInst inst) = 0;
virtual void CompIR_VecPack(IRInst inst) = 0;
virtual void CompIR_VecStore(IRInst inst) = 0;
virtual void CompIR_ValidateAddress(IRInst inst) = 0;
virtual void OverwriteExit(int srcOffset, int len, int block_num) = 0;
// Returns true when debugging statistics should be compiled in.
bool DebugStatsEnabled() const;
bool DebugProfilerEnabled() const;
// Callback (compile when DebugStatsEnabled()) to log a base interpreter hit.
// Call the func returned by MIPSGetInterpretFunc(op) directly for interpret.
static void NotifyMIPSInterpret(const char *name);
// Callback to log AND perform a base interpreter op. Alternative to NotifyMIPSInterpret().
static void DoMIPSInst(uint32_t op);
// Callback to log AND perform an IR interpreter inst. Returns 0 or a PC to jump to.
static uint32_t DoIRInst(uint64_t inst);
static int ReportBadAddress(uint32_t addr, uint32_t alignment, uint32_t isWrite);
void AddLinkableExit(int block_num, uint32_t pc, int exitStartOffset, int exitLen);
void EraseAllLinks(int block_num);
// For the profiler's report. Does nothing unless profiling.
void RecordIRAddresses(int block_num, const std::vector<const u8 *> &addresses);
IRNativeHooks hooks_;
IRBlockCache &blocks_;
std::vector<IRNativeBlock> nativeBlocks_;
std::unordered_multimap<uint32_t, int> linksTo_;
bool profiling_ = false;
};
class IRNativeBlockCacheDebugInterface : public JitBlockCacheDebugInterface {
public:
IRNativeBlockCacheDebugInterface(const MIPSComp::IRBlockCache &irBlocks);
void Init(const IRNativeBackend *backend);
int GetNumBlocks() const override;
int GetBlockNumberFromStartAddress(u32 em_address) const override;
JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override;
JitBlockMeta GetBlockMeta(int blockNum) const override;
JitBlockProfileStats GetBlockProfileStats(int blockNum) const override;
void ComputeStats(BlockCacheStats &bcStats) const override;
bool IsValidBlock(int blockNum) const override;
private:
void GetBlockCodeRange(int blockNum, int *startOffset, int *size) const;
const MIPSComp::IRBlockCache &irBlocks_;
const CodeBlockCommon *codeBlock_ = nullptr;
const IRNativeBackend *backend_ = nullptr;
};
class IRNativeJit : public IRJit {
public:
IRNativeJit(MIPSState *mipsState);
void RunLoopUntil(u64 globalticks) override;
void ClearCache() override;
bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
bool CodeInRange(const u8 *ptr) const override;
bool IsAtDispatchFetch(const u8 *ptr) const override;
u32 GetBlockShadowHookValue() const override;
const u8 *GetDispatcher() const override;
const u8 *GetCrashHandler() const override;
void UpdateFCR31() override;
void FlushProfile() override;
JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override;
const u8 *GetCodeBase() const override;
void GetStats(JitStats *stats) const override;
protected:
void Init(IRNativeBackend &backend);
bool CompileNativeBlock(IRBlockCache *irBlockCache, int block_num) override;
void FinalizeNativeBlock(IRBlockCache *irBlockCache, int block_num) override;
void InvalidateNativeBlock(IRBlockCache *irBlockCache, int block_num) override;
void CollectProfileSamples();
int FindBlockByCodeOffset(int offset) const;
int FindBlockOffsetEnd() const;
void WriteProfileReport() override;
struct BlockProfile {
int64_t samples = 0;
// Samples in a routine the block called (like the dot product).
int64_t routineSamples = 0;
std::map<int, int64_t> byOffset;
// routineSamples by the code offset the routine returns to.
std::map<int, int64_t> routineByReturn;
};
std::unordered_map<int, BlockProfile> blockProfiles_;
std::unordered_map<uintptr_t, int64_t> hostProfile_;
// Sorted by ptr. Samples by label, split by whether a block was charged for them.
std::vector<IRNativeFixedCodeLabel> fixedLabels_;
std::map<std::string, int64_t> fixedCodeByLabel_;
std::map<std::string, int64_t> routinesByLabel_;
// All fixed code samples, by code offset.
std::map<int, int64_t> fixedCodeByOffset_;
int64_t fixedCodeSamples_ = 0;
int64_t totalSamples_ = 0;
IRNativeBackend *backend_ = nullptr;
IRNativeHooks hooks_;
IRNativeBlockCacheDebugInterface debugInterface_;
};
} // namespace MIPSComp