Files
ppsspp/Core/MIPS/IR/IRInst.cpp
T
Henrik RydgårdandClaude Opus 5.5 53ac2c7c56 IR: Compile kernel loads and stores instead of interpreting them
Kernel code's loads and stores may reach hardware registers, which only the
interpreter handled, so the IR frontend sent every one of them (in kernel
blocks, only run by the VSH boot) to it.

They now become LoadKernel*/StoreKernel* IR ops (one per size, like the
plain loads and stores), which check at run time whether the address is
memory or a hardware register (MMIO). After constant
propagation, ResolveKernelMemory turns the ones at a constant address that's
plain memory into ordinary loads and stores, so only the hardware registers,
and addresses only known at run time, stay checked. The native JITs run the
two ops through their IR fallback. They're barriers, since a register access
has side effects, but can't change the GPRs, so constants stay known across.

The kernel mode check for MMIO moves to the interpreter, the only caller that
can run user code there. TestMMIO covers the new paths and which accesses
stay checked. Under --vsh, the JIT and the interpreter now make the same
unhandled register accesses, in the same numbers.

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

417 lines
14 KiB
C++

#include <cstring>
#include "Common/CommonFuncs.h"
#include "Common/Log.h"
#include "Core/MIPS/IR/IRInst.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/HLE/ReplaceTables.h"
// Legend
// ======================
// _ = ignore
// G = GPR register
// C = 32-bit constant from array
// c = 8-bit constant from array
// I = immediate value from instruction
// F = FPR register, single
// V = FPR register, Vec4. Reg number always divisible by 4.
// 2 = FPR register, Vec2 (uncommon)
// v = Vec4Init constant, chosen by immediate
// s = Shuffle immediate (4 2-bit fields, choosing a xyzw shuffle)
// r = Replacement function (in constant field)
//
// WARNING: The IRJit compiler also uses these letters for semantic information!
// So if you add new letters, don't forget to add them to IRNativeRegCacheBase::MappingFromInst.
static const IRMeta irMeta[] = {
{ IROp::Nop, "Nop", "" },
{ IROp::SetConst, "SetConst", "GC" },
{ IROp::SetConstF, "SetConstF", "FC" },
{ IROp::Mov, "Mov", "GG" },
{ IROp::Add, "Add", "GGG" },
{ IROp::Sub, "Sub", "GGG" },
{ IROp::Neg, "Neg", "GG" },
{ IROp::Not, "Not", "GG" },
{ IROp::And, "And", "GGG" },
{ IROp::Or, "Or", "GGG" },
{ IROp::Xor, "Xor", "GGG" },
{ IROp::AddConst, "AddConst", "GGC" },
{ IROp::OptAddConst, "OptAddConst", "GC" },
{ IROp::SubConst, "SubConst", "GGC" },
{ IROp::AndConst, "AndConst", "GGC" },
{ IROp::OrConst, "OrConst", "GGC" },
{ IROp::XorConst, "XorConst", "GGC" },
{ IROp::OptAndConst, "OptAndConst", "GC" },
{ IROp::OptOrConst, "OptOrConst", "GC" },
{ IROp::Shl, "Shl", "GGG" },
{ IROp::Shr, "Shr", "GGG" },
{ IROp::Sar, "Sar", "GGG" },
{ IROp::Ror, "Ror", "GGG" },
{ IROp::ShlImm, "ShlImm", "GGI" },
{ IROp::ShrImm, "ShrImm", "GGI" },
{ IROp::SarImm, "SarImm", "GGI" },
{ IROp::RorImm, "RorImm", "GGI" },
{ IROp::Slt, "Slt", "GGG" },
{ IROp::SltConst, "SltConst", "GGC" },
{ IROp::SltU, "SltU", "GGG" },
{ IROp::SltUConst, "SltUConst", "GGC" },
{ IROp::Clz, "Clz", "GG" },
{ IROp::MovZ, "MovZ", "GGG", IRFLAG_SRC3DST },
{ IROp::MovNZ, "MovNZ", "GGG", IRFLAG_SRC3DST },
{ IROp::Max, "Max", "GGG" },
{ IROp::Min, "Min", "GGG" },
{ IROp::BSwap16, "BSwap16", "GG" },
{ IROp::BSwap32, "BSwap32", "GG" },
{ IROp::Mult, "Mult", "_GG" },
{ IROp::MultU, "MultU", "_GG" },
{ IROp::Madd, "Madd", "_GG" },
{ IROp::MaddU, "MaddU", "_GG" },
{ IROp::Msub, "Msub", "_GG" },
{ IROp::MsubU, "MsubU", "_GG" },
{ IROp::Div, "Div", "_GG" },
{ IROp::DivU, "DivU", "_GG" },
{ IROp::MtLo, "MtLo", "_G" },
{ IROp::MtHi, "MtHi", "_G" },
{ IROp::MfLo, "MfLo", "G" },
{ IROp::MfHi, "MfHi", "G" },
{ IROp::Ext8to32, "Ext8to32", "GG" },
{ IROp::Ext16to32, "Ext16to32", "GG" },
{ IROp::ReverseBits, "ReverseBits", "GG" },
{ IROp::Load8, "Load8", "GGC" },
{ IROp::Load8Ext, "Load8Ext", "GGC" },
{ IROp::Load16, "Load16", "GGC" },
{ IROp::Load16Ext, "Load16Ext", "GGC" },
{ IROp::Load32, "Load32", "GGC" },
{ IROp::Load32Left, "Load32Left", "GGC", IRFLAG_SRC3DST },
{ IROp::Load32Right, "Load32Right", "GGC", IRFLAG_SRC3DST },
{ IROp::Load32Linked, "Load32Linked", "GGC" },
{ IROp::LoadFloat, "LoadFloat", "FGC" },
{ IROp::LoadVec4, "LoadVec4", "VGC" },
{ IROp::Store8, "Store8", "GGC", IRFLAG_SRC3 },
{ IROp::Store16, "Store16", "GGC", IRFLAG_SRC3 },
{ IROp::Store32, "Store32", "GGC", IRFLAG_SRC3 },
{ IROp::Store32Left, "Store32Left", "GGC", IRFLAG_SRC3 },
{ IROp::Store32Right, "Store32Right", "GGC", IRFLAG_SRC3 },
{ IROp::Store32Conditional, "Store32Conditional", "GGC", IRFLAG_SRC3DST },
{ IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 },
{ IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 },
// Barriers: a hardware register access has side effects, and mustn't move or merge.
{ IROp::LoadKernel8, "LoadKernel8", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel8Ext, "LoadKernel8Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16, "LoadKernel16", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16Ext, "LoadKernel16Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel32, "LoadKernel32", "GGC", IRFLAG_BARRIER },
{ IROp::StoreKernel8, "StoreKernel8", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel16, "StoreKernel16", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel32, "StoreKernel32", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::FAdd, "FAdd", "FFF" },
{ IROp::FSub, "FSub", "FFF" },
{ IROp::FMul, "FMul", "FFF" },
{ IROp::FDiv, "FDiv", "FFF" },
{ IROp::FMin, "FMin", "FFF" },
{ IROp::FMax, "FMax", "FFF" },
{ IROp::FMov, "FMov", "FF" },
{ IROp::FSqrt, "FSqrt", "FF" },
{ IROp::FSin, "FSin", "FF" },
{ IROp::FCos, "FCos", "FF" },
{ IROp::FRSqrt, "FRSqrt", "FF" },
{ IROp::FRecip, "FRecip", "FF" },
{ IROp::FAsin, "FAsin", "FF" },
{ IROp::FVSqrt, "FVSqrt", "FF" },
{ IROp::FExp2, "FExp2", "FF" },
{ IROp::FLog2, "FLog2", "FF" },
{ IROp::FHalfToFloat, "FHalfToFloat", "FFI" },
{ IROp::FSinCos, "FSinCos", "2F" },
{ IROp::FNeg, "FNeg", "FF" },
{ IROp::FSign, "FSign", "FF" },
{ IROp::FAbs, "FAbs", "FF" },
{ IROp::FRound, "FRound", "FF" },
{ IROp::FTrunc, "FTrunc", "FF" },
{ IROp::FCeil, "FCeil", "FF" },
{ IROp::FFloor, "FFloor", "FF" },
{ IROp::FCvtWS, "FCvtWS", "FF" },
{ IROp::FCvtSW, "FCvtSW", "FF" },
{ IROp::FCvtScaledWS, "FCvtScaledWS", "FFI" },
{ IROp::FCvtScaledSW, "FCvtScaledSW", "FFI" },
{ IROp::FCmp, "FCmp", "mFF" },
{ IROp::FSat0_1, "FSat(0 - 1)", "FF" },
{ IROp::FSatMinus1_1, "FSat(-1 - 1)", "FF" },
{ IROp::FMovFromGPR, "FMovFromGPR", "FG" },
{ IROp::FMovToGPR, "FMovToGPR", "GF" },
{ IROp::OptFMovToGPRShr8, "OptFMovToGPRShr8", "GF" },
{ IROp::OptFCvtSWFromGPR, "OptFCvtSWFromGPR", "FG" },
{ IROp::OptExitToConstIfEqElse, "OptExitIfEqElse", "CGG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfNeqElse, "OptExitIfNeqElse", "CGG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfGtZElse, "OptExitIfGtZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfGeZElse, "OptExitIfGeZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfLtZElse, "OptExitIfLtZElse", "CG", IRFLAG_EXIT },
{ IROp::OptExitToConstIfLeZElse, "OptExitIfLeZElse", "CG", IRFLAG_EXIT },
{ IROp::FpCondFromReg, "FpCondFromReg", "_G" },
{ IROp::FpCondToReg, "FpCondToReg", "G" },
{ IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" },
{ IROp::FpCtrlToReg, "FpCtrlToReg", "G" },
{ IROp::VfpuCtrlToReg, "VfpuCtrlToReg", "GT" },
{ IROp::SetCtrlVFPU, "SetCtrlVFPU", "TC" },
{ IROp::SetCtrlVFPUReg, "SetCtrlVFPUReg", "TG" },
{ IROp::SetCtrlVFPUFReg, "SetCtrlVFPUFReg", "TF" },
{ IROp::FCmovVfpuCC, "FCmovVfpuCC", "FFI", IRFLAG_SRC3DST },
{ IROp::FCmpVfpuBit, "FCmpVfpuBit", "IFF" },
{ IROp::FCmpVfpuAggregate, "FCmpVfpuAggregate", "I" },
{ IROp::Vec4Init, "Vec4Init", "Vv" },
{ IROp::Vec4Shuffle, "Vec4Shuffle", "VVs" },
{ IROp::Vec4Blend, "Vec4Blend", "VVVc" },
{ IROp::Vec4Mov, "Vec4Mov", "VV" },
{ IROp::Vec4Add, "Vec4Add", "VVV" },
{ IROp::Vec4Sub, "Vec4Sub", "VVV" },
{ IROp::Vec4Div, "Vec4Div", "VVV" },
{ IROp::Vec4Mul, "Vec4Mul", "VVV" },
{ IROp::Vec4Scale, "Vec4Scale", "VVF" },
{ IROp::Vec4VfpuDot, "Vec4VfpuDot", "FVV" },
{ IROp::Vec4VfpuMultiDot, "Vec4VfpuMultiDot", "VMVc" },
{ IROp::Vec4Neg, "Vec4Neg", "VV" },
{ IROp::Vec4Abs, "Vec4Abs", "VV" },
{ IROp::Vec4Min, "Vec4Min", "VVV" },
{ IROp::Vec4Max, "Vec4Max", "VVV" },
{ IROp::Vec4Slt, "Vec4Slt", "VVV" },
{ IROp::Vec4Sge, "Vec4Sge", "VVV" },
{ IROp::Vec4CvtScaledSW, "Vec4CvtScaledSW", "VVI" },
// Pack/Unpack
{ IROp::Vec2Unpack16To31, "Vec2Unpack16To31", "2F" }, // Note that the result is shifted down by 1, hence 31
{ IROp::Vec2Unpack16To32, "Vec2Unpack16To32", "2F" },
{ IROp::Vec4Unpack8To32, "Vec4Unpack8To32", "VF" },
{ IROp::Vec4DuplicateUpperBitsAndShift1, "Vec4DuplicateUpperBitsAndShift1", "VV" },
{ IROp::Vec4Pack32To8, "Vec4Pack32To8", "FV" },
{ IROp::Vec4Pack31To8, "Vec4Pack31To8", "FV" },
{ IROp::Vec2Pack32To16, "Vec2Pack32To16", "F2" },
{ IROp::Vec2Pack31To16, "Vec2Pack31To16", "F2" },
{ IROp::Interpret, "Interpret", "_C", IRFLAG_BARRIER },
{ IROp::Downcount, "Downcount", "_C" },
{ IROp::ExitToPC, "ExitToPC", "", IRFLAG_EXIT },
{ IROp::ExitToConst, "Exit", "C", IRFLAG_EXIT },
{ IROp::ExitToConstIfEq, "ExitIfEq", "CGG", IRFLAG_EXIT },
{ IROp::ExitToConstIfNeq, "ExitIfNeq", "CGG", IRFLAG_EXIT },
{ IROp::ExitToConstIfGtZ, "ExitIfGtZ", "CG", IRFLAG_EXIT },
{ IROp::ExitToConstIfGeZ, "ExitIfGeZ", "CG", IRFLAG_EXIT },
{ IROp::ExitToConstIfLeZ, "ExitIfLeZ", "CG", IRFLAG_EXIT },
{ IROp::ExitToConstIfLtZ, "ExitIfLtZ", "CG", IRFLAG_EXIT },
{ IROp::ExitToReg, "ExitToReg", "_G", IRFLAG_EXIT },
{ IROp::Syscall, "Syscall", "_C", IRFLAG_EXIT },
{ IROp::SyscallUnresolved, "SyscallUnresolved", "C", IRFLAG_EXIT },
{ IROp::Break, "Break", "", IRFLAG_EXIT },
{ IROp::SetPC, "SetPC", "_G" },
{ IROp::SetPCConst, "SetPC", "_C" },
{ IROp::CallReplacement, "CallRepl", "Gr", IRFLAG_BARRIER },
{ IROp::Breakpoint, "Breakpoint", "_C", IRFLAG_BARRIER },
{ IROp::MemoryCheck, "MemoryCheck", "IGC", IRFLAG_BARRIER },
{ IROp::ValidateAddress8, "ValidAddr8", "_GC", IRFLAG_BARRIER },
{ IROp::ValidateAddress16, "ValidAddr16", "_GC", IRFLAG_BARRIER },
{ IROp::ValidateAddress32, "ValidAddr32", "_GC", IRFLAG_BARRIER },
{ IROp::ValidateAddress128, "ValidAddr128", "_GC", IRFLAG_BARRIER },
{ IROp::RestoreRoundingMode, "RestoreRoundingMode", "" },
{ IROp::ApplyRoundingMode, "ApplyRoundingMode", "" },
{ IROp::UpdateRoundingMode, "UpdateRoundingMode", "" },
{ IROp::LogIRBlock, "LogIRBlock", "" },
};
const IRMeta *metaIndex[256];
// Is there a way to constexpr this?
void InitIR() {
if (metaIndex[0])
return;
for (size_t i = 0; i < ARRAY_SIZE(irMeta); i++) {
metaIndex[(int)irMeta[i].op] = &irMeta[i];
}
}
void IRWriter::Write(IROp op, u8 dst, u8 src1, u8 src2, u32 constant) {
IRInst inst;
inst.op = op;
inst.dest = dst;
inst.src1 = src1;
inst.src2 = src2;
inst.constant = constant;
insts_.push_back(inst);
}
void IRWriter::WriteFC(IROp op, u8 dst, u8 src1, u8 src2, float fconstant) {
u32 constant;
memcpy(&constant, &fconstant, sizeof(u32));
IRInst inst;
inst.op = op;
inst.dest = dst;
inst.src1 = src1;
inst.src2 = src2;
inst.constant = constant;
insts_.push_back(inst);
}
void IRWriter::WriteSetConstant(u8 dst, u32 value) {
Write(IROp::SetConst, dst, 0, 0, value);
}
void IRWriter::WriteSetConstantFloat(u8 dst, float value) {
WriteFC(IROp::SetConstF, dst, 0, 0, value);
}
void IRWriter::ReplaceConstant(size_t instNumber, u32 newConstant) {
_dbg_assert_(instNumber < insts_.size());
insts_[instNumber].constant = newConstant;
}
static std::string GetGPRName(int r) {
if (r < 32) {
return currentDebugMIPS->GetRegName(0, r);
}
switch (r) {
case IRTEMP_0: return "irtemp0";
case IRTEMP_1: return "irtemp1";
case IRTEMP_2: return "irtemp2";
case IRTEMP_3: return "irtemp3";
case IRTEMP_LHS: return "irtemp_lhs";
case IRTEMP_RHS: return "irtemp_rhs";
case IRTEMP_LR_ADDR: return "irtemp_addr";
case IRTEMP_LR_VALUE: return "irtemp_value";
case IRTEMP_LR_MASK: return "irtemp_mask";
case IRTEMP_LR_SHIFT: return "irtemp_shift";
default: return "(unk)";
}
}
void DisassembleParam(char *buf, int bufSize, u8 param, char type, u32 constant) {
static const char * const vfpuCtrlNames[VFPU_CTRL_MAX] = {
"SPFX",
"TPFX",
"DPFX",
"CC",
"INF4",
"RSV5",
"RSV6",
"REV",
"RCX0",
"RCX1",
"RCX2",
"RCX3",
"RCX4",
"RCX5",
"RCX6",
"RCX7",
};
static const char * const initVec4Names[8] = {
"[0 0 0 0]",
"[1 1 1 1]",
"[-1 -1 -1 -1]",
"[1 0 0 0]",
"[0 1 0 0]",
"[0 0 1 0]",
"[0 0 0 1]",
};
static const char * const xyzw = "xyzw";
switch (type) {
case 'G':
snprintf(buf, bufSize, "%s", GetGPRName(param).c_str());
break;
case 'F':
if (param >= 32) {
snprintf(buf, bufSize, "vf%d", param - 32);
} else {
snprintf(buf, bufSize, "f%d", param);
}
break;
case 'V':
if (param >= 32) {
snprintf(buf, bufSize, "vf%d..vf%d", param - 32, param - 32 + 3);
} else {
snprintf(buf, bufSize, "f%d..f%d", param, param + 3);
}
break;
case 'M':
snprintf(buf, bufSize, "vf%d..vf%d", param - 32, param - 32 + 15);
break;
case '2':
if (param >= 32) {
snprintf(buf, bufSize, "vf%d,vf%d", param - 32, param - 32 + 1);
} else {
snprintf(buf, bufSize, "f%d,f%d", param, param + 1);
}
break;
case 'C':
snprintf(buf, bufSize, "0x%08x", constant);
break;
case 'c':
snprintf(buf, bufSize, "0x%02x", constant);
break;
case 'I':
snprintf(buf, bufSize, "0x%02x", param);
break;
case 'm':
snprintf(buf, bufSize, "%d", param);
break;
case 'T':
snprintf(buf, bufSize, "%s", vfpuCtrlNames[param]);
break;
case 'v':
snprintf(buf, bufSize, "%s", initVec4Names[param]);
break;
case 's':
snprintf(buf, bufSize, "%c%c%c%c", xyzw[param & 3], xyzw[(param >> 2) & 3], xyzw[(param >> 4) & 3], xyzw[(param >> 6) & 3]);
break;
case 'r':
{
const ReplacementTableEntry *entry = GetReplacementFunc(constant);
if (entry) {
snprintf(buf, bufSize, "%s", entry->name);
} else {
snprintf(buf, bufSize, "(unkn. repl %d)", constant);
}
break;
}
case '_':
case '\0':
buf[0] = 0;
break;
default:
snprintf(buf, bufSize, "?");
break;
}
}
const IRMeta *GetIRMeta(IROp op) {
return metaIndex[(int)op];
}
void DisassembleIR(char *buf, size_t bufsize, IRInst inst) {
const IRMeta *meta = GetIRMeta(inst.op);
if (!meta) {
snprintf(buf, bufsize, "Unknown %d", (int)inst.op);
return;
}
char bufDst[16];
char bufSrc1[16];
char bufSrc2[16];
// Only really used for constant.
char bufSrc3[16];
DisassembleParam(bufDst, sizeof(bufDst) - 2, inst.dest, meta->types[0], inst.constant);
DisassembleParam(bufSrc1, sizeof(bufSrc1) - 2, inst.src1, meta->types[1], inst.constant);
DisassembleParam(bufSrc2, sizeof(bufSrc2), inst.src2, meta->types[2], inst.constant);
DisassembleParam(bufSrc3, sizeof(bufSrc3), inst.src3, meta->types[3], inst.constant);
if (meta->types[1] && meta->types[0] != '_') {
strcat(bufDst, ", ");
}
if (meta->types[2] && meta->types[1] != '_') {
strcat(bufSrc1, ", ");
}
if (meta->types[3] && meta->types[2] != '_') {
strcat(bufSrc2, ", ");
}
snprintf(buf, bufsize, "%s %s%s%s%s", meta->name, bufDst, bufSrc1, bufSrc2, bufSrc3);
}