mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 08:20:30 +02:00
Merge pull request #22576 from hrydgard/cpu-generic-fixes
CPU equivalence fixes, headless vertex JIT, and Mac W^X switching per thread
This commit is contained in:
15 files changed
+208
-67
No files matched your search
+2
-2
@@ -99,8 +99,8 @@ public:
|
||||
// If not WX Exclusive, no need to call ProtectMemoryPages because we never change the protection from RWX.
|
||||
PoisonMemory(offset);
|
||||
ResetCodePtr(offset);
|
||||
if (PlatformIsWXExclusive() && offset != 0) {
|
||||
// Need to re-protect the part we didn't clear.
|
||||
if (PlatformIsWXExclusive()) {
|
||||
// Need to re-protect the part we didn't clear (with MAP_JIT, this thread's view of it all).
|
||||
ProtectMemoryPages(region, offset, MEM_PROT_READ | MEM_PROT_EXEC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
#include <mach/vm_param.h>
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64)
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <pthread.h>
|
||||
#define USE_MAP_JIT
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -183,7 +191,43 @@ void *AllocateExecutableMemory(size_t size) {
|
||||
|
||||
#else // Non-Windows platforms
|
||||
|
||||
#ifdef USE_MAP_JIT
|
||||
// With MAP_JIT, a thread switches all of its view of the JIT memory between writable and executable
|
||||
// with pthread_jit_write_protect_np(), far cheaper than an mprotect() for every block compiled and
|
||||
// linked. A hardened runtime without the JIT entitlement refuses the mapping: then mprotect() it is.
|
||||
static std::mutex g_mapJitLock;
|
||||
static int g_mapJitState; // 0 untried, 1 in use, -1 refused.
|
||||
static std::vector<std::pair<uintptr_t, uintptr_t>> g_mapJitRanges;
|
||||
|
||||
static bool IsMapJitMemory(const void *ptr) {
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
for (const auto &range : g_mapJitRanges) {
|
||||
if ((uintptr_t)ptr >= range.first && (uintptr_t)ptr < range.second)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void *AllocateExecutableMemory(size_t size) {
|
||||
#ifdef USE_MAP_JIT
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
if (g_mapJitState >= 0) {
|
||||
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
|
||||
if (ptr != MAP_FAILED) {
|
||||
g_mapJitState = 1;
|
||||
g_mapJitRanges.emplace_back((uintptr_t)ptr, (uintptr_t)ptr + size);
|
||||
return ptr;
|
||||
}
|
||||
if (g_mapJitState == 0) {
|
||||
WARN_LOG(Log::MemMap, "MAP_JIT refused (errno=%d), using mprotect", errno);
|
||||
g_mapJitState = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static char *map_hint = nullptr;
|
||||
|
||||
#if PPSSPP_ARCH(AMD64)
|
||||
@@ -339,6 +383,19 @@ void FreeMemoryPages(void *ptr, size_t size) {
|
||||
}
|
||||
|
||||
void FreeExecutableMemory(void *ptr, size_t size) {
|
||||
#ifdef USE_MAP_JIT
|
||||
if (IsMapJitMemory(ptr)) {
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
for (size_t i = 0; i < g_mapJitRanges.size(); i++) {
|
||||
if (g_mapJitRanges[i].first == (uintptr_t)ptr) {
|
||||
g_mapJitRanges.erase(g_mapJitRanges.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Back to executable, which the protection change before freeing undid.
|
||||
pthread_jit_write_protect_np(1);
|
||||
}
|
||||
#endif
|
||||
FreeMemoryPages(ptr, size);
|
||||
}
|
||||
|
||||
@@ -374,6 +431,16 @@ bool ProtectMemoryPages(const void* ptr, size_t size, uint32_t memProtFlags) {
|
||||
_assert_msg_(false, "Bad memory protect flags %d: W^X is in effect, can't both write and exec", memProtFlags);
|
||||
}
|
||||
}
|
||||
#ifdef USE_MAP_JIT
|
||||
if (IsMapJitMemory(ptr)) {
|
||||
_assert_msg_((memProtFlags & (MEM_PROT_WRITE | MEM_PROT_EXEC)) != 0, "MAP_JIT memory is writable or executable");
|
||||
pthread_jit_write_protect_np((memProtFlags & MEM_PROT_WRITE) == 0);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
// Note - VirtualProtect will affect the full pages containing the requested range.
|
||||
// mprotect does not seem to, at least not on Android unless I made a mistake somewhere, so we manually round.
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -208,6 +208,7 @@ static const CommandLineParam g_autoParams[] = {
|
||||
{POFF(replayEnd), CmdParamType::Int, "replay-end", '\0', "When replaying a GE frame dump, only draw primitives up to this one (1-based)", CmdLineMode::Headless},
|
||||
{POFF(dumpCompat), CmdParamType::Bool, "dump-compat", '\0', "Apply the game's compat.ini settings when replaying a GE frame dump (off by default)", CmdLineMode::Headless},
|
||||
{POFF(hardwareTransform), CmdParamType::Bool, "hardware-transform", '\0', "Transform vertices on the GPU (default true); false transforms them on the CPU", CmdLineMode::Headless},
|
||||
{POFF(vertexJit), CmdParamType::Bool, "vertex-jit", '\0', "Decode vertices with the JIT where the host can (default; =false for the C++ decoder)", CmdLineMode::Headless},
|
||||
{POFF(disableGPUFeatures), CmdParamType::String, "disable-gpu-features", '\0', "Act as if the GPU lacked these, to test the fallbacks: comma-separated clip-distance, cull-distance, depth-clamp, framebuffer-fetch, dual-source-blend", CmdLineMode::Headless},
|
||||
{POFF(depthFilenameSave), CmdParamType::String, "depth-save", '\0', "Save the depth buffer to FILE along with the screenshot (u32 width, u32 height, u16 depths; float depths if FILE ends in .f32)", CmdLineMode::Headless},
|
||||
{POFF(screenshotRenderTarget), CmdParamType::Bool, "screenshot-render-target", '\0', "Take screenshots of the current render target instead of the displayed framebuffer", CmdLineMode::Headless},
|
||||
|
||||
@@ -195,6 +195,7 @@ struct CommandLineOptions {
|
||||
// Headless: apply the game's compatibility settings when replaying a GE frame dump.
|
||||
std::optional<bool> dumpCompat;
|
||||
std::optional<bool> hardwareTransform;
|
||||
std::optional<bool> vertexJit;
|
||||
// Headless: comma-separated clip-distance, cull-distance, depth-clamp, framebuffer-fetch, dual-source-blend.
|
||||
std::optional<std::string> disableGPUFeatures;
|
||||
std::optional<bool> screenshotRenderTarget;
|
||||
|
||||
@@ -135,7 +135,8 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink,
|
||||
js.downcountAmount += MIPSGetInstructionCycleEstimate(branchInfo.delaySlotOp);
|
||||
|
||||
MIPSGPReg lhs = rs;
|
||||
if (!branchInfo.delaySlotIsNice) { // if likely, we don't need this
|
||||
// The link happens after rs is read, which matters for ra (cpu/cpu_alu/branch_link_ra).
|
||||
if (!branchInfo.delaySlotIsNice || (andLink && rs == MIPS_REG_RA)) {
|
||||
ir.Write(IROp::Mov, IRTEMP_LHS, rs);
|
||||
lhs = (MIPSGPReg)IRTEMP_LHS;
|
||||
}
|
||||
|
||||
+78
-43
@@ -688,14 +688,18 @@ namespace MIPSComp {
|
||||
// d[N,M] = CONST[N,M]
|
||||
|
||||
int vd = _VD;
|
||||
// (A 1x1 matrix has no transposed form, the bit then picks a different register.)
|
||||
if (sz != M_1x1 && IsMatrixTransposed(vd)) {
|
||||
// All outputs are transpositionally symmetric, so should be fine.
|
||||
vd = TransposeMatrixReg(vd);
|
||||
}
|
||||
|
||||
u8 dregs[16];
|
||||
u8 dregs[16]{};
|
||||
GetMatrixRegs(dregs, sz, vd);
|
||||
// The constants are symmetric, so a transposed matrix comes out as Vec4s taken in transposed
|
||||
// order. (Flipping the transposed bit instead names other registers for a matrix with an
|
||||
// offset, which wraps around.)
|
||||
if (!IsMatrixVec4(sz, dregs)) {
|
||||
u8 dt[16];
|
||||
for (int k = 0; k < 16; k++)
|
||||
dt[k] = dregs[(k % 4) * 4 + k / 4];
|
||||
if (IsMatrixVec4(sz, dt))
|
||||
memcpy(dregs, dt, sizeof(dt));
|
||||
}
|
||||
if (!IsMatrixVec4(sz, dregs)) {
|
||||
// 3x3 is decently common. It expands a lot, but let's set each.
|
||||
// TODO: It might be worth using Vec4Blend for 3x3 to mask w.
|
||||
@@ -729,14 +733,8 @@ namespace MIPSComp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not really about trying here, it will work if enabled.
|
||||
VectorSize vsz = GetVectorSize(sz);
|
||||
u8 vecs[4];
|
||||
GetMatrixColumns(vd, sz, vecs);
|
||||
// Each Vec4 of dregs in turn (lane i of the i-th is the diagonal).
|
||||
for (int i = 0; i < 4; i++) {
|
||||
u8 vec[4];
|
||||
GetVectorRegs(vec, vsz, vecs[i]);
|
||||
// As they are columns, they will be nicely consecutive.
|
||||
Vec4Init init;
|
||||
switch ((op >> 16) & 0xF) {
|
||||
case 3:
|
||||
@@ -752,7 +750,7 @@ namespace MIPSComp {
|
||||
INVALIDOP;
|
||||
return;
|
||||
}
|
||||
ir.Write(IROp::Vec4Init, vec[0], (int)init);
|
||||
ir.Write(IROp::Vec4Init, dregs[i * 4], (int)init);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1332,16 +1330,22 @@ namespace MIPSComp {
|
||||
|
||||
uint8_t imm = (op >> 16) & 0x1f;
|
||||
|
||||
u8 sregs[4], dregs[4];
|
||||
u8 sregs[4], dregs[4], outregs[4];
|
||||
GetVectorRegsPrefixS(sregs, sz, _VS);
|
||||
GetVectorRegsPrefixD(dregs, sz, _VD);
|
||||
// Through temps if a lane would be written before a later one reads it.
|
||||
const bool direct = IsOverlapSafeAllowS(n, dregs, n, sregs);
|
||||
for (int i = 0; i < n; i++)
|
||||
outregs[i] = direct ? dregs[i] : IRVTEMP_0 + i;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (imm == 0)
|
||||
ir.Write(IROp::FCvtSW, dregs[i], sregs[i]);
|
||||
ir.Write(IROp::FCvtSW, outregs[i], sregs[i]);
|
||||
else
|
||||
ir.Write(IROp::FCvtScaledSW, dregs[i], sregs[i], imm);
|
||||
ir.Write(IROp::FCvtScaledSW, outregs[i], sregs[i], imm);
|
||||
}
|
||||
for (int i = 0; i < n && !direct; i++)
|
||||
ir.Write(IROp::FMov, dregs[i], outregs[i]);
|
||||
ApplyPrefixD(dregs, sz, _VD);
|
||||
}
|
||||
|
||||
@@ -1388,9 +1392,13 @@ namespace MIPSComp {
|
||||
|
||||
uint8_t imm = (op >> 16) & 0x1f;
|
||||
|
||||
u8 sregs[4], dregs[4];
|
||||
u8 sregs[4], dregs[4], outregs[4];
|
||||
GetVectorRegsPrefixS(sregs, sz, _VS);
|
||||
GetVectorRegsPrefixD(dregs, sz, _VD);
|
||||
// Through temps if a lane would be written before a later one reads it.
|
||||
const bool direct = IsOverlapSafeAllowS(n, dregs, n, sregs);
|
||||
for (int i = 0; i < n; i++)
|
||||
outregs[i] = direct ? dregs[i] : IRVTEMP_0 + i;
|
||||
|
||||
// Same values as FCR31.
|
||||
uint8_t rmode = (op >> 21) & 3;
|
||||
@@ -1399,24 +1407,24 @@ namespace MIPSComp {
|
||||
|
||||
if (imm != 0) {
|
||||
for (int i = 0; i < n; i++)
|
||||
ir.Write(IROp::FCvtScaledWS, dregs[i], sregs[i], imm | (rmode << 6));
|
||||
ir.Write(IROp::FCvtScaledWS, outregs[i], sregs[i], imm | (rmode << 6));
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
switch (IRRoundMode(rmode)) {
|
||||
case IRRoundMode::RINT_0: // vf2in
|
||||
ir.Write(IROp::FRound, dregs[i], sregs[i]);
|
||||
ir.Write(IROp::FRound, outregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case IRRoundMode::CAST_1: // vf2iz
|
||||
ir.Write(IROp::FTrunc, dregs[i], sregs[i]);
|
||||
ir.Write(IROp::FTrunc, outregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case IRRoundMode::CEIL_2: // vf2iu
|
||||
ir.Write(IROp::FCeil, dregs[i], sregs[i]);
|
||||
ir.Write(IROp::FCeil, outregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case IRRoundMode::FLOOR_3: // vf2id
|
||||
ir.Write(IROp::FFloor, dregs[i], sregs[i]);
|
||||
ir.Write(IROp::FFloor, outregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1424,6 +1432,8 @@ namespace MIPSComp {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n && !direct; i++)
|
||||
ir.Write(IROp::FMov, dregs[i], outregs[i]);
|
||||
|
||||
ApplyPrefixDMask(dregs, sz, _VD);
|
||||
}
|
||||
@@ -1534,7 +1544,7 @@ namespace MIPSComp {
|
||||
if (GetVFPUCtrlMask(imm, &mask)) {
|
||||
u32 setBits = GetVFPUCtrlSetBits(imm);
|
||||
if (mask != 0xFFFFFFFF || setBits != 0) {
|
||||
ir.Write(IROp::FMovToGPR, IRTEMP_0, vfpuBase + voffset[imm]);
|
||||
ir.Write(IROp::FMovToGPR, IRTEMP_0, vfpuBase + voffset[vs]);
|
||||
ir.Write(IROp::AndConst, IRTEMP_0, IRTEMP_0, 0, mask);
|
||||
if (setBits != 0)
|
||||
ir.Write(IROp::OrConst, IRTEMP_0, IRTEMP_0, 0, setBits);
|
||||
@@ -1637,18 +1647,32 @@ namespace MIPSComp {
|
||||
MatrixSize sz = GetMtxSize(op);
|
||||
int n = GetMatrixSide(sz);
|
||||
|
||||
// The entire matrix is scaled equally, so transpose doesn't matter. Let's normalize.
|
||||
// (A 1x1 matrix has no transposed form, the bit then picks a different register.)
|
||||
if (n > 1 && IsMatrixTransposed(vs) && IsMatrixTransposed(vd)) {
|
||||
vs = TransposeMatrixReg(vs);
|
||||
vd = TransposeMatrixReg(vd);
|
||||
}
|
||||
|
||||
u8 sregs[16], dregs[16], tregs[1];
|
||||
u8 sregs[16]{}, dregs[16]{}, tregs[1];
|
||||
GetMatrixRegs(sregs, sz, vs);
|
||||
GetMatrixRegs(dregs, sz, vd);
|
||||
GetVectorRegs(tregs, V_Single, vt);
|
||||
|
||||
// Every element is scaled the same, so the elements can go in any order, as long as each
|
||||
// source keeps its destination. Transposed matrices come out as Vec4s when both are taken
|
||||
// in transposed order. (Flipping the transposed bit of the registers instead would name other
|
||||
// registers for a matrix with an offset, which wraps around.)
|
||||
if (!IsMatrixVec4(sz, sregs) || !IsMatrixVec4(sz, dregs)) {
|
||||
u8 st[16], dt[16];
|
||||
for (int k = 0; k < 16; k++) {
|
||||
st[k] = sregs[(k % 4) * 4 + k / 4];
|
||||
dt[k] = dregs[(k % 4) * 4 + k / 4];
|
||||
}
|
||||
if (IsMatrixVec4(sz, st) && IsMatrixVec4(sz, dt)) {
|
||||
memcpy(sregs, st, sizeof(st));
|
||||
memcpy(dregs, dt, sizeof(dt));
|
||||
}
|
||||
}
|
||||
|
||||
// Either way, a source that partly overlaps the destination would be read after it's written.
|
||||
if (vs != vd && GetMatrixOverlap(vs, vd, sz) != OVERLAP_NONE) {
|
||||
DISABLE;
|
||||
}
|
||||
|
||||
if (GetMtx(vt) != GetMtx(vd) && IsMatrixVec4(sz, sregs) && IsMatrixVec4(sz, dregs)) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
ir.Write(IROp::Vec4Scale, dregs[i * 4], sregs[i * 4], tregs[0]);
|
||||
@@ -1656,11 +1680,7 @@ namespace MIPSComp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Element by element otherwise, which works for any size and transposition. A source that
|
||||
// partly overlaps the destination would be read after it's written, though.
|
||||
if (vs != vd && GetMatrixOverlap(vs, vd, sz) != OVERLAP_NONE) {
|
||||
DISABLE;
|
||||
}
|
||||
// Element by element otherwise, which works for any size and transposition.
|
||||
// The scale can be part of the destination, so keep a copy.
|
||||
ir.Write(IROp::FMov, IRVTEMP_0, tregs[0]);
|
||||
for (int a = 0; a < n; a++) {
|
||||
@@ -2183,9 +2203,15 @@ namespace MIPSComp {
|
||||
|
||||
VectorSize sz = GetVecSize(op);
|
||||
VectorSize outsize;
|
||||
// vc2i and vuc2i read one lane, the first of the source at the size encoded, which decides
|
||||
// which register that is.
|
||||
int sourceReg = -1;
|
||||
if (bits == 8) {
|
||||
outsize = V_Quad;
|
||||
sz = V_Single; // For some reason, sz is set to Quad in this case though the outsize is Single.
|
||||
u8 full[4];
|
||||
GetVectorRegs(full, sz, _VS);
|
||||
sourceReg = full[0];
|
||||
sz = V_Single;
|
||||
} else {
|
||||
switch (sz) {
|
||||
case V_Single:
|
||||
@@ -2201,6 +2227,8 @@ namespace MIPSComp {
|
||||
|
||||
u8 sregs[2], dregs[4], tempregs[4], srcregs[2];
|
||||
GetVectorRegsPrefixS(sregs, sz, _VS);
|
||||
if (sourceReg >= 0)
|
||||
sregs[0] = sourceReg;
|
||||
GetVectorRegsPrefixD(dregs, outsize, _VD);
|
||||
memcpy(tempregs, dregs, sizeof(dregs));
|
||||
memcpy(srcregs, sregs, sizeof(sregs));
|
||||
@@ -2455,6 +2483,14 @@ namespace MIPSComp {
|
||||
DISABLE;
|
||||
}
|
||||
}
|
||||
// A D prefix that saturates puts the result in temps first. A lane that isn't moved keeps
|
||||
// d, so they start as d.
|
||||
u8 realregs[4];
|
||||
GetVectorRegs(realregs, sz, _VD);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (dregs[i] != realregs[i])
|
||||
ir.Write(IROp::FMov, dregs[i], realregs[i]);
|
||||
}
|
||||
// 7 tests a CC bit that is always clear (cpu/vfpu/undefined).
|
||||
if (imm3 != 6) {
|
||||
// Test one bit of CC. This bit decides whether none or all subregisters are copied.
|
||||
@@ -2493,13 +2529,12 @@ namespace MIPSComp {
|
||||
// Vector half-float immediate
|
||||
// d[0] = float(imm)
|
||||
|
||||
FP16 half;
|
||||
half.u = op & 0xFFFF;
|
||||
FP32 fval = half_to_float_fast5(half);
|
||||
// Converted like vh2f: denormal halves are zero (cpu/vfpu/vfim).
|
||||
const float value = vfpu_h2f_float((u16)(op & 0xFFFF));
|
||||
|
||||
u8 dreg;
|
||||
GetVectorRegsPrefixD(&dreg, V_Single, _VT);
|
||||
ir.WriteSetConstantFloat(dreg, fval.f);
|
||||
ir.WriteSetConstantFloat(dreg, value);
|
||||
ApplyPrefixD(&dreg, V_Single, _VT);
|
||||
}
|
||||
|
||||
|
||||
@@ -1157,6 +1157,15 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) {
|
||||
checkMismatch(inst.src3, inst.m.types[0]);
|
||||
|
||||
bool cannotReplace = !readsDirectly || lenMismatch;
|
||||
// Nor where a multi-lane result would cover the source it reads instead, which the
|
||||
// backends don't expect (the copy was what kept them apart).
|
||||
IRReg destRegs[4];
|
||||
const int numDest = IRDestFPRs(inst, destRegs);
|
||||
if (numDest > 1 && check.srcReg >= destRegs[0] + 32 && check.srcReg < destRegs[0] + 32 + numDest)
|
||||
cannotReplace = true;
|
||||
// Nor where it's read as src3 that's also the destination, which renaming would move.
|
||||
if ((inst.m.flags & IRFLAG_SRC3DST) != 0 && inst.m.types[0] == 'F' && inst.src3 + 32 == check.reg)
|
||||
cannotReplace = true;
|
||||
if (!cannotReplace && check.srcReg >= 32 && lastWrittenTo[check.srcReg] < check.index) {
|
||||
// This is probably not worth doing unless we can get rid of a temp.
|
||||
if (!check.readByExit) {
|
||||
@@ -1167,7 +1176,7 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) {
|
||||
inst.src1 = srcReg;
|
||||
if (inst.m.types[2] == 'F' && inst.src2 == reg)
|
||||
inst.src2 = srcReg;
|
||||
if ((inst.m.flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && inst.m.types[0] == 'F' && inst.src3 == reg)
|
||||
if ((inst.m.flags & IRFLAG_SRC3) != 0 && inst.m.types[0] == 'F' && inst.src3 == reg)
|
||||
inst.src3 = srcReg;
|
||||
|
||||
// If this also writes the reg, the check ends here. A full overwrite leaves the
|
||||
@@ -2322,7 +2331,9 @@ bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions
|
||||
known.erase(std::remove_if(known.begin(), known.end(), [&](const Known &k) {
|
||||
if (k.base != inst.src1 || !plainStore)
|
||||
return true;
|
||||
return !(k.offset + k.size <= inst.constant || inst.constant + info.size <= k.offset);
|
||||
// Signed, as the offsets are: -16 + 16 mustn't wrap to below -12.
|
||||
const s64 known = (s32)k.offset, store = (s32)inst.constant;
|
||||
return !(known + k.size <= store || store + info.size <= known);
|
||||
}), known.end());
|
||||
}
|
||||
|
||||
|
||||
@@ -576,11 +576,17 @@ void IRNativeRegCacheBase::DiscardNativeReg(IRNativeReg nreg) {
|
||||
|
||||
void IRNativeRegCacheBase::FlushNativeReg(IRNativeReg nreg) {
|
||||
_assert_msg_(nreg >= 0 && nreg < config_.totalNativeRegs, "FlushNativeReg on invalid register %d", nreg);
|
||||
if (nr[nreg].mipsReg == IRREG_INVALID || nr[nreg].mipsReg == MIPS_REG_ZERO) {
|
||||
// Nothing to do, reg not mapped or mapped to fixed zero.
|
||||
if (nr[nreg].mipsReg == IRREG_INVALID) {
|
||||
// Nothing to do, reg not mapped.
|
||||
_dbg_assert_(!nr[nreg].isDirty);
|
||||
return;
|
||||
}
|
||||
if (nr[nreg].mipsReg == MIPS_REG_ZERO) {
|
||||
// Nothing to store, but whoever flushes is about to clobber it.
|
||||
_dbg_assert_(!nr[nreg].isDirty);
|
||||
DiscardNativeReg(nreg);
|
||||
return;
|
||||
}
|
||||
_dbg_assert_(!mr[nr[nreg].mipsReg].isStatic);
|
||||
if (mr[nr[nreg].mipsReg].isStatic) {
|
||||
ERROR_LOG(Log::JIT, "Cannot FlushNativeReg a statically mapped register");
|
||||
|
||||
@@ -288,16 +288,18 @@ namespace MIPSInt {
|
||||
int imm = _SIMM16_SHL2;
|
||||
int rs = _RS;
|
||||
u32 addr = PC + imm + 4;
|
||||
// The linking ones test rs from before they link, ra included (cpu/cpu_alu/branch_link_ra).
|
||||
const s32 value = (s32)R(rs);
|
||||
|
||||
switch ((op>>16) & 0x1F) {
|
||||
case 0: if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltz
|
||||
case 1: if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgez
|
||||
case 2: if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzl
|
||||
case 3: if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezl
|
||||
case 16: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltzal
|
||||
case 17: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgezal
|
||||
case 18: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzall
|
||||
case 19: R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezall
|
||||
case 0: if (value < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltz
|
||||
case 1: if (value >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgez
|
||||
case 2: if (value < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzl
|
||||
case 3: if (value >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezl
|
||||
case 16: R(MIPS_REG_RA) = PC + 8; if (value < 0) DelayBranchTo(mips, addr); else PC += 4; break;//bltzal
|
||||
case 17: R(MIPS_REG_RA) = PC + 8; if (value >= 0) DelayBranchTo(mips, addr); else PC += 4; break;//bgezal
|
||||
case 18: R(MIPS_REG_RA) = PC + 8; if (value < 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bltzall
|
||||
case 19: R(MIPS_REG_RA) = PC + 8; if (value >= 0) DelayBranchTo(mips, addr); else SkipLikely(mips); break;//bgezall
|
||||
default:
|
||||
_dbg_assert_msg_(false,"Trying to interpret instruction that can't be interpreted");
|
||||
break;
|
||||
|
||||
@@ -236,6 +236,15 @@ namespace MIPSInt
|
||||
PC += 4;
|
||||
}
|
||||
|
||||
// The loads write every lane, whatever mask a pending D prefix has, and leave the prefix pending
|
||||
// (cpu/vfpu/prefix_load).
|
||||
static void WriteLoadedVector(MIPSState *mips, const float *d, int vt) {
|
||||
const u32 dprefix = mips->vfpuCtrl[VFPU_CTRL_DPREFIX];
|
||||
mips->vfpuCtrl[VFPU_CTRL_DPREFIX] = dprefix & ~0xF00;
|
||||
WriteVector(mips, d, V_Quad, vt);
|
||||
mips->vfpuCtrl[VFPU_CTRL_DPREFIX] = dprefix;
|
||||
}
|
||||
|
||||
void Int_SVQ(MIPSState *mips, MIPSOpcode op) {
|
||||
int imm = SignExtend16ToS32(op & 0xFFFC);
|
||||
int rs = _RS;
|
||||
@@ -269,7 +278,7 @@ namespace MIPSInt
|
||||
d[i] = valid ? Memory::ReadUnchecked_Float(addr + 4 * i) : 0.0f;
|
||||
}
|
||||
}
|
||||
WriteVector(mips, d, V_Quad, vt);
|
||||
WriteLoadedVector(mips, d, vt);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -279,13 +288,13 @@ namespace MIPSInt
|
||||
if ((addr & 0xF) || !Memory::IsValid4AlignedAddress(addr)) {
|
||||
Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lv.q");
|
||||
const float zero[4]{};
|
||||
WriteVector(mips, zero, V_Quad, vt);
|
||||
WriteLoadedVector(mips, zero, vt);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef COMMON_BIG_ENDIAN
|
||||
cf = reinterpret_cast<const float *>(Memory::GetPointerUnchecked(addr));
|
||||
WriteVector(mips, cf, V_Quad, vt);
|
||||
WriteLoadedVector(mips, cf, vt);
|
||||
#else
|
||||
float lvqd[4];
|
||||
|
||||
@@ -294,7 +303,7 @@ namespace MIPSInt
|
||||
lvqd[2] = Memory::ReadUnchecked_Float(addr + 8);
|
||||
lvqd[3] = Memory::ReadUnchecked_Float(addr + 12);
|
||||
|
||||
WriteVector(mips, lvqd, V_Quad, vt);
|
||||
WriteLoadedVector(mips, lvqd, vt);
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -465,7 +474,7 @@ namespace MIPSInt
|
||||
if (type == 6) {
|
||||
f[0] = (float)imm; // viim
|
||||
} else if (type == 7) {
|
||||
f[0] = Float16ToFloat32((u16)uimm16); // vfim
|
||||
f[0] = vfpu_h2f_float((u16)uimm16); // vfim, converted like vh2f (cpu/vfpu/vfim)
|
||||
} else {
|
||||
_dbg_assert_msg_( 0, "Invalid Viim opcode type %d", type);
|
||||
f[0] = 0;
|
||||
|
||||
@@ -680,6 +680,10 @@ u32 vfpu_h2f(u16 h) {
|
||||
return sign | ((exp + 112) << 23) | (mant << 13);
|
||||
}
|
||||
|
||||
float vfpu_h2f_float(u16 h) {
|
||||
return vfpu_float_from_bits(vfpu_h2f(h));
|
||||
}
|
||||
|
||||
float vfpu_h2f_lower(float word) {
|
||||
return vfpu_float_from_bits(vfpu_h2f((u16)(vfpu_bits_from_float(word) & 0xFFFF)));
|
||||
}
|
||||
|
||||
@@ -311,6 +311,8 @@ float Float16ToFloat32(unsigned short l);
|
||||
// flushes below 2^-14 to zero and keeps the low ten mantissa bits of a NaN; vh2f flushes
|
||||
// subnormal halves and keeps inf/NaN mantissa bits unshifted.
|
||||
u32 vfpu_h2f(u16 h);
|
||||
// The same, as a float (vfim).
|
||||
float vfpu_h2f_float(u16 h);
|
||||
// vh2f of the lower or upper half of a word, with the word and result as float bits. For the JITs.
|
||||
float vfpu_h2f_lower(float word);
|
||||
float vfpu_h2f_upper(float word);
|
||||
|
||||
@@ -1117,9 +1117,8 @@ int main(int argc, const char* argv[]) {
|
||||
// but not now.
|
||||
CoreParameter coreParameter;
|
||||
coreParameter.cpuCore = (CPUCore)cpuCore;
|
||||
// The pspautotests expectations and frametest references were recorded with the C++ vertex
|
||||
// decoder, and the JIT decoders don't match it everywhere yet.
|
||||
coreParameter.bUseVertexDecoderJit = false;
|
||||
// Like the app, decode vertices with the JIT where the host can run one.
|
||||
coreParameter.bUseVertexDecoderJit = cmdLineOptions.vertexJit.value_or(System_GetPropertyBool(SYSPROP_CAN_JIT));
|
||||
coreParameter.gpuCore = (GPUCore)gpuCore;
|
||||
coreParameter.graphicsContext = graphicsContext;
|
||||
coreParameter.enableSound = false;
|
||||
|
||||
+1
-1
Submodule pspautotests updated: 011a777e66...a9b26db62a.
@@ -87,6 +87,7 @@ class Command(object):
|
||||
tests_good = [
|
||||
"cpu/cpu_alu/cpu_alu",
|
||||
"cpu/cpu_alu/cpu_branch",
|
||||
"cpu/cpu_alu/branch_link_ra",
|
||||
"cpu/cpu_alu/cpu_branch2",
|
||||
"cpu/cpu_alu/cpu_div",
|
||||
"cpu/cpu_alu/shift_encodings",
|
||||
@@ -101,6 +102,8 @@ tests_good = [
|
||||
"cpu/vfpu/prefix_branch",
|
||||
"cpu/vfpu/prefix_ctrl",
|
||||
"cpu/vfpu/prefix_lanes",
|
||||
"cpu/vfpu/prefix_load",
|
||||
"cpu/vfpu/vfim",
|
||||
"cpu/vfpu/vbranch",
|
||||
"cpu/vfpu/vrnd",
|
||||
"cpu/vfpu/overlap",
|
||||
|
||||
Reference in new issue
Block a user