Files
ppsspp/Core/MIPS/IR/IRInst.cpp
T
Henrik RydgårdandClaude Opus 5.5 533baffb24 IR: Compute runs of VFPU dot products four at a time
A new IR op, Vec4VfpuMultiDot, does four vfpu_dot()s against one vector,
the other operands being the rows or Vec4s of a 4x4 block of registers.
The frontend emits it for vmmul and vtfm/vhtfm, and for runs of vdot that
fit it, like a matrix times a vector written out.

On ARM64 it calls a routine that has the dots in lanes rather than the
terms, so nothing is horizontal and nothing goes through the integer
registers. The block comes in with LD1, or LD4 for the transposed form,
when none of it is in host registers. The IR interpreter runs it through
vfpu_dot(); the other backends don't get it yet.

The CPU fuzzer gets runs of vdot shaped for it, and finds no difference
between the cores on them.

There's a routine per number of terms, so vdot.t runs don't pay for a
fourth. The vector's significands are worked out once and multiplied by
element, and the rounding at the end is UCVTF's in round-to-nearest,
which is what games run in; the integer version stays for the other
modes. The routine saves nothing, and the call site keeps only those
registers it clobbers that hold guest state.

JitDotBench, ns per dot: vtfm4.q 3.40, vmmul.q 3.12, a run of vdot.t
2.83, against 4.6 for single dots.

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

404 lines
13 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 },
{ 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::Vec4Dot, "Vec4Dot", "FVV" },
{ IROp::Vec4VfpuDot, "Vec4VfpuDot", "FVV" },
{ IROp::Vec4VfpuMultiDot, "Vec4VfpuMultiDot", "VMVc" },
{ IROp::Vec4Neg, "Vec4Neg", "VV" },
{ IROp::Vec4Abs, "Vec4Abs", "VV" },
// 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);
}