diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h index 04642f16b4..853c15c149 100644 --- a/Common/CodeBlock.h +++ b/Common/CodeBlock.h @@ -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); } } diff --git a/Common/MemoryUtil.cpp b/Common/MemoryUtil.cpp index 7817c37db3..335a280b67 100644 --- a/Common/MemoryUtil.cpp +++ b/Common/MemoryUtil.cpp @@ -41,6 +41,14 @@ #include #endif +#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64) +#include +#include +#include +#include +#define USE_MAP_JIT +#endif + #ifndef _WIN32 #include #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> g_mapJitRanges; + +static bool IsMapJitMemory(const void *ptr) { + std::lock_guard 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 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 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 diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index de55ed159f..96f8daf639 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -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}, diff --git a/Core/CmdLine.h b/Core/CmdLine.h index 66a0f9e9b4..554fc2160c 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -195,6 +195,7 @@ struct CommandLineOptions { // Headless: apply the game's compatibility settings when replaying a GE frame dump. std::optional dumpCompat; std::optional hardwareTransform; + std::optional vertexJit; // Headless: comma-separated clip-distance, cull-distance, depth-clamp, framebuffer-fetch, dual-source-blend. std::optional disableGPUFeatures; std::optional screenshotRenderTarget; diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index d010500e56..4161e8823c 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -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; } diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 016a1ebc81..9093f46e26 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -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); } diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 699cc729ad..35ba6fc4f6 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -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()); } diff --git a/Core/MIPS/IR/IRRegCache.cpp b/Core/MIPS/IR/IRRegCache.cpp index b8ce57a9b5..c88f6a23f1 100644 --- a/Core/MIPS/IR/IRRegCache.cpp +++ b/Core/MIPS/IR/IRRegCache.cpp @@ -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"); diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 98554ebd11..aacfaa74a8 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -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; diff --git a/Core/MIPS/InterpreterVFPU.cpp b/Core/MIPS/InterpreterVFPU.cpp index 838025d1be..a30d66c2af 100644 --- a/Core/MIPS/InterpreterVFPU.cpp +++ b/Core/MIPS/InterpreterVFPU.cpp @@ -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(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; diff --git a/Core/MIPS/MIPSVFPUUtils.cpp b/Core/MIPS/MIPSVFPUUtils.cpp index a40644bd3f..8fcab246cb 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -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))); } diff --git a/Core/MIPS/MIPSVFPUUtils.h b/Core/MIPS/MIPSVFPUUtils.h index c96a87299b..fc2a44b72f 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -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); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 9d13985a7d..089545a6a3 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -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; diff --git a/pspautotests b/pspautotests index 011a777e66..a9b26db62a 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 011a777e6623632ce7dca581782fb0f78cca94f8 +Subproject commit a9b26db62a895c93f87550d37bf3833819bdc49f diff --git a/test.py b/test.py index cbf913ff8d..b73ccee4e9 100755 --- a/test.py +++ b/test.py @@ -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",