mirror of https://github.com/PCSX2/pcsx2.git
489 lines
13 KiB
C++
489 lines
13 KiB
C++
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: LGPL-3.0+
|
|
|
|
#include "microVU.h"
|
|
|
|
#include "common/AlignedMalloc.h"
|
|
#include "common/Perf.h"
|
|
#include "common/StringUtil.h"
|
|
|
|
//------------------------------------------------------------------
|
|
// Micro VU - Main Functions
|
|
//------------------------------------------------------------------
|
|
|
|
// Only run this once per VU! ;)
|
|
void mVUinit(microVU& mVU, uint vuIndex)
|
|
{
|
|
std::memset(&mVU.prog, 0, sizeof(mVU.prog));
|
|
|
|
mVU.index = vuIndex;
|
|
mVU.cop2 = 0;
|
|
mVU.vuMemSize = (mVU.index ? 0x4000 : 0x1000);
|
|
mVU.microMemSize = (mVU.index ? 0x4000 : 0x1000);
|
|
mVU.progSize = (mVU.index ? 0x4000 : 0x1000) / 4;
|
|
mVU.progMemMask = mVU.progSize-1;
|
|
mVU.cache = vuIndex ? SysMemory::GetVU1Rec() : SysMemory::GetVU0Rec();
|
|
mVU.prog.x86end = (vuIndex ? SysMemory::GetVU1RecEnd() : SysMemory::GetVU0RecEnd()) - (mVUcacheSafeZone * _1mb);
|
|
|
|
mVU.regAlloc.reset(new microRegAlloc(mVU.index));
|
|
}
|
|
|
|
// Resets Rec Data
|
|
void mVUreset(microVU& mVU, bool resetReserve)
|
|
{
|
|
if (THREAD_VU1)
|
|
{
|
|
DevCon.Warning("mVU Reset");
|
|
// If MTVU is toggled on during gameplay we need to flush the running VU1 program, else it gets in a mess
|
|
if (VU0.VI[REG_VPU_STAT].UL & 0x100)
|
|
{
|
|
CpuVU1->Execute(vu1RunCycles);
|
|
}
|
|
VU0.VI[REG_VPU_STAT].UL &= ~0x100;
|
|
}
|
|
|
|
xSetPtr(mVU.cache);
|
|
mVUdispatcherAB(mVU);
|
|
mVUdispatcherCD(mVU);
|
|
mVUGenerateWaitMTVU(mVU);
|
|
mVUGenerateCopyPipelineState(mVU);
|
|
mVUGenerateCompareState(mVU);
|
|
|
|
mVU.regs().nextBlockCycles = 0;
|
|
memset(&mVU.prog.lpState, 0, sizeof(mVU.prog.lpState));
|
|
mVU.profiler.Reset(mVU.index);
|
|
|
|
// Program Variables
|
|
mVU.prog.cleared = 1;
|
|
mVU.prog.isSame = -1;
|
|
mVU.prog.cur = NULL;
|
|
mVU.prog.total = 0;
|
|
mVU.prog.curFrame = 0;
|
|
|
|
// Setup Dynarec Cache Limits for Each Program
|
|
mVU.prog.x86start = xGetAlignedCallTarget();
|
|
mVU.prog.x86ptr = mVU.prog.x86start;
|
|
|
|
for (u32 i = 0; i < (mVU.progSize / 2); i++)
|
|
{
|
|
if (!mVU.prog.prog[i])
|
|
{
|
|
mVU.prog.prog[i] = new std::deque<microProgram*>();
|
|
continue;
|
|
}
|
|
std::deque<microProgram*>::iterator it(mVU.prog.prog[i]->begin());
|
|
for (; it != mVU.prog.prog[i]->end(); ++it)
|
|
{
|
|
mVUdeleteProg(mVU, it[0]);
|
|
}
|
|
mVU.prog.prog[i]->clear();
|
|
mVU.prog.quick[i].block = NULL;
|
|
mVU.prog.quick[i].prog = NULL;
|
|
}
|
|
}
|
|
|
|
// Free Allocated Resources
|
|
void mVUclose(microVU& mVU)
|
|
{
|
|
// Delete Programs and Block Managers
|
|
for (u32 i = 0; i < (mVU.progSize / 2); i++)
|
|
{
|
|
if (!mVU.prog.prog[i])
|
|
continue;
|
|
std::deque<microProgram*>::iterator it(mVU.prog.prog[i]->begin());
|
|
for (; it != mVU.prog.prog[i]->end(); ++it)
|
|
{
|
|
mVUdeleteProg(mVU, it[0]);
|
|
}
|
|
safe_delete(mVU.prog.prog[i]);
|
|
}
|
|
}
|
|
|
|
// Clears Block Data in specified range
|
|
__fi void mVUclear(mV, u32 addr, u32 size)
|
|
{
|
|
if (!mVU.prog.cleared)
|
|
{
|
|
mVU.prog.cleared = 1; // Next execution searches/creates a new microprogram
|
|
std::memset(&mVU.prog.lpState, 0, sizeof(mVU.prog.lpState)); // Clear pipeline state
|
|
for (u32 i = 0; i < (mVU.progSize / 2); i++)
|
|
{
|
|
mVU.prog.quick[i].block = NULL; // Clear current quick-reference block
|
|
mVU.prog.quick[i].prog = NULL; // Clear current quick-reference prog
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// Micro VU - Private Functions
|
|
//------------------------------------------------------------------
|
|
|
|
// Deletes a program
|
|
__ri void mVUdeleteProg(microVU& mVU, microProgram*& prog)
|
|
{
|
|
for (u32 i = 0; i < (mVU.progSize / 2); i++)
|
|
{
|
|
safe_delete(prog->block[i]);
|
|
}
|
|
safe_delete(prog->ranges);
|
|
safe_aligned_free(prog);
|
|
}
|
|
|
|
// Creates a new Micro Program
|
|
__ri microProgram* mVUcreateProg(microVU& mVU, int startPC)
|
|
{
|
|
microProgram* prog = (microProgram*)_aligned_malloc(sizeof(microProgram), 64);
|
|
memset(prog, 0, sizeof(microProgram));
|
|
prog->idx = mVU.prog.total++;
|
|
prog->ranges = new std::deque<microRange>();
|
|
prog->startPC = startPC;
|
|
if(doWholeProgCompare)
|
|
mVUcacheProg(mVU, *prog); // Cache Micro Program
|
|
double cacheSize = (double)((uptr)mVU.prog.x86end - (uptr)mVU.prog.x86start);
|
|
double cacheUsed = ((double)((uptr)mVU.prog.x86ptr - (uptr)mVU.prog.x86start)) / (double)_1mb;
|
|
double cachePerc = ((double)((uptr)mVU.prog.x86ptr - (uptr)mVU.prog.x86start)) / cacheSize * 100;
|
|
ConsoleColors c = mVU.index ? Color_Orange : Color_Magenta;
|
|
DevCon.WriteLn(c, "microVU%d: Cached Prog = [%03d] [PC=%04x] [List=%02d] (Cache=%3.3f%%) [%3.1fmb]",
|
|
mVU.index, prog->idx, startPC * 8, mVU.prog.prog[startPC]->size() + 1, cachePerc, cacheUsed);
|
|
return prog;
|
|
}
|
|
|
|
// Caches Micro Program
|
|
__ri void mVUcacheProg(microVU& mVU, microProgram& prog)
|
|
{
|
|
if (!doWholeProgCompare)
|
|
{
|
|
auto cmpOffset = [&](void* x) { return (u8*)x + mVUrange.start; };
|
|
memcpy(cmpOffset(prog.data), cmpOffset(mVU.regs().Micro), (mVUrange.end - mVUrange.start));
|
|
}
|
|
else
|
|
{
|
|
if (!mVU.index)
|
|
memcpy(prog.data, mVU.regs().Micro, 0x1000);
|
|
else
|
|
memcpy(prog.data, mVU.regs().Micro, 0x4000);
|
|
}
|
|
mVUdumpProg(mVU, prog);
|
|
}
|
|
|
|
// Generate Hash for partial program based on compiled ranges...
|
|
u64 mVUrangesHash(microVU& mVU, microProgram& prog)
|
|
{
|
|
union
|
|
{
|
|
u64 v64;
|
|
u32 v32[2];
|
|
} hash = {0};
|
|
|
|
std::deque<microRange>::const_iterator it(prog.ranges->begin());
|
|
for (; it != prog.ranges->end(); ++it)
|
|
{
|
|
if ((it[0].start < 0) || (it[0].end < 0))
|
|
{
|
|
DevCon.Error("microVU%d: Negative Range![%d][%d]", mVU.index, it[0].start, it[0].end);
|
|
}
|
|
for (int i = it[0].start / 4; i < it[0].end / 4; i++)
|
|
{
|
|
hash.v32[0] -= prog.data[i];
|
|
hash.v32[1] ^= prog.data[i];
|
|
}
|
|
}
|
|
return hash.v64;
|
|
}
|
|
|
|
// Prints the ratio of unique programs to total programs
|
|
void mVUprintUniqueRatio(microVU& mVU)
|
|
{
|
|
std::vector<u64> v;
|
|
for (u32 pc = 0; pc < mProgSize / 2; pc++)
|
|
{
|
|
microProgramList* list = mVU.prog.prog[pc];
|
|
if (!list)
|
|
continue;
|
|
std::deque<microProgram*>::iterator it(list->begin());
|
|
for (; it != list->end(); ++it)
|
|
{
|
|
v.push_back(mVUrangesHash(mVU, *it[0]));
|
|
}
|
|
}
|
|
u32 total = v.size();
|
|
sortVector(v);
|
|
makeUnique(v);
|
|
if (!total)
|
|
return;
|
|
DevCon.WriteLn("%d / %d [%3.1f%%]", v.size(), total, 100. - (double)v.size() / (double)total * 100.);
|
|
}
|
|
|
|
// Compare Cached microProgram to mVU.regs().Micro
|
|
__fi bool mVUcmpProg(microVU& mVU, microProgram& prog)
|
|
{
|
|
if (doWholeProgCompare)
|
|
{
|
|
if (memcmp((u8*)prog.data, mVU.regs().Micro, mVU.microMemSize))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
for (const auto& range : *prog.ranges)
|
|
{
|
|
#if defined(PCSX2_DEVBUILD) || defined(_DEBUG)
|
|
if ((range.start < 0) || (range.end < 0))
|
|
DevCon.Error("microVU%d: Negative Range![%d][%d]", mVU.index, range.start, range.end);
|
|
#endif
|
|
auto cmpOffset = [&](void* x) { return (u8*)x + range.start; };
|
|
|
|
if (memcmp(cmpOffset(prog.data), cmpOffset(mVU.regs().Micro), (range.end - range.start)))
|
|
return false;
|
|
}
|
|
}
|
|
mVU.prog.cleared = 0;
|
|
mVU.prog.cur = &prog;
|
|
mVU.prog.isSame = doWholeProgCompare ? 1 : -1;
|
|
return true;
|
|
}
|
|
|
|
// Searches for Cached Micro Program and sets prog.cur to it (returns entry-point to program)
|
|
_mVUt __fi void* mVUsearchProg(u32 startPC, uptr pState)
|
|
{
|
|
microVU& mVU = mVUx;
|
|
microProgramQuick& quick = mVU.prog.quick[mVU.regs().start_pc / 8];
|
|
microProgramList* list = mVU.prog.prog [mVU.regs().start_pc / 8];
|
|
|
|
if (!quick.prog) // If null, we need to search for new program
|
|
{
|
|
std::deque<microProgram*>::iterator it(list->begin());
|
|
for (; it != list->end(); ++it)
|
|
{
|
|
bool b = mVUcmpProg(mVU, *it[0]);
|
|
|
|
if (b)
|
|
{
|
|
quick.block = it[0]->block[startPC / 8];
|
|
quick.prog = it[0];
|
|
list->erase(it);
|
|
list->push_front(quick.prog);
|
|
|
|
// Sanity check, in case for some reason the program compilation aborted half way through (JALR for example)
|
|
if (quick.block == nullptr)
|
|
{
|
|
void* entryPoint = mVUblockFetch(mVU, startPC, pState);
|
|
return entryPoint;
|
|
}
|
|
return mVUentryGet(mVU, quick.block, startPC, pState);
|
|
}
|
|
}
|
|
|
|
// If cleared and program not found, make a new program instance
|
|
mVU.prog.cleared = 0;
|
|
mVU.prog.isSame = 1;
|
|
mVU.prog.cur = mVUcreateProg(mVU, mVU.regs().start_pc/8);
|
|
void* entryPoint = mVUblockFetch(mVU, startPC, pState);
|
|
quick.block = mVU.prog.cur->block[startPC/8];
|
|
quick.prog = mVU.prog.cur;
|
|
list->push_front(mVU.prog.cur);
|
|
//mVUprintUniqueRatio(mVU);
|
|
return entryPoint;
|
|
}
|
|
|
|
// If list.quick, then we've already found and recompiled the program ;)
|
|
mVU.prog.isSame = -1;
|
|
mVU.prog.cur = quick.prog;
|
|
// Because the VU's can now run in sections and not whole programs at once
|
|
// we need to set the current block so it gets the right program back
|
|
quick.block = mVU.prog.cur->block[startPC / 8];
|
|
|
|
// Sanity check, in case for some reason the program compilation aborted half way through
|
|
if (quick.block == nullptr)
|
|
{
|
|
void* entryPoint = mVUblockFetch(mVU, startPC, pState);
|
|
return entryPoint;
|
|
}
|
|
return mVUentryGet(mVU, quick.block, startPC, pState);
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// recMicroVU0 / recMicroVU1
|
|
//------------------------------------------------------------------
|
|
|
|
recMicroVU0 CpuMicroVU0;
|
|
recMicroVU1 CpuMicroVU1;
|
|
|
|
recMicroVU0::recMicroVU0() { m_Idx = 0; IsInterpreter = false; }
|
|
recMicroVU1::recMicroVU1() { m_Idx = 1; IsInterpreter = false; }
|
|
|
|
void recMicroVU0::Reserve()
|
|
{
|
|
mVUinit(microVU0, 0);
|
|
}
|
|
void recMicroVU1::Reserve()
|
|
{
|
|
mVUinit(microVU1, 1);
|
|
vu1Thread.Open();
|
|
}
|
|
|
|
void recMicroVU0::Shutdown()
|
|
{
|
|
mVUclose(microVU0);
|
|
}
|
|
void recMicroVU1::Shutdown()
|
|
{
|
|
if (vu1Thread.IsOpen())
|
|
vu1Thread.WaitVU();
|
|
mVUclose(microVU1);
|
|
}
|
|
|
|
void recMicroVU0::Reset()
|
|
{
|
|
mVUreset(microVU0, true);
|
|
}
|
|
|
|
void recMicroVU0::Step()
|
|
{
|
|
}
|
|
|
|
void recMicroVU1::Reset()
|
|
{
|
|
vu1Thread.WaitVU();
|
|
vu1Thread.Get_MTVUChanges();
|
|
mVUreset(microVU1, true);
|
|
}
|
|
|
|
void recMicroVU0::SetStartPC(u32 startPC)
|
|
{
|
|
VU0.start_pc = startPC;
|
|
}
|
|
|
|
void recMicroVU0::Execute(u32 cycles)
|
|
{
|
|
VU0.flags &= ~VUFLAG_MFLAGSET;
|
|
|
|
if (!(VU0.VI[REG_VPU_STAT].UL & 1))
|
|
return;
|
|
VU0.VI[REG_TPC].UL <<= 3;
|
|
|
|
((mVUrecCall)microVU0.startFunct)(VU0.VI[REG_TPC].UL, cycles);
|
|
VU0.VI[REG_TPC].UL >>= 3;
|
|
if (microVU0.regs().flags & 0x4)
|
|
{
|
|
microVU0.regs().flags &= ~0x4;
|
|
hwIntcIrq(6);
|
|
}
|
|
}
|
|
|
|
void recMicroVU1::SetStartPC(u32 startPC)
|
|
{
|
|
VU1.start_pc = startPC;
|
|
}
|
|
|
|
void recMicroVU1::Step()
|
|
{
|
|
}
|
|
|
|
void recMicroVU1::Execute(u32 cycles)
|
|
{
|
|
if (!THREAD_VU1)
|
|
{
|
|
if (!(VU0.VI[REG_VPU_STAT].UL & 0x100))
|
|
return;
|
|
}
|
|
VU1.VI[REG_TPC].UL <<= 3;
|
|
((mVUrecCall)microVU1.startFunct)(VU1.VI[REG_TPC].UL, cycles);
|
|
VU1.VI[REG_TPC].UL >>= 3;
|
|
if (microVU1.regs().flags & 0x4 && !THREAD_VU1)
|
|
{
|
|
microVU1.regs().flags &= ~0x4;
|
|
hwIntcIrq(7);
|
|
}
|
|
}
|
|
|
|
void recMicroVU0::Clear(u32 addr, u32 size)
|
|
{
|
|
mVUclear(microVU0, addr, size);
|
|
}
|
|
void recMicroVU1::Clear(u32 addr, u32 size)
|
|
{
|
|
mVUclear(microVU1, addr, size);
|
|
}
|
|
|
|
void recMicroVU1::ResumeXGkick()
|
|
{
|
|
if (!(VU0.VI[REG_VPU_STAT].UL & 0x100))
|
|
return;
|
|
((mVUrecCallXG)microVU1.startFunctXG)();
|
|
}
|
|
|
|
bool SaveStateBase::vuJITFreeze()
|
|
{
|
|
if (IsSaving())
|
|
vu1Thread.WaitVU();
|
|
|
|
Freeze(microVU0.prog.lpState);
|
|
Freeze(microVU1.prog.lpState);
|
|
return IsOkay();
|
|
}
|
|
|
|
#if 0
|
|
|
|
#include <zlib.h>
|
|
|
|
void DumpVUState(u32 n, u32 pc)
|
|
{
|
|
const VURegs& r = vuRegs[n];
|
|
const microVU& mVU = (n == 0) ? microVU0 : microVU1;
|
|
static FILE* fp = nullptr;
|
|
static bool fp_opened = false;
|
|
static u32 counter = 0;
|
|
|
|
u32 first = pc >> 31;
|
|
pc &= 0x7FFFFFFFu;
|
|
if (first)
|
|
counter++;
|
|
|
|
#if 0
|
|
if (counter == 184639 && pc == 0x0D70)
|
|
__debugbreak();
|
|
#endif
|
|
|
|
if (counter < 0)
|
|
return;
|
|
|
|
if (!fp_opened)
|
|
{
|
|
fp = std::fopen("C:\\Dumps\\comp\\vulog.txt", "wb");
|
|
fp_opened = true;
|
|
}
|
|
if (fp)
|
|
{
|
|
const microVU& m = (n == 0) ? microVU0 : microVU1;
|
|
fprintf(fp, "%08d VU%u SPC:%04X xPC:%04X BRANCH:%04X VIBACKUP:%04X", counter, n, r.start_pc, pc, mVU.branch, mVU.VIbackup);
|
|
#if 1
|
|
//fprintf(fp, " MEM:%08X", crc32(0, (Bytef*)r.Mem, (n == 0) ? VU0_MEMSIZE : VU1_MEMSIZE));
|
|
fprintf(fp, " MAC %08X %08X %08X %08X [%08X %08X %08X %08X]", r.micro_macflags[3], r.micro_macflags[2], r.micro_macflags[1], r.micro_macflags[0], m.macFlag[3], m.macFlag[2], m.macFlag[1], m.macFlag[0]);
|
|
fprintf(fp, " CLIP %08X %08X %08X %08X [%08X %08X %08X %08X]", r.micro_clipflags[3], r.micro_clipflags[2], r.micro_clipflags[1], r.micro_clipflags[0], m.clipFlag[3], m.clipFlag[2], m.clipFlag[1], m.clipFlag[0]);
|
|
fprintf(fp, " STATUS %08X %08X %08X %08X [%08X %08X %08X %08X]", r.micro_statusflags[3], r.micro_statusflags[2], r.micro_statusflags[1], r.micro_statusflags[0], m.statFlag[3], m.statFlag[2], m.statFlag[1], m.statFlag[0]);
|
|
|
|
for (u32 i = 0; i < 32; i++)
|
|
{
|
|
const VECTOR& v = r.VF[i];
|
|
fprintf(fp, " VF%u: %08X%08X%08X%08X (%f,%f,%f,%f)", i, v.UL[3], v.UL[2], v.UL[1], v.UL[0], v.F[3], v.F[2], v.F[1], v.F[0]);
|
|
}
|
|
|
|
for (u32 i = 0; i < 32; i++)
|
|
{
|
|
const REG_VI& v = r.VI[i];
|
|
fprintf(fp, " VI%u: %08X", i, v.UL);
|
|
}
|
|
|
|
fprintf(fp, " ACC: %08X%08X%08X%08X (%f,%f,%f,%f)", r.ACC.UL[3], r.ACC.UL[2], r.ACC.UL[1], r.ACC.UL[0],
|
|
r.ACC.F[3], r.ACC.F[2], r.ACC.F[1], r.ACC.F[0]);
|
|
fprintf(fp, " Q: %08X (%f)", r.q.UL, r.q.F);
|
|
fprintf(fp, " P: %08X (%f)\n", r.p.UL, r.p.F);
|
|
#else
|
|
fprintf(fp, " REG:%08X\n", crc32(0, (Bytef*)&r, offsetof(VURegs, idx)));
|
|
#endif
|
|
//fflush(fp);
|
|
}
|
|
}
|
|
|
|
#endif
|